Python/Flask と Backbone.js で Markdown エディタを作った

Atom はまだいまいちだし、Sublime Text は使ってると金を要求してくるし、emacs はそろそろ引退かな。Markdown 書くのに Mou とかは便利だけど、長い文章を書いてるとだんだんもっさりしてくる。と、普段使いのテキストエディタをどれにすればいいかここのところずっと考えている。

それとは別に、保存したテキストファイルたちをどう管理すればいいかもよく考える。ファイル名を考えるのがめんどくさい。どこに保存したのか忘れる。ファイルツリーをたどるのがめんどくさい。⌘+s を押すのすらめんどくさい。正直エディタのほうでなんとかしてほしい。

Evernote とか便利だけど、勝手にフォーマットされて困る。コードとか貼付けられない。

というわけで少し前に自分用の Markdown エディタを作った。

tototoshi/gfm-editor

左側にエディタ、右側にリアルタイムプレビューが表示される。Markdown エディタとしてはよくある UI。左上にあるプルダウンでメモを選択することができる。メモはサーバーのポスグレに保存される。保存は左側のエディタに書き込みをした時点で自動で行われる。保存ボタンはない。

簡単な Web アプリだけど自分の用途としては必要十分な感じ。 UI のほうは Backbone.js で APIPython/Flask で作った。Python にしたのは Pygments が使いたかっただけ。

Pygments は py-gfm というライブラリと一緒に使うことで Github Flavored Markdown も扱えるようになる。それについては、「Python で Github Flavored Markdown をレンダリングする」 で書いた。

Backbone.js については使うのほぼ初めてで苦戦したけれど、慣れると確かにきれいに書けるなってことで気に入った。Angular よりいいんじゃないかな。(Angular 使ったことない人のご意見です)

あと _.debounce を覚えたので使った。(こういうの毎回自分で実装してた...) javascriptで発生するイベントを間引く - 終わる世界とコンテンツ

Fluid でネイティブアプリ化する

Web アプリとして作ったのでアクセスするには当然 Web ブラウザでアクセスするんだけど、ネイティブアプリにしたほうがよくわからないけどなんかかっこいい気がする。

node-webkit とか試してみたりもしたけど、モチベーションに対して頑張り過ぎな気もしたのでやめた。

そうこうしてたら Fluid という Web アプリをネイティブアプリっぽくみせかけるアプリを見つけたのでこれで適当にネイティブアプリ化した(ことにした)。

http://fluidapp.com/

先のスクリーンショットは Fluid を作ったので Web アプリなのにネイティブアプリっぽい見た目になっている。あと、アプリケーションのアイコンも設定できるようになる。iconfinder でフリー素材落としてきてつかった。

これでだいたい目的は達成できた。めでたしめでたし。

Scala.js が sbt プロジェクト以外でも使えるようになっていた

Scala.js 0.5.0 では standalone 版が配布されるようになりました。

http://www.scala-js.org/news/2014/06/13/announcing-scalajs-0.5.0/

これで sbt プロジェクトでなくても Scala.js が使えるようになりました。 以下の URL で配布されています。

http://www.scala-js.org/downloads.html

このパッケージには 3 つコマンドが含まれています。

  • scalajsc
  • scalajsld
  • scalajsp

scalajsc は Scala.js のコンパイラです。 scalac のラッパーになっていて、scalac を起動するときに Scala.js の jar もクラスパスに加えてくれるだけの単純なもの。

試して見ましょう。次のような Hello.scala を用意します。

package hello

import scala.scalajs.js
import js.Dynamic.{ global => g }

import js.annotation.JSExport

@JSExport
object Hello {

  @JSExport
  def hello(): Unit = {
    g.alert("Hello")
  }

}
$ scalajsc Hello.scala

すると、hello というディレクトリが生成されました。この中にクラスファイルが含まれています。

$ tree hello
hello
├── Hello$.class
├── Hello$.sjsir
└── Hello.class

0 directories, 3 files

sjsir ってなんだよって感じですね。これは Scala.js が生成する中間ファイルで、気にしないで良いですが、気になる人は scalajsp コマンドでのぞけるっぽいですよ。

$ scalajsp hello/Hello\$.sjsir
module class Lhello_Hello$ extends O ancestors O {
  hello__V(): <notype> {
    <global>["alert"]("Hello".cast[dyn])
  }
  $$js$exported$meth$hello__O(): any {
    this.hello__V();
    undefined
  }
  "hello"(): any {
    this.$$js$exported$meth$hello__O()
  }
  export "Hello"
  hello__(): any {
    this.hello__V();
    undefined
  }
}
;

生成されたクラスファイルをリンクして js を生成するには scalajsld コマンドを使います。 scalajsld コマンドには引数としてクラスパス(つまりhelloディレクトリ)を渡し、 出力先の js ファイルを -o オプションとして渡します。

$ scalajsld -o hello.js hello
Fast optimizing hello.js
Inc. opt stats: reused: 0 -- invalidated: 2726 -- trees read: 219

scalajsld のヘルプを見ればわかりますが、optimize の仕方は fastOpt(-f), noOpt(-n), fullOpt(-u) から選ぶことができます。デフォルトは fastOpt です。

これで、hello.js というファイルが生成されてめでたく使えるようになりました。

<html>
  <head>
    <script type="text/javascript" src="hello.js"></script>
    <script type="text/javascript">
      Hello().hello();
    </script>
  </head>
</html>

f:id:tototoshi:20140622004359p:plain

ちゃんと動いた。

sbt のプロジェクトどころか Scala のプロジェクトである必要もないですね。めでたしめでたし。

PHP の file_get_contents は get どころか post も put も delete も upload もできる

stream_context_create と組み合わせて使います。

手元でてきとーに動かしてた REST API とかで試してます。

get

普通ですね。

<?php
$content = json_decode(file_get_contents("http://localhost:5000/api/note/161"));

post

<?php
$context = stream_context_create(
    array(
        'http' => array(
            'method'=> 'POST',
            'header'=> 'Content-type: application/json; charset=UTF-8',
            'content' => json_encode(
                array(
                    'title' => 'file_get_contents で POST',
                    'raw' => "file_get_contents で POST\nPHP すごい...\n"
                )
            )
        )
    )
);

file_get_contents('http://localhost:5000/api/note', false, $context);

なるほど〜。file_get_contents は file_post_contents だったのか〜。

put

<?php
$context = stream_context_create(
    array(
        'http' => array(
            'method'=> 'PUT',
            'header'=> 'Content-type: application/json; charset=UTF-8',
            'content' => json_encode(
                array(
                    'id' => 162,
                    'title' => 'file_get_contents で PUT',
                    'raw' => "file_get_contents で PUT\nPHP すごい...\n"
                )
            )
        )
    )
);

file_get_contents('http://localhost:5000/api/note/162', false, $context);

なるほど〜。file_get_contents は file_put_contents だったのか〜。

delete

<?php
$context = stream_context_create(
    array(
        'http' => array(
            'method'=> 'DELETE'
        )
    )
);

file_get_contents('http://localhost:5000/api/note/162', false, $context);

なるほど〜。file_get_contents は file_delete_contents だったのか〜。

upload

<?php
$upload_content = file_get_contents('upfile.txt');

$context = stream_context_create(
    array(
        'http' => array(
            'method'=> 'POST',
            'header'=> 'Content-Type: multipart/form-data; boundary=-PHP_FILE_GET_CONTENTS',
            'content' => "---PHP_FILE_GET_CONTENTS
Content-Disposition: form-data; name=\"upfile\"; filename=\"upfile.txt\"
Content-Type: text/plain

{$upload_content}
---PHP_FILE_GET_CONTENTS
"
        )
    )
);

file_get_contents("http://localhost:5001/test_upload.php", false, $context);

なるほど〜。file_get_contents は file_upload_contents だったのか〜。

ひとこと

無理すんなや。

Scala.js を情報商材風に説明する

天下一altJS武闘会Scala.js の紹介をしました。

Scala.js 楽しいよって言っても全く流行る気配ないので、 情報商材風のスライドを作ってみました。洗脳されちゃって下さい。

f:id:tototoshi:20140608174751p:plain

http://tototoshi.github.io/slides/tenka1altjs-scalajs/

fork元を明示したいところですが、 普通によくある怪しげな情報商材サイトなのでやめておきます。

Play 2.4 と Dependency Injection

Play 2.3 が出たばっかで 2.4 の話をします。

前置き: Scala での DI

Scala では DI についてのベストプラクティスと言える方法はなく、まだ意見が分かれている状態です。大きく割ると DI コンテナなどを使った動的な DI と、cake pattern, implicit parameter, macro, reader monad などを利用した静的な DI の 2 パターンです。

動的な DI と静的な DI にはそれぞれメリットとデメリットがあります。動的な DI のメリットは Java のわりと優秀な DI コンテナ (Guice とか) が使えて比較的取っ付きやすいこと、デメリットは型安全ではないところ。静的な DI のメリットは型安全、デメリットは trait だの macro だの implicit parameter だの、コンパイル時間が伸びる要因満載であることです。

また静的な DI を行うとコードが少し複雑になってしまう傾向もあります。私は良く cake pattern で DI をしていますが、いくつもの trait を組み合わせてオブジェクトを組み立てていくと実装がばらけてしまい、コードが追いづらくなるという批判は納得しています。オブジェクトの継承関係が複雑になると、初期化順でハマったりもします。implicit parameter や reader monad による DI はあまりしませんが、まあ多くの開発者はそんなフレンドリーとは受け取ってくれないでしょう。

型安全好きな Scala コミュニティとしては当然静的な DI が良いよねと言いたいんでしょうが、現状はいろいろ問題があるので、DI は定期的によく盛り上がる(燃え上がる)話題の一つです。

Play 2.4 の DI

Play には DI の機能は一部あるものの、まだ本気ではない感じです。 そんな中 play-framework dev(開発者 ML) に、James Roper さんから DI の話題が投下されました。

Play 2.4 - Dependency Injection

要旨をまとめると

  • Play としては DI を動的に行うのか、静的に行うのか、どちらか一方の立場を取るつもりはない。ただしドキュメントに載せるなら動的な方法が良い気がする。
  • Play 2.4、そして Play 3.0 では constructor injection を基本にしたい
  • play.api.Play.current が状態を持たせるよりも constructor injection を基本にしたほうがテスタビリティは上がるだろう

constructor injection と言ってるのはまあこんな感じです。

class MyController(wsClient: WSClient) {
  wsClient.url(...)
}

constructor で WSClient という依存性を渡しています。まあ普通っすね。普通っぽいですが、これができると Play ではかなりテストがしやすくなります。

現状では Play の各モジュールは play.api.Application というグローバルな状態を持つクラスにべったりと依存しています。ピンとこない人に説明すると、良く何も考えずに import しているあれです。

import play.api.Play.current // これが play.api.Application のインスタンス

設定などアプリケーションの情報はこのインスタンスがなんでもかんでも持っています。そのため、Play のテストではこの Play.api.Application の Fake を作ってあげる必要があります。サードバーティプラグインを使ったり、データベースの設定を変えたり、ちょっと凝ったことをやりたくなるとこれがなかなかめんどくさい。

// Play のドキュメントから
class ExampleSpec extends PlaySpec with OneAppPerSuite {

  // Override app if you need a FakeApplication with other than
  // default parameters.
  implicit override lazy val app: FakeApplication =
    FakeApplication(
      additionalConfiguration = Map("ehcacheplugin" -> "disabled")
    )

  "The OneAppPerSuite trait" must {
    "provide a FakeApplication" in {
      app.configuration.getString("ehcacheplugin") mustBe Some("disabled")
    }
    "start the FakeApplication" in {
      Play.maybeApplication mustBe Some(app)
    }
  }
}

グローバルなオブジェクトに状態を持たせるのは、良くないなあと思いつつも、実際 Web アプリの場合はこういうグローバルなものがあったほうがやっぱり便利かなと思ってしまうこともあるのでよくわかりません。ただそういう設計ではやっぱりテストしづらいよね。もっとシンプルな Constructor Injection を基本にしたフレームワークにしてテストをもう少し楽に書けるようにしようね、というのが Play 2.4 そして Play 3.0 の方針だそうです。

実際どのような DI の方式が採用されるのか、どのくらいの規模の変更になるのかは知りませんが、Play 2.4、Play 3.0 のことを考えるならあまり play.api.Application(play.api.Play.current) に頼ったコードは書かないよう気をつけていたほうが移行が楽になるかもしれません。

いやあしかしすでにもう Play 3.0 の話題が出る時期なんですね。

middleman で簡単なドキュメントサイトを作る

play-flyway のドキュメントサイトを作るのに middleman を使った。そのメモ。

インストール

$ gem install middleman

最低限のコマンド

middleman init でひな形を作成。

$ middleman init website

...

      create  website/.gitignore
      create  website/config.rb
      create  website/source/index.html.erb
      create  website/source/layouts/layout.erb
      create  website/source/stylesheets
      create  website/source/stylesheets/all.css
      create  website/source/stylesheets/normalize.css
      create  website/source/javascripts
      create  website/source/javascripts/all.js
      create  website/source/images
      create  website/source/images/background.png
      create  website/source/images/middleman.png

source/ 以下のファイルを編集していくことになる。

開発用サーバの起動は middleman server で。

$ bundle exec middleman server

できあがったら middleman build でHTML にエキスポートできる。

$ bundle exec middleman build

Markdown を使えるようにする

redcarpet を Gemfile に加える。オプション次第で GFM っぽくなる。

# Gemfile
gem "redcarpet"
# config.rb
set :markdown_engine, :redcarpet
set :markdown, :tables => true, :autolink => true, :gh_blockcode => true, :fenced_code_blocks => true

これでファイルの拡張子を .markdown にすると markdown で書けるようになる。

シンタックスハイライト

middleman-syntax を使う

# Gemfile
gem "middleman-syntax"
# config.rb
activate :syntax

感想

  • reloadとか勝手にしてくれるし使いやすかった。
  • middleman 流の asset の扱いがよくわかってない、というかまじめに調べてない。layout.erb で普通に script タグ書いたりしたけど、まあいっかという感じ。

play-flyway 1.1.0 をリリースしました

tototoshi/play-flyway

Play 2.3 用に play-flyway 1.1.0 をリリースしました。 pull-req 下さった皆様ありがとうございました。

変更点

Flyway 3.0 への更新は API 的にはさほど変化なく、package 名の変更くらいで済みました。

管理ページに bootstrap を当ててややかっこ良くしました。

f:id:tototoshi:20140601164120p:plain