あんパン

こしあん派

高専カンファ100で発表します

高専生ではないのですが、スタッフに高専生じゃない人もいるということで、高専カンファレンス100 in 東京で発表させていただくことになりました。

まだタイムテーブルなどは出ていないのですが、そのうち出るような気がします。高専カンファ100自体は12/19、12/20の2日続けて開催で、自分の発表は複数トラック発表のため20日の方になります。(19日も見に行くかもしれないです。)

すでにタイトル/概要は提出済みで、ScratchXの話をしようと思っています。ScratchはMITで開発されたビジュアルプログラミング環境で、ScratchXはこれを元にJavaScriptで拡張できる実験的リリースという立ち位置のツールです。

f:id:masawada:20151125012459p:plain

Scratch自体難しいツールではない(日本語、ひらがなで扱えるため小学生にもユーザが多い)ので、プログラミングに関する知識などなくてもある程度は楽しめる発表にする予定です。
ぜひお越しください。

詳しくはこちら: 高専カンファレンス100 in 東京 - 高専カンファレンス Wiki
場所がなんと電通大なので、定期圏内ということで最高に素敵。

dokku上で動作する係り受け解析APIサーバの作り方

係り受け解析はあくまで例なので、これに関して詳しく知りたい人は他をあたってください。本稿はdokku(Bashスクリプトで作られたHerokuライクなオンプレミスPaaS環境)上でCaboChaを用いて係り受け解析をする簡単なAPIを作成する、というシナリオで、システムコマンドをインストールするような複雑なアプリケーションの作成方法を解説するものです。

といっても、その方法はお粗末なものなので、より良い方法があるかもしれません。あくまで手軽に実験することができる、程度で読んでください。

dokkuについて

dokkuはBashスクリプトで作られたHerokuライクなオンプレミスPaaS環境です。Dockerコンテナを作成し、Herokuが提供しているbuildpackを利用してアプリケーションをデプロイします。インストールやHTTPS化などについては以下を御覧ください。

masawada.hatenablog.jp

係り受け解析について

係り受け解析というと馴染みのない方もいらっしゃると思います。ある文中の文節間にどのような修飾(係)・被修飾(受)関係があるかを解析するものです。形態素解析では文を品詞に分解するところまでを行いますが、係り受け解析をすることにより、自然言語の文をよりシステムが理解しやすいように落としこむことができます。

日本語ではCaboChaを使うのが一般的ではないかと思います。今回はこのCaboChaを用いて係り受け解析をするAPIサーバを作成する、ということを目標にします。

本題

というわけで、ここからはcabocha-apiという名前のアプリケーションを作成してdokkuにデプロイするまでを解説していきます。Herokuで少し複雑なアプリケーションを作ったことがある方はすぐに理解できるかと思います。

dokkuでheroku-buildpack-multiを使う

buildpackとは、言語やフレームワーク、アプリケーション等を手軽に利用できるようパッケージ化したものです。Herokuによって作られたものや、ユーザによって作られたものが存在します。

通常、Herokuにアプリケーションをデプロイする際は1つのbuildpackが自動的に選択されます。ffmpegやmecab等のbuildpack使いたい場合は言語のbuildpackとあわせて複数のbuildpackを利用できるようにする必要があります。複数のbuildpackを扱うには、buildpackとしてheroku-buildpack-multiを指定します。

$ dokku apps:create cabocha-api
$ dokku config:set cabocha-api BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

これでdokkuのアプリケーション作成と複数buildpackを扱う設定がおわりました。

buildpackを設定する

MacにHomebrewがあるのと同じく、LinuxにはLinuxbrewがあります。LinuxbrewはHomebrewのforkです。そのため、Homebrewで入れられるアプリケーションのほとんどはLinuxbrewでも入れることができます。本稿ではLinuxbrewを用いてCaboChaをインストールします。

ところが、本家のLinuxbrewではCaboChaをインストールすることができません。素直にインストールしようとするとlibiconvがない、とエラーを吐いてデプロイプロセスが強制終了します。そこで、独自のCaboChaインストーラを利用するために修正を施したheroku-buildpack-linuxbrewを用います。また、APIサーバをRubyで書くのでRubyのbuildpackも設定します。

以下を.buildpacksに記述してください。

https://github.com/masawada/heroku-buildpack-linuxbrew.git
https://github.com/heroku/heroku-buildpack-ruby.git

CaboChaのインストールを設定する

heroku-buildpack-linuxbrewは.cellarというファイルにアプリケーション名を記述するだけで、デプロイ時にそのアプリケーションを自動的にインストールします。ところが、本家のheroku-buildpack-linuxbrewでは独自のHomebrew formulaを利用することができません(brew tapすることができない)。そこでmasawada/heroku-buildpack-linuxbrewではtapをすることができるよう改良を施しています。

CaboChaのformulaにはlibiconvのオプションを設定している箇所があり、これが悪さをしているため、オプションを外した独自formulaをGitHub上に用意し、これを利用することにします。

以下のように.cellarを記述することで修正したCaboCha formulaを利用することができます。

tap masawada/cabocha-without-iconv
install cabocha-without-iconv

環境変数を設定する

ここからはdokkuのアプリケーションに環境変数を幾つか設定していきます。これを行わないとLinuxbrewでのビルドがうまくいかなかったり、サーバに用いるcabocha.gemのインストールがうまくいかなかったりします。

$ dokku config:set cabocha-api LIBRARY_PATH=/app/.linuxbrew/lib
$ dokku config:set cabocha-api LD_LIBRARY_PATH=/app/.linuxbrew/lib
$ dokku config:set cabocha-api LD_INCLUDE_PATH=/app/.linuxbrew/include
$ dokku config:set cabocha-api C_INCLUDE_PATH=/app/.linuxbrew/include
$ dokku config:set cabocha-api CPLUS_INCLUDE_PATH=/app/.linuxbrew/include
$ dokku config:set cabocha-api PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app/.linuxbrew/bin

デプロイ後に環境変数を設定してからrebuildするとうまく設定されないようなので、必ず最初に設定するようにしてください。

APIサーバを書く

ここからはRubyとSinatraを用いてAPIサーバを書いていきます。

  • Gemfile: 依存ファイル
  • server.rb: 本体
  • Procfile: dokku上でアプリケーションを起動するためのスクリプト
    • web: commandという形式で書くことで、アプリケーションを起動し、リバースプロキシを設定する

また、事前にbundle installを実行してGemfile.lockを生成してください。これがない場合、デプロイ時にプロセスが強制終了します。

Gemfile

source 'https://rubygems.org'

gem 'sinatra'
gem 'cabocha'

server.rb

require 'sinatra'
require 'cabocha'

get '/' do
  parser = CaboCha::Parser.new
  tree = parser.parse(params["q"])
  @result = tree.toString(CaboCha::OUTPUT_RAW_SENTENCE)
  erb :index
end

__END__

@@index
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
  <style>pre { font: normal 14px/20px courier; }</style>
</head>
<body>
  <pre>
<%= @result %>
</pre>
</body>
</html>

Procfile

web: ruby server.rb -p $PORT

デプロイする

ここまできたらいつもどおりデプロイするだけです。上記までの作業をgit commitし、git remote add dokkuでdokkuの情報を登録します。

$ git push dokku master

でデプロイが始まります。幾つかのファイルをビルドするので、全作業を終えるのに数分かかります。作業を終えたらサーバにアクセスしてみましょう。ルートに?q=文章とクエリをつけることで、その文章の係り受け解析を行うことができます。

f:id:masawada:20150922174750p:plain

このように表示されれば成功です。

まとめ

多少手順は複雑でしたが、作業量としてはそんなに多くないのではないかと思います。Herokuの場合でも環境変数の設定をうまく行えば全く同じリポジトリをデプロイできるような気がしますが、なぜか失敗しました。今回はdokkuで動かすまで、ということでここまでにしておきます。

最後に、ここで作成したアプリケーションを以下のリポジトリに置いていますので、よろしければご活用ください(そのまま使うのは良くないです、アクセス制限をかける、入力文字列を制限するなどしたほうが良いかもしれません)。

github.com

Android版Firefoxでは文字選択後にtouchendが発火しない

というかマジかこれ

Android版Firefoxで文字選択をすると、まずselectionchangeが発火しない。これ自体は標準でそうなっているので仕方ないという感じがする。

詳細はここらへんを見るとわかる気がする。

もうselectionchangeが来るのは望み薄な気がする。それは良いとして、Android版のFirefoxだと文字選択をしてそのまま指を押し上げるとtouchendが発火しない。この時点でなんなんだこのブラウザはと思うんだけど、これもまぁiOSとかAndroid版Chromeと同じ挙動ではある気がする。

そして、何故か選択を解除するとtouchendが発火する。これが謎。

頼むからブラウザだけじゃなくてスマートフォンのイベント仕様もちゃんと整えてくれという気持ちでこの記事を書いています。

Android Chrome/標準ブラウザとiOSのSafariについてはこちら

masawada.hatenablog.jp

AndroidではCSSの:activeが使いづらい話

割と一般的なのかもしれないけどつい最近知ったネタなので共有します。

CSSには:activeという擬似セレクタがあって、要素をクリックしている間だけ特定のスタイルを有効にすることができます。スマートフォンのブラウザにも当然この:activeは存在しており、タップすることでスタイルを有効にすることができます。

ところが、AndroidのChromeのみ若干挙動が異なっており、:activeが使いドコロの難しい機能になっています。というのも、タップではなく長押ししないと有効にならないのです。

stackoverflow.com

The :active pseduo works on Chrome for Android, but only after a long press (and cancel of the dialog), it doesn't work for any other element as far as I can tell. It looks like a bug, and I have just raised it with the team.

とのことで、この raise it は

173976 - :active pseudo class does not work on Chrome for Android - chromium - Monorail

を指しています。どうやら

<meta name="viewport" content="user-scalable=no, initial-scale=1">

をheadタグ内に記述するか、または

<body ontouchstart="">

とすると解決するらしいのですが、これはあまりにも汚いハックだと思います。(しかも前者はコードの有無で挙動が変わってしまう)

が、せざるを得ないという状況もあるため覚えておくと良さそうです。なお、JavaScriptでontouchstartontouchendontouchcancelを拾うとかをするのも良さそうです。

とはいえ、押下時にスタイル変更範囲がならユーザが気づかないということもあるため、そこまでコストをかけてやるべきことかという疑問は残ります。やる気と時間があるならやってもよさそうというスタンスでいるべきと思いました。

iOSのSafariはselection.removeAllRangesの挙動がおかしい

selectionwindow.getSelection()で取得することができます。

通常のウェブブラウザだとselection.removeAllRanges()を実行すると選択範囲が解除されます。iOSのSafariではこれが解除されずに残ります。

また、selectionrangeCountという選択範囲の数を保持するプロパティがあり、通常は選択が解除されるとこれが1から0になります。iOSのSafariではselection.removeAllRanges()を実行すると選択範囲は解除されないままrangeCountが0になります。.toString()の結果も空文字列となります。

同様に、addRangeでrangeを追加すると選択範囲が変更されないままrangeCountが1になります。

ざっと調べましたがどうも解除方法はない(結構前から質問は上がっているがまともに動く回答がない)ようなので、各位頑張っていきましょう。