Category Archives: Ruby

Ultraviolet Syntax Highlighting Engineをさくらインターネットのサーバにインストールする

UltravioletはRubyによって書かれた様々なSyntax(文法)に対応したSyntax Highlighting Engineです。
今回はこれをさくらインターネットのサーバにインストールしてみましょう。

RubyGemsを使ってインストールをするのですが、以前RubyGemsのインストール方法については書きました。
cocoa*life – ブログをさくらインターネットへ移行しました。

ということで、RubyGemsのインストールが終わっている状態で書き始めます。

Ultravioletのインストール

正規表現ライブラリOniguruma(鬼車)のインストール

まずは正規表現ライブラリであるOnigurumaをインストールします。
普通にconfigureしてmakeしてinstallすれば良いだけです。

wget http://www.geocities.jp/kosako3/oniguruma/archive/onig-5.9.1.tar.gz
tar zxf onig-5.9.1.tar.gz
cd onig-5.9.1
./configure --prefix=$HOME/local
make
make install

RubyGemsを使ってUltravioletのインストール

一直線に答えを書いてしまってもいいのですが、問題解決法を書くのも忘れてしまう自分のためになると思うので、書きます。

gem install -r ultraviolet

とすればいいと思いきや、こんなエラーが出て止まります。

oregexp.c:2:23: oniguruma.h: No such file or directory

どうも標準ではない/home/hoge/local/以下にインストールしてあるので、oniguruma.hなどが見つからないようです。
さらに、エラーの最後を見ていると

Stop in /home/hoge/local/gems/oniguruma-1.1.0/ext.

なんて書いてあって、 どうも依存関係によりまず他のライブラリをインストールしているよう。
調べてみるとOniguruma for Rubyというものらしい。

そこでまず~/local/gems/oniguruma-1.1.0というフォルダを見てみると、Rakefileがある。
さらに中身を読んでみると、30 〜 36行目に以下のような記述があります。

if ENV['PLATFORM'] =~ /win32/
   p.lib_files = ["win/oregexp.so"]
   p.spec_extras[:require_paths] = ["win", "lib", "ext" ]
   p.spec_extras[:platform] = Gem::Platform::WIN32
else
   p.spec_extras[:extensions] = ["ext/extconf.rb"]
end

どうも、ext/extconf.rbがくさそうだという気がしたので、ext/extconf.rbを見てみます。

require 'mkmf'
have_library("onig")
$CFLAGS='-Wall'
create_makefile( "oregexp" )

たった4行!
ですがcreate_makefileでMakefileを作成しているというのは読み取れますので、何かありそうです。
そしてどうも、require ’mkmf’というのが鍵を握っている様な気がしました。
mkmf – Rubyリファレンスマニュアル

Ruby の拡張ライブラリのための Makefile を作成するライブラリです。

mkmf – Rubyリファレンスマニュアル

ということで、ビンゴでしょうか?
さらに読み進めると 

–with-opt-dir=directory
ヘッダファイル、ライブラリファイルを探索するディレクトリ directory/include、directory/lib をそれぞれ追加します。  

mkmf – Rubyリファレンスマニュアル

と書いてあるので、これを指定できればいいということになります。
ということで、

gem install -r oniguruma -- --with-opt-dir=$HOME/local
gem install -r ultraviolet

として、これでめでたくインストールが完了します。1

RubyGemsに慣れている人であれば当たり前のことなのかもしれませんが、自分は初めてだったので良い経験になりました。

Ultravioletの使い方

uvというコマンドがUltravioletのコマンドになりますが、uv –helpでヘルプを見てみると

uv -t amy -h ~/.bashrc > bashrc.html

なんてのがサンプルとして書いてあります。
amyというのはテーマの名前のようです。

テーマはUltraviolet Theme Galleryで見ることができます。
-sでSyntaxを指定することができますが、ある程度は自動的に認識されるのかもしれません。

uv -l syntax

で利用可能なSyntax一覧を表示することが可能です。 

自分は.zshrcをやってみました。
Syntaxが認識されないので、-s shell-unix-genericというのを指定しました。

uv -t blackboard -s shell-unix-generic -h ~/.zshrc > zshrc.html

生成されたzshrc.htmlはcssを参照しているところがあるのですが、そのcssは
/home/hoge/local/gems/ultraviolet-0.10.2/render/xhtml/files/cssにあります。

Rubyからもライブラリとして使うことができるようです。
Usageのところを見ると書いてあります。

  1. 試してはいないけれど、gem install -r ultraviolet — –with-opt-dir=$HOME/localでうまくいくのかなぁ? []

RSRubyでRubyのArrayをRのMatrixに変換する

題名の通りです。

以前ご紹介したように、RSRubyはRubyでRのスクリプトを実行するためのものです。
RのオブジェクトをRubyのオブジェクトに変換することは簡単です。
というのはeval_Rをした結果がArrayとして扱うのが適当であればArrayに、Hashとして扱うのが適当であれば、Hashに変換してくれます。

問題はRubyのArrayをRの中で使いたいときです。

たとえば
[1, 2, 3, 4, 5]
というArrayをRの行列にしたい場合。

ruby_data = [1, 2, 3, 4, 5]
r = RSRuby::instance
r.eval_R <<-RCOMMANDS
r_data <- matrix(c(#{ruby_data.join(",")}), 1, #{ruby_data.size})
<<-RCOMMANDS

などとしてうまくjoinを使ってやればできます。

Ruby de R

RでRubyではなく、RubyでR。
Rubyが本当に便利なので、できる限りRubyで作業をしたいなぁと思っています。

そんなことを助けてくれるのにrurubyというライブラリがあります。

MacBookでのインストールは

sudo gem install rsruby -- --with-R-dir=/Library/Frameworks/R.framework/Resources

としました。

このままでは $R_HOMEが見つからないと怒られるようです。
私はzshを使っているので、~/.zshrcに

export R_HOME=/Library/Frameworks/R.framework/Resources

を追加しておきました。

使い方はとりあえずeval_Rコマンドを使えばRのコマンドはなんでも実行できるので、特にRubyのデータ構造を使う必要がなければこれだけでもいけます。
ちなみにヒアドキュメントを使えばいいと

require 'rubygems'
require 'rsruby'

r = RSRuby.instance
r.eval_R <<-RCOMMAND
  # 何かしらのRのコマンド
RCOMMAND

これで使いなれたRubyの正規表現が使えるので自分としてはとても嬉しい。

鯖の方に移らなきゃ

Crêpeで使うデータ更新用のサーバーを作らないといけないのですが、今までやったことがないことなので(スタンドアロンアプリケーションしか作ったことがない)、何からやらなきゃいけないかがわかりません。

まずは、言語およびフレームワークの選択?
PHPを使うか、 Ruby on Railsを使うか、それとも単純にRubyだけで・・・。

とりあえず昔買ったRails本を引っ張り出して斜め読みをしています。
いつの間にか第2版が出ていたようで、というか第2版でもRails 2系とは違うらしいという驚くべき開発スピードなんですが(汗)。

RailsによるアジャイルWebアプリケーション開発 第2版
著者: Dave Thomas, David Heinemeier Hansson, Leon Breedt, Mike Clark, Andreas Schwarz, James Duncan Davidson, Justin Gehtland
出版社: オーム社
発売日: 2007年10月20日

ちなみに本国?の方では第3版の準備がされているようです。1

Agile Web Development with Rails, Third Edition

2ヶ月前はSQLもさっぱりだったし、Objective-Cもさっぱりだったし(最近ようやく参照カウンタに慣れてきた)、ある程度頑張ればそこからはある程度わかるようになるでしょうと思っているのですが・・・。

SQLがわかったあとなので、少しは頭に入ってきていますが、まだ全体的な繋がりがよく見えない。
ちょろちょろとテストアプリを作ってみて掴んでいくしかないな。
でも、ここでの開発の経験はあとに生かせるはず!!!

  1. 上記サイトでBeta版のPDFが出ているのだから、日本語版も2, 3ヶ月遅れでできないかしら?なんてわがまま放題なことを発言してみます。 []

SQLite 3では?インデックスの名前はユニークである必要がある

Crêpeのおかげで最近はSQLite 3にどっぷりで、SQLでデータベースを操作するのは非常に楽しいです。

SQLiteではというか、SQLでは高速に検索を実行するためにはインデックスの作成がかかせません。
インデックスを作成しているかしていないかで検索スピードが100倍以上変わることも珍しくありません。

そのためインデックスを作成する訳ですが、 次のような構文で作成することができます。

CREATE INDEX idx_hoge ON piyo(hoge);

piyoがテーブル名、hogeが列名になり、作成されるidx_hogeがhogeに対するインデックス名になります。

逆にインデックスを削除するときは

DROP INDEX idx_hoge;

です。ただ、いつも参考にしている本の一つ、「ぐんぐん実力がつく! 逆算式SQL教科書」ではMySQLを使っている関係上、構文は次のようになります。

DROP INDEX idx_hoge ON piyo(hoge);

ぐんぐん実力がつく! 逆算式SQL教科書
著者: 小野哲
ページ数: 288ページ
出版社: 技術評論社
発売日: 2007年12月26日

これらの構文を見て勝手に勘違いをしました(自分が勝手に勘違いをしただけで、この本自体は何度も参照するとても良い本です)。
インデックス名は同じテーブル内でユニークであればいいと。

でも実際にSQLite 3でスキーマを調べられる.schema命令を使ってみると、作成されていませんでした。
今まではRubyでCREATE INDEXをしていたので、そのときはなぜかエラーが出ていなかったので気づきませんでした(どうもいままではほぼ主キーを使っていたのでうまいこといっていたらしい)。

MySQLでは調べたりなくて、同じなのかどうかちょっとわからないのですが、これからインデックスを作成する際には

CREATE INDEX idx_piyo_hoge ON piyo(hoge);

のようにインデックス名にテーブル名も含めようと思います。

Rubyで自動的にリンクを拾ってくるツールを作ったのだが・・・ 2

先日の続き。
Rubyで自動的にリンクを拾ってきてくるツールを作ったのだが・・・

もう少し具体的に処理内容を書く。

  1. 2万件近くのhtmlファイルをHTTPで取得。
  2. 取得したhtmlを解析し、リンクを見つる。
  3. ある一定条件のリンクであれば、さらにそこから飛んで、とんだ先のhtmlファイルを取得。
  4. その中にある特定条件のリンクを抽出する。

というものである。

このリンクをデータベース化しておく(サーバー側)。
こうすることの利点は2点ある。

  • クライアントアプリが必要なデータにダイレクトにアクセスできるようにする。
  • 将来URLの変更がおこなわれても、クライアント側の修正は不要。

まずはどこに時間がかかったのかを調べようと思って、プロファイラを使うことに決めた。

Rubyのプロファイラには標準でついてくるprofileと、それよりも高速なruby-profがある模様。
profile – Rubyリファレンスマニュアル
ruby-prof

はじめは高速なということに引かれてruby-profをインストール

sudo gem install ruby-prof

使用方法は

ruby -runprof hoge.rb

だそうなので、実際にやってみたところ

ruby: no such file to load -- unprof (LoadError)

と怒られてしまい、うまくいかなかった。

仕方がないので、標準のprofileを使う。
使い方は

ruby -rprofile piyo.rb

今処理の全部をやってしまうと時間がかかりすぎるので100件に絞っておこなった。
プロファイルをやってみたところ(上位5つの処理を抜き出している)

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 31.08    81.24     81.24      197   412.39   760.41  Hpricot.scan
  7.87   101.82     20.58    18912     1.09    19.36  Hpricot.build_node
  5.75   116.86     15.04    32238     0.47     9.89  Array#each
  5.53   131.31     14.45    99767     0.14     0.19  Hash#[]
  5.26   145.07     13.76   232541     0.06     0.06  Array#[]

こんな結果になって、(右端の% time全体時間のパーセンテージということであるから)が全体の40%ほどがhpricotがhtmlを解析する処理に使われていることがわかる。
プロファイルなしで実際の処理時間を測定してみると、35秒ぐらいかかった 1

どうも正規表現の処理は遅くなさそうだということで、(hpricotを使う前はhtml全体をパースしてしまっていたので、それを止めて)anchorタグだけをパースさせることにする。
参考はたのしいRubyのHTMLパーサー

AnchorTagRegexp = /<a\s+[^>]*(?:href=(?:"([^"]*)"|'([^']*)'|([^'"\s]*)))[^>]*>/mi
response.body.scan(AnchorTagRegexp) { |tag|
  href  = $1 || $2 || $3
}
たのしいRuby 第2版
著者: 高橋征義、後藤裕蔵
ページ数: 489ページ
出版社: ソフトバンククリエイティブ
発売日: 2006年8月5日

結果は、、、同じぐらい。

やはり35秒ぐらいかかる。

ええい、こうなったらもうC++で書いてしまえということで書いてみた。
結果は、、、同じくらい、というかもっと遅い???(ぉぃ
単純にプログラムの書き方が悪いのか?

ちなみにC++ではlibxml2を使って書こうとしたところ、HTMLのパースでエラーがいっぱい出た。
どういうことかというと、たとえば<meta ・・・/>ではなくて、<meta・・・>となっているからタグの対応関係があっていないとか・・・。
厳密に見ないやり方はよくわからなかったので、仕方がないので上で使った正規表現を用いることにした。

使ったもの

  • 正規表現にはboost::regex。
  • HTTPでの通信部分はboost::asio。
  • EUC-JP → UTF-8への変換はiconv。

たぶん、何で書いても速度が変わらないということはネットワークがボトルネックなんだということで、とりあえず今は納得した。
HTTPの同時接続数を増やすというやり方はどうかと思うので、やらないけれども(やっても4接続ぐらいにしておく)。

もう一つやるべきこととすれば、今はデータベースを丸ごと更新しているので(やり方としてはどうかと思う)、更新されている場合にだけリンクを深追いするという方式にすればもう少し速くなるかもしれない。

ただし、このデータベースにURLを入れておくというやり方は止めて、必要なデータをその都度引っ張ってくる方が色々な意味で優しいのではないかと思えてきた。

  1. エントリを書きながら、プログラムを調節すればいいのですが、あとでエントリを書いているため正確な数字がわからない []

Rubyで自動的にリンクを拾ってきてくるツールを作ったのだが・・・

とある目的のため、Rubyで自動的にリンクをたどって、HTMLファイルを解析し、必要となるリンクを抜き出してきてくれるツールを作った。

基本的にはNet::HTTPを使ってresponseをgetし、response.bodyで中身のHTMLファイルが取得できるので、あとはHTMLパーサーにかけるだけである。

HTMLパーサーにはhpricotというものを使った。
Hpricot, a fast and delightful HTML parser

これで非常に簡単にリンクが取得できる。
たとえば(たぶん)こんな風に(いい加減、ブログにアップロードするときにコードに色をつけてくれるのが欲しいな)。

require "rubygems"
require "hpricot"
require "net/http"

url = ARGV[0]

Net::HTTP.start(url) { |http|
  response = http.get(hoge)
  
  doc = hpricot(response.body)
  (doc/:a).each { |link|
    p(link[:href])
  }
}

実際に動かしてみると、何かが遅くて1時間ぐらい経っても?(放置していたので正確な時間がわからない)終わっていない(挙げ句の果てには、#<eoferror : end of file reached>とはき出して死んでいたのだが(笑))。

プロファイルしてみないとわからないけれども、ネットワークが遅いのか、Rubyが遅いのか、HTMLのパースが遅いのかのどれかだと思う。
ただ、hpricotはCでバックエンドが書かれているようなので、ここが遅いというのは考えにくいのかも。

こうなったらC++で書くしかないのか?
でも文字列処理が面倒 + httpを扱うものを書いたことがないということで(boost::asioで解決できるかな)、できる限り使いたくはないのだけれど。
しかも元々のHTMLはEUC-JPだしなぁ・・・。

rubyで半角カタカナを全角にする

NKFを使えばできる。

UTF-8には対応していないのかなぁなどと勝手に思っていたら、そんなことはなかったです。

標準添付ライブラリ紹介 【第 3 回】 Kconv/NKF/Iconv

ここに細かいオプションが書いてあります。
UTF-8で入力する場合には ‘-W’
UTF-8で出力する場合には ‘-w’
半角カタカナを全角にするには ‘-X’(デフォルトらしいのでつけなくても変換されるようですが)
MIME encodeをしないので ‘-m0’

ということで、たとえばこんな感じになります。

#!/usr/bin/ruby -Ku
require 'nkf'
p NKF::nkf('-WwX -m0', 'テストだよ。')

日々雑感 2008/05/05

1. SQLiteで遊んでいる。

コマンドラインからでも気軽に試せるし、rubyで少しスクリプトを書いてもよい。
2万件近くレコードがあるのに、一瞬にして結果を表示してくれるのがおもしろくて仕方がない。

図書カードが確か2,500円分あったので、今日はSQLの本を一冊購入してこようと思う。

csvファイルから読み込むには、コマンドラインから

sqlite3 -separator , test.db ".import test.csv test"

なんて書いてもよいのだけれども、自分のつたない知識では主キーを設定できなかったので、rubyで一項目ずつ加えていくことにした。

ただし、そのまま一項目ずつ加えると劇的に遅いので、トランザクションを使う。

db.transaction
begin
  db.execute "insert test (hoge, piyo) values(?, ?)", hoge, piyo
  db.commit
rescue
  db.rollback
end

こんな風に使うらしい。

トランザクションを明示的にしない場合には、裏で一項目追加されるごとにトランザクションをおこない(この書き方があっているかどうかは知らない)、commitされるそうだ。
それで、遅くなる理由が納得できた。

事実、トランザクションを使うと何倍も早く追加ができた。

Ruby on RailsでLighttpdの起動にはまる

 とある事情により、Ruby on Railsで実際にちょこっとしたものを書こうかなぁということになったので、実際にインストール済みのRailsでプロジェクトを一つ作って

ruby script/server

でサーバーを起動させてみました。しかしながら、エラー発生。

 どうもエラーメッセージをよく見てみると

(mod_fastcgi.c.988) execve failed for:
/Volumes/DataHD/***/public/dispatch.fcgi
No such file or directory

と書かれているところで失敗しているようでした。

 今使っているMacはHDDのパーティションを二つに分断して、一つのパーティションはMacBook HD、もう一つはData HDにしています。どうもエラーを見てみると、DataとHDの間のスペースが削られてしまっていて、Rubyのソースコードを見たのですが、原因となるようなところは見つからず・・・。lighttpdのバグかなぁなどと思います(まあディレクトリ名にスペースなんて入れるなといわれるのでしょうが)。

 そこで、本当はイヤなのですが、仕方がないのでドライブ名をDataだけにすることに決めました。まず起動しているアプリを終了。ドライブの名前をDataに変更し、NetInfo マネージャを起動してhomeの欄を変更して、再起動。無事今度はうまく行くようになりました。

 意外とネットで検索してみてもそれらしいのは載っていなかったので、書いておきます。