hatena-vimを入れてみた
書いた記事の編集もできる.素晴らしいな.
githubにあります.セットアップの仕方については適当にぐぐる.
GitHub - motemen/hatena-vim: Vim scripts for posting/updating hatena diary/group
第13回北海道開発オフに参加
第13回北海道開発オフに参加してきました。
「大量ドロリッチ襲来」「変なキャンディー」「小さなすもけさんと大きなすもけさん」「ランチ大分裂時代」「@shuji_w6e&@hadzimmeサシで定食屋事変」「リアルハヂモiPhoneから参加」などなど、今回も盛りだくさんでお送りいたしました。終わってみれば19人(20人?)の大所帯。ホントすごいですね開発オフ。次回は寒そうなので、会場サイドからお許しいただけそうなら出前でも取りましょうそうしましょう。
さて、今回取り組んだことを紹介します。
現在、ブラウザ上で動くチャットアプリにボットを繋げることを考えています。チャットアプリには、ShootingStarという、Cometサーバを用いたウェブアプリを作れるRailsプラグインを使用しています。
Cometの特長は、こちらから何度もサーバにリクエストを送らなくても、更新があったタイミングでサーバから情報が送られる、Push型のアプリが実現できることです。Lingrなどでよく知られていると思います。
ブラウザ上では、その通信を行うために、Ajaxを使用しています。ボットがこの通信を行うために、JavaScriptでやっているのと同じやり方で、非同期通信を行うためのプログラムを書きます。
普段使っているRubyで、Cometサーバと通信するプログラムをガーッと書きました。以下にそのまま載せます。
require "rubygems" require "httpclient" require "json" module Longpoll @connection = "connect" def self.start strike = "http://localhost:3000/meteor/strike" c = HTTPClient.new c.receive_timeout = 86400 res = c.post_content( "http://localhost:8080/chat", "__p__" => @connection, "__t__" => "xhr", "execute" => strike, "sig" => 12345678, "uid" => "hadzimme") args = *res.match(/meteorStrike.execute\("*([^"]+?)"*,"([^"]+?)"\)/) res = c.get_content( strike + "/" + args[1] + "?" + args[2], "format" => "json") chat = JSON.parse(res) if chat.size > 0 puts "#{chat["chat"]["name"]}> #{chat["chat"]["message"]}" end if @connection == "connect" @connection = "reconnect" end start end end Longpoll.start
Cometサーバとの通信では、クライアントが最初にサーバにリクエストを送るところは通常と同じですが、サーバはすぐにレスポンスを返しません。Webサーバ上で何らかの更新があったタイミングで、リクエストを預かっているクライアント全部にレスポンスを返します。クライアントはレスポンスを受け取って、すぐに次のリクエストを送ります。これを何度も繰り返します。つまり、ほとんどの時間は、サーバがリクエストを預かったままになっているわけです*1。
RubyのコードではHTTPClientを使っていますが、receive_timeoutがデフォルトで60になっており、サーバから60秒間レスポンスがないとタイムアウトになってしまいます。なので、このタイムアウトを長めに設定しておくのがポイントです。レスポンスを受け取ったら、自身を呼び出して次のリクエストを送ります。
このアプリでは、Cometサーバからのレスポンスが、「meteorStrike.execute()」というJavaScriptのインスタンスメソッドになっています。このメソッドにより、ブラウザからRails側のmeteor/strikeメソッドを呼び出して、発言内容やイベントを取得しています。今回のコードでも、それと同じ手順を踏みます。まずpost_contentでCometサーバからの通知を受け取り、次のget_contentでWebサーバ上の更新を取得します。meteor/strikeメソッドは、発言者名と発言内容をJSONを用いたレスポンスとして返すことができるように書き加えてあります。
ボット自体の発言は、単にPOSTメソッドで送ってやればよいです。これらを組み合わせることで、ブラウザ上の非同期チャットアプリでボットとおしゃべりをすることができるようになります。
*1:そのプロセスをサーバがどう処理するのか、というのが問題になりますが、そのへんはうまくゴニョゴニョされているらしいです
andLinuxに入れたRuby 1.9.1からCaboChaを呼んでみた
Ruby 1.9.1はすんなり入る気がする。ソースから。今回はruby(1.8)とruby19(1.9.1)に分けた。
$ ./configure --program-suffix=19 $ make $ make test $ sudo make install $ ruby -v ruby 1.8.6 (2007-06-07 patchlevel 36) [i486-linux] $ ruby19 -v ruby 1.9.1p0 (2009-01-30 revision 21907) [i686-linux]
CaboChaについてはSWIGやらGCCやらが新しくなってたりするあたりでいろいろコケたので、備忘録。
まず、SWIGは1.3.29から%typemapに言語の名前を入れてはいけないと言われるので、#ifdef(#if defined())で書きなおす。
cd cabocha-0.53/swig
で、diffとった。
--- arg.i.orig +++ arg.i @@ -1,11 +1,12 @@ %include typemaps.i /* java */ -%typemap(java,jni) (int argc, char *argv[]) "jobjectArray" -%typemap(java,jtype) (int argc, char *argv[]) "String[]" -%typemap(java,jstype) (int argc, char *argv[]) "String[]" +#if defined(SWIGJAVA) +%typemap(jni) (int argc, char *argv[]) "jobjectArray" +%typemap(jtype) (int argc, char *argv[]) "String[]" +%typemap(jstype) (int argc, char *argv[]) "String[]" -%typemap(java, in) (int argc, char *argv[]) (jstring *jsarray) { +%typemap(in) (int argc, char *argv[]) (jstring *jsarray) { int i; $1 = JCALL1 (GetArrayLength, jenv, $input); @@ -24,7 +25,7 @@ %typemap(argout) (int argc, char *argv[]) "" /* override char *[] default */ -%typemap(java, freearg) (int argc, char *argv[]) { +%typemap(freearg) (int argc, char *argv[]) { int i; for (i = 0; i < $1; i++) { JCALL2 (ReleaseStringUTFChars, jenv, jsarray$argnum[i], $2[i]); @@ -33,7 +34,8 @@ } /* perl */ -%typemap(perl5,in) (int argc, char *argv[]) { +#elif defined(SWIGPERL) +%typemap(in) (int argc, char *argv[]) { AV *tempav; SV **tv; I32 len; @@ -56,12 +58,13 @@ $2[i] = 0; } -%typemap(perl5,freearg) (int argc, char *argv[]) { +%typemap(freearg) (int argc, char *argv[]) { delete [] $2; } -/* python */ -%typemap(python,in) (int argc, char *argv[]) { +/* python */ +#elif defined(SWIGPYTHON) +%typemap(in) (int argc, char *argv[]) { int i; if (!PyList_Check($input)) { SWIG_exception(SWIG_ValueError, "Expecting a list"); @@ -84,12 +87,13 @@ $2[i] = 0; } -%typemap(python,freearg) (int argc, char *argv[]) { +%typemap(freearg) (int argc, char *argv[]) { delete [] $2; } /* ruby */ -%typemap(ruby,in) (int argc, char *argv[]) { +#elif defined(SWIGRUBY) +%typemap(in) (int argc, char *argv[]) { int i; if (TYPE($input) != T_ARRAY) { @@ -111,6 +115,7 @@ $2[i] = 0; } -%typemap(ruby,freearg) (int argc, char *argv[]) { +%typemap(freearg) (int argc, char *argv[]) { delete [] $2; } +#endif
これでSWIGは通る。
$ cd cabocha-0.53/swig $ make ruby
さらにこのあと、コンパイルでコケる。たぶん、GCCのバージョンが新しいせいでエラーが出てる。ちょこちょこ書き直し。ostringstreamのところは、正しいのかどうかよくわからない。
cd /usr/local/include
--- cabocha.h.orig +++ cabocha.h @@ -107,7 +107,7 @@ #include <string> #include <vector> #include <iostream> -#include <strstream> +#include <sstream> namespace CaboCha { @@ -129,7 +129,7 @@ private: FreeList<char> *freelist1; FreeList<Chunk> *freelist2; - std::ostrstream *ostrs; + std::ostringstream *ostrs; std::ostream& writeLattice (std::ostream &, int); std::ostream& writeTree (std::ostream &, int);
Ruby 1.9.1ではRArrayの仕様が変更されてるっぽい。includeのほうは、言われるままに書き換え。
$ cd cabocha-0.53/ruby
--- CaboCha_wrap.cpp.orig +++ CaboCha_wrap.cpp @@ -1576,7 +1576,7 @@ #ifdef __cplusplus extern "C" { #endif -#include "rubyio.h" +#include "ruby/io.h" #ifdef __cplusplus } #endif @@ -2797,7 +2797,7 @@ if (TYPE(argv[0]) != T_ARRAY) { SWIG_exception(SWIG_ValueError, "Expected an array"); } - arg1 = RARRAY(argv[0])->len; + arg1 = RARRAY_LEN(RARRAY(argv[0])); if (arg1 == 0) { SWIG_exception(SWIG_ValueError, "List must contain at least 1 element"); }
これで一応コンパイルが通り、Ruby 1.9.1から呼べるようになった。
あと、一番ハマってたのは、make installするのを忘れてたこと。