2015年4月2日木曜日

[Ruby][Hipchat]Hipchat用のBotをLitaで作る(2)

前回に引き続き、LitaでBotを作る。
今回は簡単な応答をするようにしてみよう。

後でgemで公開するつもりなら、
$ lita handler lita-ハンドラ名称
とすれば、雛形を作ってくれるようだ。

今回は単純に、「@Bot名 hello」と書きこみがあったら、「〜さん、こんにちは。」と応答するだけのHandlerを作る。
ソースは以下の通り。
module Lita
  module Handlers
    class HelloHandler < Handler
      route(/^hello/, :hello, command: true)

      def hello(response)
        response.reply("#{response.user.name}さん、こんにちは。")
      end
    end
  end

  Lita.register_handler(Handlers::HelloHandler)
end
routeで応答するメッセージの正規表現と呼び出すメソッド(:hello)を指定し、
呼び出されるメソッドhelloでは response.user.nameでメッセージを書いたユーザ名を参照し、response#replyで、応答メッセージを返している。
routeメソッドで command: trueを渡しているのは、@Bot名で、Botを宛先にしたメッセージだけに応答するためである。
commandの詳細については、こちらを参照。

デバッグ中は lita_config.rb を以下のように設定すると、コマンドラインで動作確認できる。
config.robot.adapter = :shell
config.adapters.shell.private_chat = true 
ただし、この場合は、routeメソッドでcommand: false にし、 テスト時には、@Bot名を付けずに、helloだけにしないと応答しない。
      route(/^hello/, :hello, command: false)
コマンドラインでの実行例
$ lita
Type "exit" or "quit" to end the session.
Lita Bot > hello
Shell Userさん、こんにちは。
Lita Bot > 

2015年3月29日日曜日

[Ruby][Hipchat]Hipchat用のBotをLitaで作る

仕事場でHipchatを使い始めた。
Botを作って、いろいろと便利な機能を提供したい。
関連する情報を検索すると、Hubot + Hipchat用のアダプタという例が多数あった。
しかし、Debian Wheezy で試してみると、Hipchatのアダプタ(hubot-hipchat)のインストールに失敗してしまう。
最終的には、node.js のバージョン落として、v0.10.38でインストールできたのだけど。

気に入らなかったので、RubyでHipchatと連携できるものが無いのか調べたら、
Lita - A robot companion for your company's chat room
というのがあった。
こちらの解説記事がまとまっており、このページに書かれている手順で、動かすことができた。
以下、Botでinfo表示、whois、Googleのイメージ検索ができるようにする設定を、まとめておく

環境

環境は以下の通り。
  • Debian Wheezy
  • rbenvでruby-2.2.0をインストール済み
  • apt-getでredis-server をインストール済み

準備

Hipchatで利用するBotアカウントを作成しておく。
アカウント作成にはメールアドレスが必要。
gmailのアカウントがあれば、メールアドレスに+を使って、複数アカウトを作る方法がお手軽だ。 (ここらへんを参考に。)

Litaのインストール

Litaはgemでインストールできる。
$ gem install lita

Botの作成

Bot用のプロジェクトを作る。
$ lita new bot
botディレクトリに、新しいLitaのプロジェクトが作成される。

Gemfileを編集する。
HipChatアダプタを使う。
gem "lita-hipchat"
の行をコメントアウトする。
$ bundle install
で反映させる。

lita_config.rbを編集する。
# 名前を合わせておかないと起動時にエラーになるので注意。
config.robot.name = "Lita Bot"
# Hipchatを利用する。
config.robot.adapter = :hipchat
# ポート番号はデフォルトは1234になっていたが、6379にしないと起動しなかった。
config.redis.host = "127.0.0.1"
config.redis.port = 6379
# Hipchatの管理画面からXMPP/Jabber Account Informationに表示されるJabber IDを設定する。
config.adapters.hipchat.jid = "省略@chat.hipchat.com"
# Hipchatのアカウントのパスワード
config.adapters.hipchat.password = "省略"
# trueにしておくと、デバッグログが表示される。接続できない場合に便利。
config.adapters.hipchat.debug = true
# 利用するChat roomの名前か :all
config.adapters.hipchat.rooms = :all

Litaの起動

$ lita
設定が間違っているとエラーログが表示されるので確認する。
問題がなければ、BotはChat roomに参加する。

Hipchatで動作確認

@lita info
LitaとRedisの情報が表示される。

whoisとGoogleのイメージ検索の利用

Gemfileに次の設定を追加する。
gem "lita-google-images"
gem "lita-whois"
lisaを再起動する。
whoisは
@lita whois ホスト名
イメージ検索は
@lita image cat
で利用できる。


機能の拡張

Botが応答するwhoisやimageの処理は、Handlerとして実装できるようなので、まずは、単純に、ユーザの入力に応答をするものを作ってみよう。

参考

2015年3月26日木曜日

[FPGA][コンピュータ開発] DE0の関連書籍

DE0とパソコンは接続できたが、これから先、どうすれば良いのかさっぱり分からない。
本屋とAmazonを徘徊して、良さそうな本をいくつか購入した。

FPGA ボードで学ぶ組込みシステム開発入門 ~Altera編~ 

DE0を使う上での定番の書籍みたい。
開発ソフトQuartus2の使い方に始まり、Nios2やμClinux等、幅広く、いろいろな記事がある。

インターフェースZERO No.04 Hello Worldから始めるFPGA入門: 2大メーカXilinx,Alteraのお手軽ボードでチョコッと体験! 

表紙に、本書の内容を試せる定番FPGAボードとして、DE0-nanoとDE0が載っていたので購入。
Altera社のFPGAでLEDをチカチカさせるという記事があるので、まずはこれを試すのが良いかな。
HDLの説明や、基本的な例、オセロゲームを作るなど、チョコッと体験!というタイトルのわりには、いろいろ書いてある。

FPGA/PLD入門記事全集[2200ページ収録CD-ROM付き]: 月刊トランジスタ技術,Interface,Design Wave Magazine10年分(2001-2010)から集大成 (アーカイブスシリーズ) 

Design Wave Magazineで連載していた記事で、基礎から学ぶVerilog HDL & FPGA設計が面白そうだったので購入。この連載の「CPUを作ろう」「CPUの設計」「アセンブラの設計」「コンパイラの設計」のタイトルが興味を引いた。

2015年3月25日水曜日

[FPGA][コンピュータ開発]DE0購入

以前から、FPGAを使ってCPU製作してみたかったのだけど、とうとうDE0を買ってしまった。
最近、DE1-SocDE0-CV が販売されており、どれにしようか悩んだのだけど、書籍やWeb上での情報が豊富なDE0に決定。
digikeyで購入しようと思ったが、すぐにでも欲しくなってしまったので、物が置いてあった千石通商で購入。\17,850だった。円安が原因で、以前より価格が上がっているようだ。

DE0はWindows8.1 64bit版のマシンで使う。
Windows8.1で使えるか、心配だったが、大丈夫のようだ。

Alteraから Quartus2 Web Edition 13.1 をダウンロードして、
QuartusSetupWeb-13.1.0.162.exe
をインストール。
※DE0に載っているCyclone III 3C16 FPGA deviceに対応しているのは 13.1までのようだ。

全てデフォルトのままインストールした。
インストールするもののうち、ModelSim-Altera Editionだけ、チェックが付いていなかった。

DE0に電源を繋いで、USBとPCを接続する。
デバイスマネージャ を見ると、ほかのデバイスに USB Blasterがあるので、
手動で、C:\altera\13.1\quartus\drivers\usb-blaster を指定してドライバをインストールする。
インストールが完了すると、ユニバーサル シリアル バス コントローラー に
Altera USB-Blaster が表示される。

DE0の添付DVDにあった、Getting Started with Altera DE0 board.pdf に書いてあるように、電源を入れると、
  • All user LEDs (LED0~LED9) are flashing
  • All 7-segment displays(HEX0 and HEX3) are cycling through the numbers 0 to F
となっており、いまのところ問題は無い。


2015年1月27日火曜日

[OCaml]godi-ocaml-textでShiftJISからUTF8へ変換

ShiftJISからUTF8に文字列を変換しようと思って、Camomileを久しぶりに使おうとしたのだけど、すっかり使い方を忘れてしまっていた。

iconvライブラリを使った文字列処理のライブラリはないものかなーと探してみたら、
GODI Package godi-ocaml-text
A library dealing with text as sequence of unicode characters.
があった。

モジュール定義を眺めてみたら、すごく簡単そうだったので試してみた。

ライブラリはOPAMでインストールできる。
$ opam install text

utopで試してみた。
utop[16]> #require "text";;
utop[18]> Encoding.recode_string;;
- : src:bytes -> dst:bytes -> bytes -> bytes = 
utop[22]> Encoding.recode_string ~src:"Shift_JIS" ~dst:"UTF8" "\x95\x5c\x8e\xa6";;
- : bytes = "表示"

おお、これは簡単。なかなか良いね。

2014年11月29日土曜日

[OCaml]XML Parser PXPを使ってみた(Bufferモジュールを利用)

前記事の、E_char_dataイベントで文字列を ^ で連結するやり方は効率が悪すぎた。

ブログ更新をtweetしていたら、@camloebaさんに Bufferがあることを教えていただいた。
いつもありがとうございます!

ということで、Bufferを使うように修正してみた。

修正後のプログラム

修正したのはparse関数だけ。
let parse element_func_list =
  let tags = Stack.create () in
  let element_func () =
    let item = List.find element_func_list ~f:(fun (element_list, _) ->
        Stack.to_list tags = element_list
      ) in
    match item with
      Some (_, func) -> Some func
    | None -> None in
  let in_content = ref false in
  let content = Buffer.create 1024 in
  let apply_func = ref (fun _ -> ()) in
  Pxp_ev_parser.process_entity config entry entmng (fun ev ->
      (* let event_string = Pxp_event.string_of_event ev in *)
      match ev with
        Pxp_types.E_start_tag (name, _, _, _) -> begin
          Stack.push tags name;
          (* print_endline @@ List.to_string (Stack.to_list tags) ~f:ident; *)
          match element_func () with
            Some func' -> begin
              apply_func := func';
              in_content := true
            end
          | _ -> ()
        end
      | Pxp_types.E_end_tag (name, _) -> begin
          ignore @@ Stack.pop_exn tags;
          if !in_content then
            begin
              !apply_func @@ Buffer.contents content;
              Buffer.clear content;
              in_content := false;
            end;
        end
      | Pxp_types.E_char_data str -> begin
          if !in_content then
            Buffer.add_string content str
        end
      | _ -> ())
このような修正は、動的な型の言語の場合、必要な修正箇所を見逃しやすいが、 OCamlのような静的な型の言語の場合は、コンパイルエラーで検出できるので非常にやりやすい。
Emacs+Tuaregでコード書いており、保存と同時にエラーがある行の色が変わるので、修正箇所は一目瞭然。

実行結果

satoshi@xubuntu:~/workspace/ocaml-try/pxp$ time ./parse_wiki > /dev/null

real    0m0.093s
user    0m0.081s
sys     0m0.011s
修正前は、
satoshi@xubuntu:~/workspace/ocaml-try/pxp$ time ./parse_wiki > /dev/null

real    0m3.361s
user    0m3.168s
sys     0m0.180s
およそ35倍速くなった!
これなら全く問題なし。
計測していないが、メモリの使用量も、かなり減ったはず。

なお、^による文字列連結に関しては、 Real World OCaml Chapter 3. Lists and Patternsの Performance of String.concat and ^ にも書かれていた。
^ を使うたびに文字列を生成するので、少しずつ文字列を連結して、大きな文字列を作る場合は、非常にパフォーマンスが悪い。

[OCaml]XML Parser PXPを使ってみた

OCamlのXML ParserであるPXPを使ってみた。

処理したXMLは日本語版Wikipediaの文書データ。
この文書データはここからダウンロードできる。

この文書データは、複数のpage要素からなり、 title要素にタイトル、 text要素に内容が書かれている。
今回は、このタイトルと内容を抜き出して、出力する処理を書いてみた。

PXPのインストール

PXPはopamでインストールできる。
$ opam install pxp

PXPについて

treeモードとeventモードがある。
treeモードでは、DOM treeを構築できるようだ。

やりたいことは 大きなサイズの日本語版Wikipediaのデータをパースすること。
DOMを構築する必要はないので、eventモードを使う。

XML data as stream of events を参考にコードを書いてみる。

eventモードにはPush parsingとPull pasingの二種類のパース方法がある。
今回はPush parsingを利用した。

前準備

ここからWikipediaのデータをダウンロードする。
テストが簡単になるように、10ページ分だけ切り出したデータを作っておく。
$ grep -m 10 -n -F "</page>" jawiki-20141122-pages-articles.xml | cut -d: -f1 | tail -n 1 | xargs -I @ head -n @ jawiki-20141122-pages-articles.xml > jawiki_10.xml
$ echo "</mediawiki>" >> jawiki_10.xml


サンプルプログラム


内容を取り出したい要素と、その内容を取り出したときに呼ばれる関数を、 処理関数parseに渡す方式で実装した。

let element_func_list = [
  (["title"; "page"; "mediawiki"], (fun content -> ..snip..));
  (["text"; "revision"; "page"; "mediawiki"], (fun content -> ..snip..))]

let () = parse element_func_list

parse_wiki.ml


open Core.Std

let config = let open Pxp_types in
  {default_config with encoding = `Enc_utf8}
               
let source = Pxp_types.from_file "/home/satoshi/Documents/jawiki/jawiki_10.xml"

let entmng = Pxp_ev_parser.create_entity_manager config source

let entry = `Entry_document []

let element_func_list = [
  (["title"; "page"; "mediawiki"],
   (fun content ->
      print_endline @@ sprintf "Title: %s" content));
  (["text"; "revision"; "page"; "mediawiki"],
   (fun content ->
      let max_length = 40 in
      let content' = 
        if String.length content > max_length then
          (String.slice content 0 max_length) ^ "..."
        else
          content in
      print_endline @@ sprintf "Text: %s" content'))]

let parse element_func_list =
  let tags = Stack.create () in
  let element_func () =
    let item = List.find element_func_list ~f:(fun (element_list, _) ->
        Stack.to_list tags = element_list
      ) in
    match item with
      Some (_, func) -> Some func
    | None -> None in
  let in_content = ref false in
  let content = ref "" in
  let apply_func = ref (fun _ -> ()) in
  Pxp_ev_parser.process_entity config entry entmng (fun ev ->
      (* let event_string = Pxp_event.string_of_event ev in *)
      match ev with
        Pxp_types.E_start_tag (name, _, _, _) -> begin
          Stack.push tags name;
          (* print_endline @@ List.to_string (Stack.to_list tags) ~f:ident; *)
          match element_func () with
            Some func' -> begin
              apply_func := func';
              in_content := true
            end
          | _ -> ()
        end
      | Pxp_types.E_end_tag (name, _) -> begin
          ignore @@ Stack.pop_exn tags;
          if !in_content then
            begin
              !apply_func !content;
              content := "";
              in_content := false;
            end;
        end
      | Pxp_types.E_char_data str -> begin
          if !in_content then
            content := !content ^ str
        end
      | _ -> ())

let () =
  parse element_func_list

実行結果

satoshi@xubuntu:~/workspace/ocaml-try/pxp$ ./parse_wiki
Title: Wikipedia:アップロードログ 2004年4月
Text: <ul><li>14:46 2004年4月30日 [[利用..
Title: Wikipedia:削除記録/過去ログ 2002年12月
Text: Below is a list of the most recent delet...
Title: アンパサンド
Text: {{記号文字|&amp;}}
[[Image:Trebuchet...
Title: Wikipedia:Sandbox
Text: #REDIRECT [[Wikipedia:サンドボック...
..snip.. 

エンコーディングの指定

<?xml 〜?>ディレクティブでUTF-8を指定してみたが、駄目だった。
configでエンコーディングを指定したら、正しく処理できた。
let config = let open Pxp_types in
  {default_config with encoding = `Enc_utf8}
自動でエンコーディングを認識させる方法はあるのかな?

文字列の連結処理

page要素10個分のxmlファイルのサイズは
-rw-rw-r-- 1 satoshi satoshi  533408 11月 28 23:56 jawiki_10.xml
たった500kb弱なのに、想定よりも処理に時間がかかっている。
satoshi@xubuntu:~/workspace/ocaml-try/pxp$ time ./parse_wiki > /dev/null

real    0m3.361s
user    0m3.168s
sys     0m0.180s
ちなみに実行環境はWindows8.1@Core i3のVirtualBox上のXubuntu14.04で、OCaml4.01.0を使っている。
重い環境だと思うが、メモリ不足にもなっていないのに、3秒以上もかかるのは何故だろう。
E_char_dataのイベントで文字を ^ で連結している処理が駄目なのかな?
      | Pxp_types.E_char_data str -> begin
          if !in_content then
            content := !content ^ str  ← ◆ここを () に変更
        end
文字列連結部分を () にしてみるとどうなるかな?
satoshi@xubuntu:~/workspace/ocaml-try/pxp$ time ./parse_wiki > /dev/null

real    0m0.087s
user    0m0.086s
sys     0m0.000s
おおよそ40倍早くなった。これが原因かー。
JavaのStringBuilderに相当するものは、OCamlでは何になるんだろう。

参考