/ 最近 .rdf 追記 設定 本棚

脳log[Ruby: 2008-01-01~]



2008年01月01日 (火) Application Dataなんてフォルダを掘って一人で中に収まってる Operaは恥を知れ (Vistaでの話)

[SHJS][Ruby][tDiary] SHJSの Rubyルールを修正。(sh_ruby.js, sh_ruby.min.js)

以下、変更点のリスト。(\bの使い方が適当なのでスペースの少ないソースで問題が出る可能性あり。\bの使いどころが全然わかってないせい)

    { // part of Kernel methods.
      'regex': /\b(?:defined\?|Array|Floar|Integer|String|abort|callcc|exec|exit!?|fork|proc|lambda|set_trace_func|spawn|syscall|system|trace_var|trap|untrace_var|warn)\b/g,
      'style': 'sh_preproc'
    },

なくてもいいかな、と思うけど defined?と Kernelモジュールのメソッドの一部を sh_preprocとして追加。Rubyで sh_preprocなのは requireだけなので sh_preprocの配色を流用した。選んだのは abort、callcc、exit、fork、systemなど比較的重要そうなもの。(loopなど一部の他のメソッドは sh_keywordとして既に分類されている)

    {
      'next': 4,
      'regex': /<(?=[\w\/])/g,
      'style': 'sh_string'
    },

正規表現を /</g から変更。<<メソッドやヒアドキュメント(<<HOGE)にマッチしないように。

    { // Symbol
      'regex': /:(?:(?:@@|@|\$)?\w+[\?!]?|\+=?|!=?|~|\*\*=?|-=?|\*=?|\/=?|%=?|<<=?|>>=?|&=?|\|=?|^=?|>=?|<=?|<=>|===?|=~|!~|&&=?|\|\|=?|\.\.|\.\.\.|=)(?=\s|$)/g,
      'style': 'sh_string'
    },

新ルール。シンボル(:hoge)を sh_stringとして色付け。

    { // %!string!
      'regex': /%[Qq]?([!-'*-\/:;=?^]).*?\1/g,
      'style': 'sh_string'
    },

新ルール。%!string!、%Q!string!、%q!string!を sh_stringとして色付け。残念ながら %Q[]のように括弧を使ったものは入れ子になった括弧を数えられないので非対応。対応した。詳しくは下の方。

    {
      'regex': /(?:\b(?:alias|begin|BEGIN|at_exit|break|case|do|else|elsif|end|END|ensure|for|if|in|include|loop|next|raise|redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield|and|not|or|def|class|module|catch|fail|load|throw)\b|&&|\|\|)/g,
      'style': 'sh_keyword'
    },

ここにはプログラムの流れや定義に関するキーワードや Kernelメソッドが集められているようなので、既に登録されている ENDと同じ働きの at_exitを追加し、definedを削除(上で sh_preprocとして defined?を登録済み)、false、nil、self、true、__FILE__、__LINE__を削除し、あとで定数として定義。&& と || を and、orに対応するものとして追加。

    { // global variables
      'regex': /\$(?:[_&~`'\+\?!@=\/\\,;\.<>\*\$:"]|-?[A-Za-z0-9_]+)/g,
      'style': 'sh_type'
    },

グローバル変数の定義を追加。sh_typeはインスタンス変数やクラス変数のクラス名として使用されているもの。

    { // Constants
      'regex': /\b[A-Z]\w+[!\?]?(?=\b|$)/g,
      'style': 'sh_function'
    },
    { // Constants
      'regex': /\b(?:false|nil(?!\?)|true|self|__FILE__|__LINE__)(?=\b|$)/g,
      'style': 'sh_function'
    },

定数のルールを追加。sh_functionは Rubyでは使われていないクラス。

    {
      'regex': /[a-z0-9_]+(?:\?|!)/g,
      'style': 'sh_normal'
    },

正規表現を /[A-Za-z0-9_]+(?:\?|!)/g から変更。定数は区別したいじゃない。

 余談

    {
      'exit': true,
      'regex': /$/g
    },

文字列リテラルの終了条件に上のは必要ない、むしろこれがあることで複数行にまたがったリテラルを正しく認識できない、のだけど強力すぎる正規表現は誤認識があったときにソースを最後まで一色に染めてしまう危険性があるのでそのままにしている。ヒアドキュメントに対応しないのも同じ理由。

 追記@2008-01-02:括弧を使ったリテラルにも対応した

    { // %r(regexp)
      'next': 6,
      'regex': /%r[\(<\[\{]/g,
      'style': 'sh_regexp'
    },
    { // %x(command), %w(array)
      'next': 7,
      'regex': /%[xWw][\(<\[\{]/g,
      'style': 'sh_normal'
    },
    { // %(string)
      'next': 8,
      'regex': /%[Qq]?[\(<\[\{]/g,
      'style': 'sh_string'
    },
  [
    {
      'exit': true,
      'regex': /$/g
    },
    {
      'next': 6,
      'regex': /[\(<\[\{]/g,
      'style': 'sh_regexp'
    },
    {
      'exit': true,
      'regex': /[)>\]}]/g
    }
  ],
  [
    {
      'exit': true,
      'regex': /$/g
    },
    {
      'next': 7,
      'regex': /[\(<\[\{]/g,
      'style': 'sh_normal'
    },
    {
      'exit': true,
      'regex': /[)>\]}]/g
    }
  ],
  [
    {
      'exit': true,
      'regex': /$/g
    },
    {
      'next': 8,
      'regex': /[\(<\[\{]/g,
      'style': 'sh_string'
    },
    {
      'exit': true,
      'regex': /[)>\]}]/g
    }
  ],

括弧の対応をチェックすることはするけどカッコの種類を区別しないので

%(foo{bar)baz}

こんなのも通る。でも現実的には区別する必要ないよね。HTML断片を組み立てるときに問題がありそう。そしてそういうときにこそダブルクォーテーションを使わずに %[]を使うんだよね。試してみる。

html << %[<option value="#{h hoge}"] << (selected? ? ' selected="selected">' : '>') << h(hoge) << "</option>\n";

やっぱりダメだ〜。

 追記@2008-01-02:括弧を使ったリテラルに正式に対応した

上で出した「こんなのも通る」と「やっぱりダメだ〜」の例が、言葉とは裏腹に「通ってない」と「ちゃんとできてる」状態になってると思う。だとしたら成功。

変更点は20080102p01で。

 追記@2008-01-05:#コメントと #{interpolation}の順番を入れ替え

# for variable interpolation, #{ is not a comment

というコメントを付けて #{}のハイライトルールを定義しているにも関わらず、それが #コメントルール よりも後ろにあるために機能していなかった。#コメントルールを後ろに持ってきて解決。

続きは20080105p01で。


2007年12月31日 (月)

[tDiary][Ruby] evalは最終手段*。module ::TDiaryを使うんだ。

 plugin/counter.rb

 plugin/disp_referer.rb

 plugin/makelirs.rb

 plugin/makerss.rb

 plugin/navi_user.rb

 plugin/pb-show.rb

 plugin/recent_list.rb

 plugin/tb-show.rb

 plugin/title_tag.rb

eval(<<__END_OF_TOPLEVEL__,TOPLEVEL_BINDING)
module TDiary
end
__END_OF_TOPLEVEL__

に類する evalは

module ::TDiary
end

でいいじゃない。

 tdiary/defaultio.rb

(行間に、構造に関係しないコードが省略されています)

module TDiary
	class DefaultIO < IOBase
	private
		def restore( fh, diaries )
						diary = eval( "#{style( style_name )}::new( headers['Date'], headers['Title'], body, Time::at( headers['Last-Modified'].to_i ) )" )

最後の行はこれ↓で。

						diary = style( style_name )::new( headers['Date'], headers['Title'], body, Time::at( headers['Last-Modified'].to_i ) )

 tdiary.rb

 plugin\pingback\pb.rb

anchor_str = @plugin.instance_eval( %Q[anchor "#{@diary.date.strftime('%Y%m%d')}"].untaint )

anchor_str = @plugin.anchor( @diary.date.strftime('%Y%m%d' ) ).untaint

で OK。


あえて寝た子を起こすまねをして新たなエラーを引き起こすこともないとは思うけど……。とはいえ、

@plugin_files.grep(/\/category.rb$/).empty?

のコピペの連鎖のようなものは断ち切りたい。emptyかどうかを知りたいのならマッチする全ての要素を集めてくる必要はなくて

not @plugin_files.any?{|pi| /\/category.rb\z/ =~ pi }
@plugin_files.find{|pi| /\/category.rb\z/ =~ pi }.nil?

のどちらかで十分です。速度的なもの(なんという婉曲さw)は計ってないけど、category.rbは cで始まるから (有効になっているのなら) @plugin_filesの最初の方にあって、すぐに結果がでるもののはず。

 計ってみたよ

irb(main):044:0> plugin_files = Dir.glob('./*.rb')
=> ["./a.rb", "./akismet.rb", "./amazon.rb", "./append-css.rb", "./bq.rb", "./calendar2.rb", "./calendar3.rb", "./category.rb", "./comment_mail-qmail.rb", "./comment_mail-sendmail.rb", "./comment_mail-smtp.rb", "./comment_rank.rb", "./counter.rb", "./daily_theme.rb", "./disp_referrer.rb", "./doctype-html401tr.rb", "./dropdown_calendar.rb", "./edit_today.rb", "./footnote.rb", "./gradation.rb", "./gradient.rb", "./hide-mail-field.rb", "./highlight.rb", "./html_anchor.rb", "./image.rb", "./kw.rb", "./list.rb", "./makelirs.rb", "./makerss.rb", "./my-ex.rb", "./my-sequel.rb", "./navi_user.rb", "./number_anchor.rb", "./pb-show.rb", "./ping.rb", "./pingback.rb", "./random_google.rb", "./recent_comment.rb", "./recent_comment3.rb", "./recent_list.rb", "./recent_namazu.rb", "./recent_rss.rb", "./recent_trackback3.rb", "./referer-antibot.rb", "./referer-utf8.rb", "./referer_scheme.rb", "./search_control.rb", "./search_form.rb", "./sn.rb", "./speed_comment.rb", "./squeeze.rb", "./src.rb", "./tb-send.rb", "./tb-show.rb", "./title_list.rb", "./title_tag.rb", "./tlink.rb", "./todo.rb", "./weather.rb", "./whatsnew.rb", "./xmlrpc.rb"]
irb(main):045:0> Benchmark.bmbm{|j|
irb(main):046:1* j.report('grep'){ 10000.times{ plugin_files.grep(/\/category.rb$/).empty? } }
irb(main):047:1> j.report('grep2'){ 10000.times{ plugin_files.grep(/\/category.rb\z/).empty? } }
irb(main):048:1> j.report('any?'){ 10000.times{ not plugin_files.any?{|pi| /\/category.rb\z/ =~ pi } } }
irb(main):049:1> j.report('find'){ 10000.times{ plugin_files.find{|pi| /\/category.rb\z/ =~ pi }.nil? } }
irb(main):050:1> }
Rehearsal -----------------------------------------
grep    0.359000   0.000000   0.359000 (  0.361000)
grep2   0.297000   0.000000   0.297000 (  0.275000)
any?    0.109000   0.000000   0.109000 (  0.104000)
find    0.094000   0.000000   0.094000 (  0.105000)
-------------------------------- total: 0.859000sec

            user     system      total        real
grep    0.297000   0.000000   0.297000 (  0.270000)
grep2   0.281000   0.000000   0.281000 (  0.275000)
any?    0.094000   0.000000   0.094000 (  0.100000)
find    0.140000   0.000000   0.140000 (  0.101000)

最新のプラグイン集のプラグインを全て有効にしたのと同じ状態では any? と findの勝ち。これでは全ての要素を調べる grepがあまりに不利。(ちなみに category.rbが見つからなくて grep同様に配列の最後まで調べた場合、any?と findは grepの二倍強の時間がかかっていた。yieldがあるからなあ)

plugin_filesの要素数を半分の 30にしても any?、findの勝ちだったので、有効なプラグイン数15(category.rbを含む)の状態でもう一度。

irb(main):058:0> plugin_files = plugin_files[0,15]
=> ["./a.rb", "./akismet.rb", "./amazon.rb", "./append-css.rb", "./bq.rb", "./calendar2.rb", "./calendar3.rb", "./category.rb", "./comment_mail-qmail.rb", "./comment_mail-sendmail.rb", "./comment_mail-smtp.rb", "./comment_rank.rb", "./counter.rb", "./daily_theme.rb", "./disp_referrer.rb"]
irb(main):059:0> Benchmark.bmbm{|j|
irb(main):060:1* j.report('grep'){ 10000.times{ plugin_files.grep(/\/category.rb$/).empty? } }
irb(main):061:1> j.report('grep2'){ 10000.times{ plugin_files.grep(/\/category.rb\z/).empty? } }
irb(main):062:1> j.report('any?'){ 10000.times{ not plugin_files.any?{|pi| /\/category.rb\z/ =~ pi } } }
irb(main):063:1> j.report('find'){ 10000.times{ plugin_files.find{|pi| /\/category.rb\z/ =~ pi }.nil? } }
irb(main):064:1> }
Rehearsal -----------------------------------------
grep    0.188000   0.000000   0.188000 (  0.175000)
grep2   0.109000   0.000000   0.109000 (  0.099000)
any?    0.110000   0.000000   0.110000 (  0.106000)
find    0.109000   0.000000   0.109000 (  0.107000)
-------------------------------- total: 0.516000sec

            user     system      total        real
grep    0.125000   0.000000   0.125000 (  0.096000)
grep2   0.094000   0.000000   0.094000 (  0.094000)
any?    0.110000   0.000000   0.110000 (  0.101000)
find    0.125000   0.000000   0.125000 (  0.105000)

category.rbを含めて 15のプラグインが有効の場合、僅差で any?、findの負け。category.rbを使ってない場合、any?、findはさらに不利になるわけだけど……。(みんな使ってるよね?) この日記では category.rbを含めて 23(+必ず有効な4つ)のプラグインが有効だから any?、findがもう少し有利になって、結論は「どっちでもいい」。けど、それなら findを使う。プラグインは増やすことはできても減らすと過去の日記でエラーが出たりするから。

 追記@2008-01-06:grepは名前勝ち?

なじみがあって処理内容が明確だから使いやすいのかも。同じことを find_allでやろうとするよりも(any?や findの結果から考えて)倍近く高速だろうことも想像ができるので、使いどころが正しければ優秀なメソッド。

 追記@2008-01-06:ブロック付き Enumerable#any?(all?)は Ruby 1.8 feature

2.2.0までは Ruby 1.6もサポートしていたので any?の使用は考えられないのだった。でも findは Ruby 1.6からあるようなのでここまで書いたことが全否定されたというわけでもない。よかった。

* ほんとうに?なんで?


2007年12月25日 (火) なんでもかんでも「相性」で片付けて原因究明を怠る(一部の)風潮が嫌い

[Ruby][tDiary] PStore, category.rb: 日記に変更があるたびにカテゴリキャッシュファイルが一斉に更新されるのをなんとかする。

日記を更新すると、TDiary::Config#data_path/category/ 以下の、カテゴリごとに作られるキャッシュファイルがずいぶんたくさん更新される。全部ではないが半分近い 21のファイルが更新されていた。日記の内容はというと一つのカテゴリしか使っていない。

どこのコードが無駄にファイルを更新しているのかと絞っていくと、category.rbの中の Category::Cache#replace_sectionsだとわかった。ではこの replace_sectionsが悪いのかというとそうではない。replace_sectionsの中の

PStore.new(cache_file(c)).transaction do |db|
end

に囲まれた部分をすべてコメントアウトしても 21のキャッシュファイルが一斉に更新されたのだから。一部の PStoreファイルは開いて閉じるだけで常に更新されるのだとしか考えられない。

PStoreは transactionの前後で Marshal::dump の戻り値のサイズと MD5が変化したかどうかを見て、変更があったかどうかを判断し、ファイルに書き込みをするかしないかを決めている。Marshal::load/dump が対称ではないのだろか。(そもそも Hashを Marshal::dumpした結果が一定だと仮定してよいのだろうか*)

原因が何であるにせよサーバーの pstore.rbを書き換えるわけにもいかないので、tDiaryの category.rbに対策を施した。

 PStore#transaction(true)を使う

読み出し専用であることが予めわかっている PStore#transactionはすべて trueを引数にして(readonly=trueの意)呼び出す。実はそういう transactionはすべて PStore#abortで終わっているのでこの対策は必要ないのだが、ファイルを排他ロックすることと MD5を計算する手間が省けるので一応。

 PStore#abortを使う

肝心の Category::Cache#replace_sectionsは transactionの開始前に変更があるのかどうかがわからないので PStore#transaction(true)は使えない。日付の削除が空振りに終わったときにだけ PStore#abortを呼ぶことにする。これで必要最低限のキャッシュファイルだけが更新されるようになった。

	#
	# cache each section of diary
	# used in update_proc
	#
	def replace_sections(diary)
		return if diary.nil? or !diary.categorizable?

		categorized = categorize_diary(diary)
		categories = restore_categories
		deleted = []
		ymd = diary.date.strftime('%Y%m%d')

		categories.each do |c|
			PStore.new(cache_file(c)).transaction do |db|
				db['category'] = {} unless db.root?('category')
				if categorized[c] and diary.visible?
					db['category'].update(categorized[c])
				else
					# diary is invisible or sections of this category is deleted
					db.abort unless db['category'].delete(ymd)
					deleted << c if db['category'].empty?
				end
			end
		end

		if !deleted.empty?
			deleted.each do |c|
				File.unlink(cache_file(c))
			end
			replace_categories(categories - deleted)
		end
	end

* 追記:最近の Ruby(1.8.7だか 1.9.0)は Hashの keyの順序を保存しているみたいだけど。


2007年12月20日 (木)

[Ruby] るびまゴルフ 【第 2 回】

 問題

以下のコードと同じ出力をより短いコードで得よ。(パーは 27バイト)

-2000.step(-10000,-10) do |v|
  puts v
end

 答案

n=1990;801.times{p -n+=10}

-1 (26バイト)だからバーディーかな。アルバトロス(-3)が上限とは限らないけど。数値リテラルが冗長な気がするけど他の表現が思いつかない。文字コード(?X)を使おうと思ったけどできなかった。

 やってみたこと

  • 省ける空白を省く
  • do〜end を {〜} に
  • puts を pに
  • 三カ所の -(マイナス)を一カ所に

 失敗したこと

  • ブロック変数名を $\(レコードセパレータ)にして、printを無引数で呼ぶ。(.step{|$\\|print}。$\に数字を代入できなくて失敗。なら文字列に対して uptoを呼べばいいんだけど文字列リテラルにするために引用符で +2バイト使っちゃ無駄が多いし、そもそもお題を満たせないし、泥縄)
  • (irb限定で) 変数_を使ったインチキ。

 できなかったこと (他人の答えを見て)

  • 三カ所の 0(十)を一カ所に (思いつかなかった)
  • Shift_JISの文字コードを利用 (できなかった)

2007年12月17日 (月)

[Ruby] Rubyの 定数の見える範囲 の理解はあきらめております

irb(main):115:0> require :benchmark.to_s
irb(main):119:0> Benchmark.bmbm{|j| j.report }
NameError: uninitialized constant Benchmark::Job::ArgmentError
        from C:/Program Files (x86)/ruby/lib/ruby/1.8/benchmark.rb:333:in `report'
        from (irb):119
        from C:/Program Files (x86)/ruby/lib/ruby/1.8/benchmark.rb:250:in `bmbm'
        from (irb):119
        from :0
irb(main):120:0>

 追記@2008-02-20:実は可視範囲の問題ではなくタイポだった

ArgmentError -> ArgumentError

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/44601


2007年01月29日 (月)

[Ruby] 文字列リテラル連結

irb(main):001:0> RUBY_RELEASE_DATE
=> "2006-12-25"
irb(main):002:0> "aaa"\
irb(main):003:0* "bbb"
=> "aaabbb"
irb(main):004:0> "aaa"\
irb(main):005:0* %[bbb]
NameError: undefined local variable or method `bbb' for main:Object
        from (irb):5

こういうもんなの? "bbb" と %!bbb! は同じものだと思っていたが。

 追記:2007-12-12

'%04d-%02d-%02d'%[2007,12,12]
=> "2007-12-12"

String#% が呼ばれてるんだよ。


2007年01月20日 (土)

[Ruby][SQLite] sqlite3-ruby-1.2.0リリース

二年近くの長い沈黙を破って 2007-01-13にリリースされていた。

[Ruby] mkmf.rb

空白を含むパスで失敗するのでこのように↓。

 def link_command(ldflags, opt="", libpath=$LIBPATH)
   Config::expand(TRY_LINK.dup,
                  CONFIG.merge('hdrdir' => $hdrdir.quote,
+                              'srcdir' => $srcdir.quote,
                               'src' => CONFTEST_C,
                               'INCFLAGS' => $INCFLAGS,

2007年01月13日 (土)

[Ruby][Hiki] Hiki-0.8.6のインストロールインストゥール、install。

まずローカルで試してからアップロードしたのだがローカルの Apacheが Ruby-1.9.0を呼ぶので、FrontPageの表示からログインまでを可能にするために加えた変更点のリストをメモしておく。

 hiki/config.rb: Hiki::Config#initialize

-      instance_variables.each do |v|
+      instance_variables.each do |v| v = v.to_s;

vにシンボルが渡されて、次の行で v.sub!したときにエラーになっていた。

 hiki/command.rb: Hiki::Command#cmd_login

          if page && !page.empty?
-           redirect(@cgi, @conf.base_url + @plugin.hiki_url( page ), session_cookie( session.session_id ))
+           redirect(@cgi, @conf.base_url + @plugin.hiki_url( page ), [session_cookie( session.session_id )])
          else
-           redirect(@cgi, @conf.index_url, session_cookie( session.session_id ))
+           redirect(@cgi, @conf.index_url, [session_cookie( session.session_id )])

Cookieが session_id=SESSIONIDの形でなく SESSIONIDと key名なしの状態でブラウザにセットされるからログインに失敗していた。

Hiki::Command#cmd_logoutでは同じ引数を [session_cookie(session_id, -1)] としていたので同じように配列にした。

 hiki/storage.rb: Hiki::HikiDBBase#md5hex

-      Digest::MD5::new( s || '' ).hexdigest
+      Digest::MD5::hexdigest( s || '' )

リファレンスマニュアルには Digest::MD5.new([str]) とあるが引数の数が 0でないと叱られる。

 hiki/db/tmarshal.rb: TMarshal::dump_text

    when Array
-     "[\n"+obj.collect{|x| dump_text(x)+",\n"}.to_s+"]"
+     "[\n"+obj.collect{|x| dump_text(x)}.join(",\n")+"\n]"
    when Hash
-     "{\n"+obj.sort_by{|e| e[0].inspect}.collect{|k,v| "#{dump_text(k)} => #{dump_text(v)},\n"}.to_s+"}"
+     "{\n"+obj.sort_by{|e| e[0].inspect}.collect{|k,v| "#{dump_text(k)} => #{dump_text(v)}"}.join(",\n")+"\n}"

dumpに失敗していた。

原因となった Array#to_sのバージョンによる出力の違い↓。

Ruby-1.8.5p12> [1,2,3].to_s #=> "123"
Ruby-1.9.0 20061205> [1,2,3].to_s #=> "[1, 2, 3]"

リファレンスマニュアルには

to_s
   self.join($,) と同じです。

と書いてあるから to_sで(ある種の) joinを代用していても仕方ない。

と思ったがどちらにしろ Array#to_sの出力は $, に依存するので、後で(loadするときに) evalすることを考えれば今回の to_sの使用は不適切か。

[Ruby][Hiki] 差分の表示

上のエントリで FrontPageの表示とログインまでやったが、差分の表示もおかしかったので Array#to_s らしき部分を join('') に書き換えまくったら直った模様。

'string'.join は存在しなかったので、書き換えてエラーにならないということは考えたとおり Array#to_sだったか、そのコードが実行されてなくて発覚してないが実は間違いだった(実行されたらNoMethodErrorになる)かのどちらか ^_^; こういう実行してみないとわからないところは javascriptと同じでレアなコードパスのデバッグを難しくするね。

以下、リスト。

 style/default/hikidoc.rb: HikiDoc#escape_meta_char

+if(defined? ' '.ord) # Ruby-1.9
   def escape_meta_char( text )
     text.gsub( META_CHAR_RE ) do |s|
+      '&#x%x;' % s[1].ord
+    end
+  end
+else
+  def escape_meta_char( text )
+    text.gsub( META_CHAR_RE ) do |s|
       '&#x%x;' % s[1]
     end
   end
+end

いきなり Array#to_sと関係ないが String#[index] が Integerに代えて一文字の Stringを返すようになった対策。

 hiki/util.rb: Hiki::Util#word_diff

      if digest
-        return View.new( diff, src.encoding, src.eol ).to_html_digest(overriding_tags, false).to_s.gsub( %r|<br />|, '' ).gsub( %r|\n</ins>|, "</ins>\n" )
+        return View.new( diff, src.encoding, src.eol ).to_html_digest(overriding_tags, false).join(\).gsub( %r|<br />|,  ).gsub( %r|\n</ins>|, "</ins>\n" )
      else
-        return View.new( diff, src.encoding, src.eol ).to_html(overriding_tags, false).to_s.gsub( %r|<br />|, '' ).gsub( %r|\n</ins>|, "</ins>\n" )
+        return View.new( diff, src.encoding, src.eol ).to_html(overriding_tags, false).join(\).gsub( %r|<br />|,  ).gsub( %r|\n</ins>|, "</ins>\n" )
      end

 hiki/util.rb: Hiki::Util#word_diff_text

      if digest
-        return View.new( diff, src.encoding, src.eol ).to_wdiff_digest({}, false).join.gsub( %r|\n\+\}|, "+}\n" )
+        return View.new( diff, src.encoding, src.eol ).to_wdiff_digest({}, false).join(nil).gsub( %r|\n\+\}|, "+}\n" )
      else
-        return View.new( diff, src.encoding, src.eol ).to_wdiff({}, false).join.gsub( %r|\n\+\}|, "+}\n" )
+        return View.new( diff, src.encoding, src.eol ).to_wdiff({}, false).join(nil).gsub( %r|\n\+\}|, "+}\n" )
      end

join('')か join(nil)か統一しろよ、とセルフツッコミ。

 hiki/util.rb: Hiki::Util#compare_by_line_word

-          before_change = Document.new(line[1].to_s,
+          before_change = Document.new(line[1].join(''),
                                       doc1.encoding, doc1.eol)
-          after_change  = Document.new(line[2].to_s,
+          after_change  = Document.new(line[2].join(''),
                                       doc2.encoding, doc2.eol)

 hiki/docdiff/view.rb: View#apply_style

       if block_given?
-        source = yield block[1].to_s
-        target = yield block[2].to_s
+        source = yield block[1].to_a.join ''
+        target = yield block[2].to_a.join ''
       else
-        source = block[1].to_s
-        target = block[2].to_s
+        source = block[1].to_a.join ''
+        target = block[2].to_a.join ''
       end

block[i]は nilの可能性があるので to_a.join

 hiki/docdiff/view.rb: View#apply_style_digest

       if block_given?
-        source = yield entry[1].to_s
-        target = yield entry[2].to_s
+        source = yield entry[1].to_a.join ''
+        target = yield entry[2].to_a.join ''
       else
-        source = entry[1].to_s
-        target = entry[2].to_s
+        source = entry[1].to_a.join ''
+        target = entry[2].to_a.join ''
         end
       if  i == 0
         context_pre  = ""  # no pre context for the first entry
       else
-        context_pre  = @difference[i-1][1].to_s.scan(context_pre_pat).to_s
+        context_pre  = @difference[i-1][1].to_a.join('').scan(context_pre_pat).to_s
       end
       if (i + 1) == @difference.size
         context_post = ""  # no post context for the last entry
       else
-        context_post = @difference[i+1][1].to_s.scan(context_post_pat).to_s
+        context_post = @difference[i+1][1].to_a.join('').scan(context_post_pat).to_s
       end

 hiki/docdiff/view.rb: View#source_lines

   def source_lines()
     if @source_lines == nil
-      @source_lines = @difference.collect{|entry| entry[1]}.join.scan_lines(@eol)
+      @source_lines = @difference.collect{|entry| entry[1]}.join(nil).scan_lines(@eol)

 hiki/docdiff/view.rb: View#target_lines

   def target_lines()
     if @target_lines == nil
-      @target_lines = @difference.collect{|entry| entry[2]}.join.scan_lines(@eol)
+      @target_lines = @difference.collect{|entry| entry[2]}.join(nil).scan_lines(@eol)

[Hiki][Ruby]プラグイン記法

 {{hoge a b 5}}      #=> hoge('a', 'b', 5)
 {{hoge 'a' b(5)}}   #=> hoge('a', 'b', 5)
 {{hoge ,a(, b)(5)}} #=> hoge('a', 'b', 5)
 {{hoge, a, b, 5}}   #=> PluginException('not plugin method: hoge,')
 {{hoge; hage}}      #=> PluginException

文法の緩さとか、一つのメソッドしか呼べないとか、嫌すぎる。

そりゃあ Rubyで

require digest/md5

という風にライブラリ名をクォーテーションで括らずに書けたら楽だなとかは考えるし、Symbolが Stringのサブクラスになったときは

require :sqlite3

が通るのを一番に確認したけど、

defined? printf
alias printg printf

を見て、

  • printfって何?クラスは?
  • printgと printfの間にカンマがないけどどういう文法? alias専用ルール?

というのと同種の嫌悪を感じる。


2006年12月30日 (土)

[Ruby][iPod] ipodpl.rb

前々回(9月12日)前回(12月12日)

結局

> ruby ipodpl.rb I:\ artist:"小松 未歩" rating:5 rating:4 or and --album-shuffle > temp.m3u

という形になった。

最近は iPodから曲が溢れてきていて、HDDが安いこともあって iPodに入ってる曲や溢れた曲、全て PCに入っている。

iPodが唯一のミュージックライブラリだった 9月とは状況が変わっているので iPod内の曲を PCから検索できてもあまり嬉しくない。iPodより多くの曲が PCに入っているから。

一応、産物

予め iTunesDBから使えそうな情報を拾って SQLite3形式のデータベースに登録しておいて、検索は SQL任せ、という仕様です。

 2007-02-11

Songbirdのデータベースは最初から SQLiteだ。良き哉良き哉。


2006年12月12日 (火)

[Ruby][SWIG][iPod] Rubyから iPodのデータベースを読む

  1. libipod-0.1を bcc32でコンパイル
  2. ipod/ipod.hをベースに swigで Ruby用のラッパー作成
  3. 手持ちの iPod(第3世代*/ソフトウェアバージョン2.3)のデータベースは読めた

* 再生時間 8時間は短いけど、タッチボタンによる軽快な操作は捨てがたい。


2006年10月30日 (月)

[Ruby][SQLite] sqlite3-ruby-1.1.0の DLドライバを Ruby-1.9.0(20061029版)添付の dl2に*不完全*対応。

29日に Ruby1.9をダウンロードした。添付ライブラリのRipperが、m4sugar.m4が見つからない、というエラーでコンパイルできない以外は問題なくインストール完了。RUBY_PLATFORMは i386-bccwin32。

Ruby1.8.5で動いている http://vvvvvv.sakura.ne.jp/ds14050/buch/ が Ruby1.9でも動くのか試してみると、sqlite3-rubyの Nativeドライバ、DLドライバが両方とも動かない。Nativeは当然として、DLが動かないのは Ruby1.9では ruby-dl2が dlとして添付されているから。

出てくるエラーを順番に潰す過程でやったことは定数名の置き換えが殆ど。そんなことしかできません。コールバック関数を SQLiteに渡す authorizerや create_function関連は自分で使っていないので何もしていない。DL.callbackが存在しない為にエラーが出るのは間違いない。dl2では bindを使うのだろうか?

以下は変更点のリスト

* sqlite3/driver/dl/api.rb:38
   -extend ::DL::Importable
   +extend ::DL::Importer

単なる名称変更。

* sqlite3/driver/dl/api.rb:92
   -extern "ibool sqlite3_complete(const char*)"
   +extern "int sqlite3_complete(const char*)"
* sqlite3/driver/dl/api.rb:93
   -extern "ibool sqlite3_complete16(const void*)"
   +extern "int sqlite3_complete16(const void*)"
* sqlite3/driver/dl/driver.rb:96
   -API.send( utf16 ? :sqlite3_complete16 : :sqlite3_complete, sql+"\0" )
   +API.send( utf16 ? :sqlite3_complete16 : :sqlite3_complete, sql+"\0" ) != 0

iboolという返り値(戻り値?)の型が dl2では定義されていない(定義しようがない?)ので、intを bool値として受け取るのは諦めて、返り値を利用するドライバの方で非0かどうか調べる。

* sqlite3/driver/dl/driver.rb:40
   -DL.sizeof("L")
   +DL::SIZEOF_LONG

sizeofというメソッドは dl2の DL::Importerモジュールにもあるが使い方がわからないし、定数の方が良い。

* sqlite3/driver/dl/driver.rb:*
   -DL::PtrData
   +DL::CPtr
* sqlite3/driver/dl/driver.rb:242,247,252
   -result ? result.to_s : nil
   +result.null? ? nil : result.to_s

DL::CPtrが DL::PtrDataと完全に互換な置き換えなのかわからないが当面のエラーは消えた。

DLL関数の返値がポインタの場合は常に CPtrが返ってくるらしく、CPtrの指すアドレスが NULLの場合でも Ruby的には nilではないので「result ? result.to_s : nil」が常に result.to_sになり、ぬるぽエラーになることがある。PtrDataとは振る舞いが違う?

* lib/ruby/1.9/dl/value.rb:72
   -return [arg].pack("p").unpack("l!")[0]
   +return [arg.dup].pack("p").unpack("l!")[0]
* lib/ruby/1.9/dl/value.rb:74
   -return [arg].pack("p").unpack("q")[0]
   +return [arg.dup].pack("p").unpack("q")[0]

frozenオブジェクトを変更しようとした、ってエラーがでるので間に合わせで Rubyの添付ライブラリの方を修整。どこから frozenオブジェクトが渡されたのやら。

 追記(2006-11-01):Nativeドライバ

swigが sqlite3_api.iを基に出力する sqlite3_api_wrap.cを以下のように置換したら自分が使用している範囲では動いている。

-RSTRING()->ptr
+RSTRING_PTR()
-RSTRING()->len
+RSTRING_LEN()

2006年08月22日 (火)

[Ruby] 1.8系でもはまる、String#split

irb(main):001:0> RUBY_VERSION
=> "1.8.4"
irb(main):002:0> '//a//b//'.split('/')
=> [, , "a", "", "b"]
irb(main):003:0> '//a//b//'.split('/', 99)
=> [, , "a", , "b", , ""]

末尾のスラッシュが無かったことに……。< Perl由来のようです

因みに 1.6系のはまりどころは splitの第一引数に 2文字以上の文字列を与えた場合、勝手に正規表現にコンパイルされてしまうところ。


2006年06月26日 (月)

[Ruby] REXML::Element#xpathがおかしい

 1. 適当なXMLファイルの各要素ごとに xpathを列挙して現象を確認

/ItemLookupResponse
/ItemLookupResponse/OperationRequest
/ItemLookupResponse/OperationRequest/HTTPHeaders
/ItemLookupResponse/OperationRequest/RequestId
/ItemLookupResponse/OperationRequest/Arguments
/ItemLookupResponse/OperationRequest/RequestProcessingTime
/ItemLookupResponse/OperationRequest/HTTPHeaders/Header
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument

pathが重複している。

 2. xpathの定義(rexml/element.rb)

    def xpath
      path_elements = []
      cur = self
      path_elements << __to_xpath_helper( self )
      while cur.parent
        cur = cur.parent
        path_elements << __to_xpath_helper( cur )
      end
      return path_elements.reverse.join( "/" )
    end

pathの各要素は __to_xpath_helperで取ってきている。

 3. __to_xpath_helperの定義(rexml/element.rb)

    def __to_xpath_helper node
      rv = node.expanded_name
      if node.parent
        results = node.parent.find_all {|n|
          n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name
        }
        if results.length > 1
          idx = results.index( node )
          rv << "[#{idx+1}]"
        end
      end
      rv
    end

node.expanded_nameを破壊的に変更している。("Argument" -> "Argument[1]")

 4. 修正

D:\ruby\lib\ruby\1.8\rexml>diff -u element.rb~ element.rb
--- element.rb~      2005-08-12 21:08:47.000000000 +0900
+++ element.rb       2006-06-27 00:36:58.546875000 +0900
@@ -720,7 +720,8 @@
         }
         if results.length > 1
           idx = results.index( node )
-          rv << "[#{idx+1}]"
+          rv += "[#{idx+1}]"
         end
       end
       rv

 5. もう一度列挙して結果を確認

/ItemLookupResponse
/ItemLookupResponse/OperationRequest
/ItemLookupResponse/OperationRequest/HTTPHeaders
/ItemLookupResponse/OperationRequest/RequestId
/ItemLookupResponse/OperationRequest/Arguments
/ItemLookupResponse/OperationRequest/RequestProcessingTime
/ItemLookupResponse/OperationRequest/HTTPHeaders/Header
/ItemLookupResponse/OperationRequest/Arguments/Argument[1]
/ItemLookupResponse/OperationRequest/Arguments/Argument[2]
/ItemLookupResponse/OperationRequest/Arguments/Argument[3]
/ItemLookupResponse/OperationRequest/Arguments/Argument[4]
/ItemLookupResponse/OperationRequest/Arguments/Argument[5]
/ItemLookupResponse/OperationRequest/Arguments/Argument[6]
/ItemLookupResponse/OperationRequest/Arguments/Argument[7]

 6. REXML 3.1.4

をを、直っている。というわけで上記は ruby-1.8.4に付随する REXML 3.1.3限定の話でした。

    def __to_xpath_helper node
      rv = node.expanded_name.clone
      if node.parent
        results = node.parent.find_all {|n|
          n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name
        }
        if results.length > 1
          idx = results.index( node )
          rv << "[#{idx+1}]"
        end
      end
      rv
    end

3.1.3 からの ChangeLog。(そのうち 3.1.4からの ChangeLogになりそう)

 7. レンタルサーバーの REXMLを更新することはできないので

間に合わせにこんなのを紛れ込ませる。

if(::REXML::Version < '3.1.4')
module ::REXML
  class Element
    def __to_xpath_helper node
      rv = node.expanded_name.clone
      if node.parent
        results = node.parent.find_all {|n|
          n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name
        }
        if results.length > 1
          idx = results.index( node )
          rv << "[#{idx+1}]"
        end
      end
      rv
    end
  end
end
end