20090113p01や20090114p01で発生したエラーを起こす最小のスクリプトとそれを回避する方法。
>type a.rb puts "a.rb required." >ruby19 -v ruby 1.9.1 (2008-12-30 patchlevel-0 revision 21203) [i386-mswin32_90] >ruby19 -e "$SAFE=1; require 'a'" -e:1:in `require': Insecure operation - require (SecurityError) from -e:1:in `<main>' >ruby19 -e "$SAFE=1; require 'a.rb'" a.rb required.
二つの違いは requireするライブラリの拡張子(.rb)を明示しているかどうか。拡張子なしの場合に発生する SecurityErrorは間違いだと思う。そうでないと $SAFE = 1がまるで使い物にならない。添付ライブラリだってまともに動かなくなるんだから。
ところで、Ruby 1.9 - 1.9.1 RC2 issues - Ruby Issue Tracking Systemにはチケットを作成するためのフォームがない。ruby-dev MLはアーカイブをときどき閲覧しているが購読はしていない。是非ともこの SecurityErrorは消して欲しいのだが、報告を受け付ける間口が狭い。直通ルートがない。どうすべ。
どうすべ、と言ってる間に原因究明。
load.c:500: type = rb_find_file_ext(&tmp, loadable_ext); load.c:501: tmp = rb_file_expand_path(tmp, Qnil);
501行目が不要に思える。そしてこれが SecurityErrorの原因。rb_find_file_extは内部で rb_file_expand_pathや file_expand_pathを呼び、その結果を tmpにコピーしてくれている。二度目を呼ぶ必要はないのでは? rb_file_expand_pathは適宜汚染されたStringオブジェクトを返し、また $SAFE>0のとき、汚染された引数を SecurityErrorで拒絶するので、複数回の (rb_)file_expand_path呼び出しは容易に SecurityErrorを引き起こす。これは Ruby1.9.1の、Ruby1.8.7とは異なっている動作。
>irb irb(main):001:0> File.expand_path("a") => "Y:/a" irb(main):002:0> File.expand_path("a").tainted? => true irb(main):003:0> File.expand_path(File.expand_path("a")) => "Y:/a" irb(main):004:0> $SAFE=1 => 1 irb(main):005:0> File.expand_path(File.expand_path("a")) => "Y:/a" # $SAFE>0で、taintedな文字列でも展開する。(Ruby1.8.7) irb(main):006:0> exit >irb19 irb(main):001:0> File.expand_path("a") => "Y:/a" irb(main):002:0> File.expand_path("a").tainted? => true irb(main):003:0> File.expand_path(File.expand_path("a")) => "Y:/a" irb(main):004:0> $SAFE=1 => 1 irb(main):005:0> File.expand_path(File.expand_path("a")) # $SAFE>0で、taintedな文字列を引数にすると SecurityError (Ruby1.9.1RC1) SecurityError: Insecure operation - expand_path from (irb):5:in `expand_path' from (irb):5 from C:/Program Files (x86)/ruby/bin/irb19.bat:20:in `<main>' irb(main):006:0>
問題設定が間違っていたのか? load.cの一行をコメントアウトしたことで、たしかに一つの SecurityErrorは消えたが tDiaryは動かない。20090114p01のエラーがまだ出る。
ただ、20090114のタイトルにちらっと書いた、open-uriの SecurityErrorはでなくなってる。
>irb19 (野良パッチ済み) irb(main):001:0> $SAFE=1 => 1 irb(main):002:0> require 'open-uri' => true irb(main):003:0> open 'http://www.example.com' => #<StringIO:0x2b8e924> irb(main):004:0>
比較として ASRでエラーが出るのを確認する。ただ、ASRでも二回目以降の openはエラーにならない。謎の挙動。この SecurityErrorも本来発生すべきものではないのだろう。
>"C:\Program Files (x86)\ActiveScriptRuby-1.9.1\bin\irb.bat" irb(main):001:0> $SAFE=1 => 1 irb(main):002:0> require 'open-uri' => true irb(main):003:0> open 'http://www.example.com' SecurityError: Insecure operation - write from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:375:in `write' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:375:in `<<' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:375:in `<<' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:322:in `block (3 levels) in open_http' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/protocol.rb:373:in `call_block' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/protocol.rb:364:in `<<' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/protocol.rb:88:in `read' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:2333:in `read_body_0' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:2288:in `read_body' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:321:in `block (2 levels) in open_http' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:1120:in `block in transport_request' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:2251:in `reading_body' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:1119:in `transport_request' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:1103:in `request' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:312:in `block in open_http' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/net/http.rb:564:in `start' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:306:in `open_http' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:767:in `buffer_open' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:203:in `block in open_loop' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:201:in `catch' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:201:in `open_loop' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:146:in `open_uri' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:669:in `open' from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/open-uri.rb:33:in `open' from (irb):3 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:21:in `<main>' irb(main):004:0> open 'http://www.example.com' => #<StringIO:0x2b611a4> irb(main):005:0>
引き続き rexml/source.rb:16の requireが SecurityErrorになる原因を探る。(tDiaryを起動しないと再現させられないのが辛い)
方針は昨日書いたとおり、プラグインが自由に日記データを取得できる手段を提供した。
日記を一日書いたとたんにエラーということはなくなったみたい。
$SAFE=1で requireが失敗する(ファイル名の untaintもしているのに)のがそもそもおかしい。open-uriや rexmlで同様に requireで SecurityErrorエラーが生じていることからも、疑惑の目がウチの Rubyに向いてきた。「1.9.1RC1だから」ではなく「ウチでコンパイルしたから」、あるいは(開発者に)利用者が少なそうな 「Windows(それも Vista)だから」なのかもしれない。
ASRをインストールしてみたけどダメだった。同じ。tDiaryをセキュアモードで動かしているわけではないので Rubyのセーフレベルは最高でも 1。taintedな文字列を使った requireが失敗するならわかる。でも rexml/source.rbの 16行目は「require 'stringio'」だ。べったべたのリテラルだ。
500 Internal Server Error Insecure operation - require (SecurityError) C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/source.rb:16:in `require' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/source.rb:16:in `create_from' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/parsers/baseparser.rb:146:in `stream=' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/parsers/baseparser.rb:123:in `initialize' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/parsers/treeparser.rb:9:in `new' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/parsers/treeparser.rb:9:in `initialize' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/document.rb:228:in `new' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/document.rb:228:in `build' C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/rexml/document.rb:43:in `initialize' (plugin/amazon.rb):231:in `new' (plugin/amazon.rb):231:in `amazon_get' (plugin/amazon.rb):322:in `isbn_image' (TDiary::Plugin#eval_src):32:in `block in eval_src' Y:/.../server_root/www/ds14050/tdiary_on_ruby191/tdiary.rb:787:in `eval' Y:/.../server_root/www/ds14050/tdiary_on_ruby191/tdiary.rb:787:in `block in eval_src' Y:/.../server_root/www/ds14050/tdiary_on_ruby191/tdiary.rb:112:in `block in safe'
>clipboard
'clipboard' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。 >clip
情報: "CLIP /?" と入力すると使用法が表示されます。 >clip /?
コマンド ライン ツールの出力を Windows クリップボードにリダイレクトします。(やっぱりあるよねー。手コピしなくてすんでよかった。なお XPには……)最終更新: 2015-07-09T23:54+0900
tdiary/trunk (r3394) を ruby 1.9.1 (2008-12-30 patchlevel-0 revision 21203) [i386-mswin32_90] で動かしてみた。
dot.htaccessと tdiary.conf.beginnerを編集&リネームして、トップページの表示と一通りの設定変更を済ませて、記念すべき最初の書き込み。……トップページすら表示されなくなりました。
Insecure operation - require (SecurityError) Y:/.../tdiary_on_ruby191/tdiary.rb:434:in `require' Y:/.../tdiary_on_ruby191/tdiary.rb:434:in `block in load_styles' Y:/.../tdiary_on_ruby191/tdiary.rb:433:in `glob' Y:/.../tdiary_on_ruby191/tdiary.rb:433:in `load_styles' Y:/.../tdiary_on_ruby191/tdiary/defaultio.rb:142:in `initialize' Y:/.../tdiary_on_ruby191/tdiary.rb:1069:in `new' Y:/.../tdiary_on_ruby191/tdiary.rb:1069:in `initialize' Y:/.../tdiary_on_ruby191/tdiary.rb:1660:in `initialize' Y:/.../tdiary_on_ruby191/tdiary.rb:1858:in `initialize' (plugin/recent_list.rb):39:in `new' (plugin/recent_list.rb):39:in `block (3 levels) in recent_list' (plugin/recent_list.rb):37:in `reverse_each' (plugin/recent_list.rb):37:in `block (2 levels) in recent_list' (plugin/recent_list.rb):36:in `reverse_each' (plugin/recent_list.rb):36:in `block in recent_list' (plugin/recent_list.rb):35:in `catch' (plugin/recent_list.rb):35:in `recent_list' (TDiary::Plugin#eval_src):67:in `block in eval_src' Y:/.../tdiary_on_ruby191/tdiary.rb:787:in `eval' Y:/.../tdiary_on_ruby191/tdiary.rb:787:in `block in eval_src' Y:/.../tdiary_on_ruby191/tdiary.rb:112:in `block in safe'
プラグイン:recent_listが原因。(外したら解決した)
$SAFE==1の状況で TDiaryMonth.new()するのがダメっぽい。
recent_list()を呼ばれたときに、そのたびに、TDiaryMonth.new()するんでなくて、読み込まれたときに必要なデータを準備しておけばいいんじゃないか、とか思ったけど、evalで TDiaryMonthクラスにアクセサを追加したりしているあたり*、反則。泥縄の対応では気が済まない。プラグインが日記データを要求できるようなインターフェイスが求められている(現在は TDiaryXXXX#initializeで読み込まれたもののみ、Plugin@diariesからアクセスできる)。然るべき手段を用意したのち、recent_list.rbはそれを利用するべき。
* 歴史的経緯>http://kitaj.no-ip.com/tdiary/20021106.html#p03
映画でもハンバート ハンバートを演じたジェレミー アイアンズが朗読します。驚きの CD10枚組、その収録時間や 11時間半。コストパフォーマンス高すぎです。
本当は子供向けの Audiobookを探していたのです。不思議の国のアリスとか赤毛のアンとか秘密の花園を狙っています。アリスは言い回しが難しそうなのでまずはこの Anne of Green Gables。3枚組 4時間。朗読ではなく、BGMあり、キャストありのドラマCD風。きかん気が強そうで、口の減らなそうなアンの声が素敵。
SearchIndexを Books決めうちではなく ForeignBooksとの二択にする必要があるのだけど、978-4-*********が Foreignか否かは、どの国の Amazonを利用するつもりかで変わってくる。とりあえず Amazon.co.jpの利用を前提とした変更を amazon.rbに施して、書影の表示にこぎ着けた。(さらに、ISBN-13の頭が 978だけしかない期間限定の対応だけど)
url << "&SearchIndex=#{'Foreign' if /\A(978)?4/ !~ asin}Books" if id_type == 'ISBN'
こう書くと
""[[http://vvvvvv.sakura.ne.jp]] ""どこからの引用だかわかるでしょうか? "" ""すくなくとも Firefox3、Safari3.1、Opera9.50ではわかるはずですが、IE7では無理です。
こうなる。
どこからの引用だかわかるでしょうか?
すくなくとも Firefox3、Safari3.1、Opera9.50ではわかるはずですが、IE7では無理です。
HTMLはこんな感じになっている。
<blockquote cite="http://vvvvvv.sakura.ne.jp">...</blockquote>
スタイルシートはこう。残念な子 IE7は contentをサポートしていないのが敗因。
.section blockquote[cite]:after, .section blockquote[title]:after { content: "引用元: "attr(title)" "attr(cite); /* ハイパーリンクにしたい。マークアップもしたい。 */ display: block; text-align: right; font-style: oblique; background-color: #F3F9FF; }
引用の最初の行が二重ブラケットリンクだけだった場合に限り、その中身を <blockquote>の cite/title属性として扱う。こういうパターンがあり得る。
URLANDTITLETITLEONLYhttp://URLONLY
気付いたのは日記を書くときに、カテゴリ名入力支援機能(クリックすると本文にカテゴリが挿入される*)のカテゴリリストに目当てのカテゴリがなかったから。
脱線。何かのソースを見たときに思ったのだけど ERB::Util.u も CGI.escape もエンコードしすぎだと感じてる人がいるみたい。(一部の記号をわざわざ復号していた。たしかに %XX が URLに現れるのは美しくない⁑)
閑休。存在するはずのカテゴリファイルがなくてエラーを出していたのは、ここ(20071208p01)で自分が書いた tdiary/categorizedio.rb だったので誰にでも起こる不具合なのか確証がなかったり。
http://tdiary-users.sourceforge.jp/cgi-bin/wforum/wforum.cgi?mode=allread&no=5718&page=0
tDiary標準のカテゴリモードがどのようになるのかは未確認だったり。
複数のポストを日付で括ってしまう tDiary(<日記だから)はどうしても <title>タグの中身が味気なくなってしまって、ボットにも人間にもアピールが弱いな、とか全然関係ないけど、いま思った。(BlogKitでは解決してそう)
ぼそり。(category.rbは @conf.data_pathと 'category'を連結するときにパスセパレータを二重化してる。問題はレンタルサーバ(FreeBSD)でもローカル(Windows)でも起きていないが、そういうのが気持ちわるい&気にしたくないので自分は File.joinや Scripting.FileSystemObject.BuildPathを必ず使う)
tDiary-devel MLより
「すでにUTF-8で運用している日記に対して、このパッチ込みのtDiaryを起動すると、さらに(EUC-JP→UTF-8決め打ちな)文字コード変換をかけようとして大変なことになります」のでご注意ください。
今から思えば、migrate.rbでの変換時や、Test_UTF8ブランチの時から、何らかのしるしをCGI生成のtdiary.confかどこかにうめこんでおくべきでした。
そんな気がしていました > UTF-8化済みのデータを EUC-JP->UTF-8変換。
だから一日待って勝手に変換してくれるのに任せようとしていた。
ダウンロードした tarballが期待したものより古かったと気付いたものの UTF-8化を最後まで行った後では、migrate.rbが印を埋め込んでいてくれることに期待していた。
漠然とした不安は肯定され、期待していた救いは否定された。
最新の tDiaryを調べて手動で印を埋め込むまでアップデートは危険。
このパッチは trunkに適用されたわけではなかった。早とちり。
というわけで必要がなくなってしまったけれど書いておくと、
tdiary_version = "2.3.0.20080302"
という行(インデントしてはいけない)を data_pathにある方の tdiary.confに付け加えておけば、再度 migrateが実行されることを回避できる。
今度こそパッチではなくコミット済み。migrate.rbを実行済みの人は(パッチの時とは別の)回避措置がアップデートの前に必要。
例えばこんな、<h1>の中身を<title>の中身と同じにし、かつ日記名部分をトップページへのリンクにする、ヘッダはエラーになる。
<h1> <a href="<%=h @conf.index%>"><%=@conf.html_title%></a> <%=title_tag[/\A<title>#{Regexp.quote h @conf.html_title}(.*?)<\/title>\z/oi, 1]%> </h1>
原因は
#{Regexp.quote h @conf.html_title}
この部分が tdiary.conf読み込み時に展開されてしまうから。
結局
#{'#'}{Regexp.quote h @conf.html_title}
こうなった。
1と 2のコンボで、ヘッダ/フッダに限らず全ての設定が変更できなくなる。他の設定を保存するときでも、ヘッダの保存部分で失敗するから。(結局 tdiary.confの可読性を犠牲にして String#dumpを使用するしかないようだ)
嫌い*だが tDiary本体をいじくるぐらいならしかたがない。今回のケースは結局 #{} を使わない形に書き換えた。無理矢理使っても面倒なだけだったので……。敗北。
<h1> <a href="<%=h @conf.index%>"><%=@conf.html_title%></a> <%=title_tag[Regexp.new('\\A<title>'+Regexp.quote(h( @conf.html_title))+'(.*?)</title>\\z', Regexp::IGNORECASE), 1]%> </h1>
* Regexp.new()のこと。eval()や new Function()と同じくらい嫌い。※new演算子があるのは Rubyではない。
ささいなことなのでここに書くわけだけど、HikiDocと tDiaryの許容する urlの違いから、tDiary同梱の hikidoc.rbの fix_url()が継ぎを当てられた結果、難解なことになっている。
if /:/ =~ uri and %r!\A(https?|ftp|file|mailto):! !~ uri uri elsif %r|://| !~ uri and /\Amailto:/ !~ uri uri.sub(/\A\w+:/, "") else uri end
uri.sub(/\A(?:https?|ftp|file):(?!\/\/)/, "")
上が 2008-03-04現在のコード。下は上と同じ意味になっていると思うが自信が持てない。それもひとえに前者が難解だから……。
下の方のコードはほとんど hikidoc.rb(revision 93, 2008-02-16)のものだ。これを目にしていなかったはずがない。
昨日(3日)の朝に、tDiary本体に migrate機能を付けたと読んだので、一日待って、今日アップデートしたのだけど、tarballは 2日のものだったみたい。(待った意味がない)
どこかから拾ってきたプラグイン以外は、tDiaryを最新のものにして migrate.rbを実行するだけでいいみたい。
カテゴリキャッシュも、プラグインが(confに)保存したデータも、古いキャッシュも migrate.rbが面倒を見てくれる。
中身ががらっと変わっていてびっくり。浦島太郎になっていた。Rubyist Magazine 出張版で hikidoc.rbが添削されていて、そのコードをベースに書き直したらしい。
tDiary-2.2.0同梱の hikidoc.rbにあった、複数行PREや複数行プラグイン記法が干渉する問題がなくなっている、というのがアップデートの目的。
Wikiスタイルでこういう本文を書くと
!_ <<< {{{}}} >>> {{'test'}}
このような HTMLになっていた ( {{'test'}}は testになっているべき )が、
<h3><a name="p01" href="./20080111.html#p01"><span class="sanchor">_</span></a> _</h3> <pre> {{{}}} </pre> <p>{{'test'}}</p>
VERSION 0.0.2では直っている。
HikiDocのアップデートに伴って、HikiDocを呼び出す部分を下のように変更する必要がある。
def to_html( string ) html = HikiDoc::to_html( string, :level => 3, :use_wiki_name => false, :allow_bracket_inline_image => false, :plugin_syntax => method(:valid_plugin_syntax?) ).strip
本文中にべた書きしたWikiNameをリンクにしてほしくない。
[[beautiful_stuff.png]]など角かっこに入れた画像っぽい URLを <img>に置換してほしくない。
空タグの終了の仕方は HikiDoc.to_htmlと HikiDoc.to_xhtmlを呼び分けることで変更する。
以前の形式のままでも大丈夫なように互換性が保たれているが、警告が出るので。
以前は二重のbrace(=プラグイン記法)など HikiDocにとって意味のある記号をそのまま表示したいときに
\{{plain text}}
とエスケープすることができたがそれが不可能になっている。今は代わりに
{{'{{'}}plain text}}
と書いている。他の書き方もあるかもしれないがプラグイン記法は将来も変更されないだろうから冗長でもこれが確実*。さもないとエスケープ方法が変わったときに過去の日記を書き換えてまわる羽目になる。(今の自分のように)
以前の hikidoc.rbに加えていた変更を新しい方にも。
「記法が有効になる ⇒ これらの文字をそのまま表示するためには一手間必要」
なんだけど、複数行PRE記法(<<<〜>>>)をそのままの状態(一切の修飾が無効)で残しているので必要なら(というか強調や打ち消しが不要なら)そちらを使える。
--- hikidoc.rb.r87 Mon Jan 14 09:43:25 2008 +++ hikidoc.rb Mon Jan 14 09:57:35 2008 @@ -328,11 +328,11 @@ INDENTED_PRE_RE = /\A[ \t]/ + # ''em'', '''strong''', ==strike== and {{plugin}} are available. def compile_indented_pre(f) lines = f.span(INDENTED_PRE_RE)\ - .map {|line| rstrip(line.sub(INDENTED_PRE_RE, "")) }\ - .map {|line| @output.text(line) } - @output.preformatted restore_plugin_block(lines.join("\n")) + .map {|line| rstrip(line.sub(INDENTED_PRE_RE, "")) } + @output.preformatted compile_modifier(lines.join("\n")) end BLOCK_PRE_OPEN_RE = /\A<<<\s*(\w+)?/
強調は有効にするけどプラグインは無効がいいなら、追加分の最終行を
+ @output.preformatted compile_modifier(restore_plugin_block(lines.join("\n")))
こうすればよいが、そうするとモディファイア(''em'', '''strong''', ==strike==)をモディファイアでない、そのままのテキストとして書く方法がなくなる気がする。
* プラグイン記法の中身は HikiDocを利用するアプリケーション(Hikiや tDiaryなど)によって許可される内容が変わってくる。プラグインを利用して書かれた HikiDoc形式の文章は HikiDocを利用するアプリ間でポータブルでないかもしれない。全然確実ではなかった。実際この書き方は Hikiに、有効なプラグインの呼び出しではないとエラーにされてしまう。だってただの文字列だから……。
20080101p01からの続き。正式な sh_ruby.js (私的改訂版)はそちらから。
機能は同じ(はず)なのになぜか全く様子の違う二つのスクリプトができてしまった。こんな感じ。
{ // %r(regexp) 'next': 6, 'regex': /%r(?=[\(<\[\{])/g, 'style': 'sh_regexp' }, { // %x(command), %w(array) 'next': 11, 'regex': /%[xWw](?=[\(<\[\{])/g, 'style': 'sh_normal' }, { // %(string), %s(symbol) 'next': 16, 'regex': /%[Qqs]?(?=[\(<\[\{])/g, 'style': 'sh_string' },
[ // state 6-10: %r(regexp) { 'exit': true, 'regex': /$/g }, { 'next': 7, 'regex': /\(/g, 'style': 'sh_regexp' }, { 'next': 8, 'regex': /</g, 'style': 'sh_regexp' }, { 'next': 9, 'regex': /\[/g, 'style': 'sh_regexp' }, { 'next': 10, 'regex': /\{/g, 'style': 'sh_regexp' }, { 'exit': true, 'regex': /[)>\]}]/g, 'style': 'sh_regexp' } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?=\()/g, }, { 'exit': true, 'regex': /(?=\))/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?=<)/g, }, { 'exit': true, 'regex': /(?=>)/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?=\[)/g, }, { 'exit': true, 'regex': /(?=])/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?={)/g, }, { 'exit': true, 'regex': /(?=})/g, } ], [ // state 11-15: %x(command) { 'exit': true, 'regex': /$/g }, { 'next': 12, 'regex': /\(/g, 'style': 'sh_normal' }, { 'next': 13, 'regex': /</g, 'style': 'sh_normal' }, { 'next': 14, 'regex': /\[/g, 'style': 'sh_normal' }, { 'next': 15, 'regex': /\{/g, 'style': 'sh_normal' }, { 'exit': true, 'regex': /[)>\]}]/g, 'style': 'sh_normal' } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?=\()/g, }, { 'exit': true, 'regex': /(?=\))/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?=<)/g, }, { 'exit': true, 'regex': /(?=>)/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?=\[)/g, }, { 'exit': true, 'regex': /(?=])/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?={)/g, }, { 'exit': true, 'regex': /(?=})/g, } ], [ // state 16-20: %Q(string) { 'exit': true, 'regex': /$/g }, { 'next': 17, 'regex': /\(/g, 'style': 'sh_string' }, { 'next': 18, 'regex': /</g, 'style': 'sh_string' }, { 'next': 19, 'regex': /\[/g, 'style': 'sh_string' }, { 'next': 20, 'regex': /\{/g, 'style': 'sh_string' }, { 'exit': true, 'regex': /[)>\]}]/g, 'style': 'sh_string' } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?=\()/g, }, { 'exit': true, 'regex': /(?=\))/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?=<)/g, }, { 'exit': true, 'regex': /(?=>)/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?=\[)/g, }, { 'exit': true, 'regex': /(?=])/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?={)/g, }, { 'exit': true, 'regex': /(?=})/g, } ]
{ // %r(regexp) 'next': 6, 'regex': /%r(?=[\(<\[\{])/g, 'style': 'sh_regexp' }, { // %x(command), %w(array) 'next': 8, 'regex': /%[xWw](?=[\(<\[\{])/g, 'style': 'sh_normal' }, { // %(string), %s(symbol) 'next': 10, 'regex': /%[Qqs]?(?=[\(<\[\{])/g, 'style': 'sh_string' },
[ { 'exit': true, 'regex': /$/g }, { // from 7. next sibling exists. 'next' : 7, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_regexp' }, { // from 7. no next sibling. 'exit' : true, 'regex': /(?:\)[^\)]*\)|>[^>]*>|][^\]]*]|}[^}]*})/g, }, { // from 0. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*\)|<[^<>]*>|\[[^\[\]]*]|\{[^\{}]*})/g, }, { // from 0. nesting parenthesis. 'next' : 7, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_regexp' } ], [ { 'exit': true, 'regex': /$/g }, { // from 7. next sibling exists. 'next': 7, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_regexp' }, { // from 7. no next sibling. 'exit': true, 'regex': /(?:\)[^\)]*(?=\))|>[^>]*(?=>)|][^\]]*(?=])|}[^}]*(?=}))/g, }, { // from 6. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*(?=\))|<[^<>]*(?=>)|\[[^\[\]]*(?=])|\{[^\{}]*(?=}))/g, }, { // from 6. nesting parenthesis. 'next': 7, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_regexp' } ], [ { 'exit': true, 'regex': /$/g }, { // from 9. next sibling exists. 'next' : 9, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_normal' }, { // from 9. no next sibling. 'exit' : true, 'regex': /(?:\)[^\)]*\)|>[^>]*>|][^\]]*]|}[^}]*})/g, }, { // from 0. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*\)|<[^<>]*>|\[[^\[\]]*]|\{[^\{}]*})/g, }, { // from 0. nesting parenthesis. 'next' : 9, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_normal' } ], [ { 'exit': true, 'regex': /$/g }, { // from 9. next sibling exists. 'next': 9, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_normal' }, { // from 9. no next sibling. 'exit': true, 'regex': /(?:\)[^\)]*(?=\))|>[^>]*(?=>)|][^\]]*(?=])|}[^}]*(?=}))/g, }, { // from 8. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*(?=\))|<[^<>]*(?=>)|\[[^\[\]]*(?=])|\{[^\{}]*(?=}))/g, }, { // from 8. nesting parenthesis. 'next': 9, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_normal' } ], [ { 'exit': true, 'regex': /$/g }, { // from 11. next sibling exists. 'next' : 11, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_string' }, { // from 11. no next sibling. 'exit' : true, 'regex': /(?:\)[^\)]*\)|>[^>]*>|][^\]]*]|}[^}]*})/g, }, { // from 0. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*\)|<[^<>]*>|\[[^\[\]]*]|\{[^\{}]*})/g, }, { // from 0. nesting parenthesis. 'next' : 11, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_string' } ], [ { 'exit': true, 'regex': /$/g }, { // from 11. next sibling exists. 'next': 11, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_string' }, { // from 11. no next sibling. 'exit': true, 'regex': /(?:\)[^\)]*(?=\))|>[^>]*(?=>)|][^\]]*(?=])|}[^}]*(?=}))/g, }, { // from 10. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*(?=\))|<[^<>]*(?=>)|\[[^\[\]]*(?=])|\{[^\{}]*(?=}))/g, }, { // from 10. nesting parenthesis. 'next': 11, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_string' } ]
stateいっぱい版の方が素性がいいのは一目瞭然ですね。(;^_^A アセアセ… 書くのにかかった時間は数分の一から十分の一だし、読み返して理解できるのもそっちだし。
ありえない正規表現の方は SHJSのエンジン部分(sh_main.js)を全く利用していないところに複雑さの原因がありそう。括弧の種類ごとに一つの stateが必要でなおかつそれが×3(=12)という stateいっぱい版の見通しに後込みしてこっちの泥沼にはまりこんでいった感じ。
尚どちらも、似てるけどちょっとだけ違うコードがほとんどの部分を占めている。例えば stateいっぱい版の state7-10、state12-15、state17-20の相違点は
'next': 6, // state7-10 'next': 11, // state12-15 'next': 16, // state17-20
の部分だけ。ここを
'next': 'caller'
と書ければ共通化できるのに……。また、state6、state11、state16の違いは
'style': 'sh_regexp' // state6 'style': 'sh_normal' // state11 'style': 'sh_string' // state16
の部分だけここを
'style': 'inherit'
と書ければ共通化できるのに……。
それなら追加部分のサイズが今のほぼ 1/3になったものを。
以下、変更点のリスト。(\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 },
文字列リテラルの終了条件に上のは必要ない、むしろこれがあることで複数行にまたがったリテラルを正しく認識できない、のだけど強力すぎる正規表現は誤認識があったときにソースを最後まで一色に染めてしまう危険性があるのでそのままにしている。ヒアドキュメントに対応しないのも同じ理由。
{ // %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";
やっぱりダメだ〜。
上で出した「こんなのも通る」と「やっぱりダメだ〜」の例が、言葉とは裏腹に「通ってない」と「ちゃんとできてる」状態になってると思う。だとしたら成功。
変更点は20080102p01で。
# for variable interpolation, #{ is not a comment
というコメントを付けて #{}のハイライトルールを定義しているにも関わらず、それが #コメントルール よりも後ろにあるために機能していなかった。#コメントルールを後ろに持ってきて解決。
続きは20080105p01で。
makerss.rbプラグインを有効にして、index.rbと同じ場所に空の index.rdfをアップロードしただけ。Firefoxのアドレス欄の右端にオレンジの電波マークが出現した。HTML中に rdfタグを埋め込むのだけは(たとえ HTMLの文法を満たしていようとも)いやだと思ってた*が下の一行が付け加えられただけのよう。
<link rel="alternate" type="application/rss+xml" title="RSS" href="http://vvvvvv.sakura.ne.jp/ds14050/diary/index.rdf">
実はフィード、RSS、rdf、Atomと新出単語が多くてよくわかっていない。lirsも含めて全部同じか全部違うものだと思っていて、どちらにしてもゲンナリだな、と。
過去の日記を「編集」するとその日の全てのセクションが rdfに上ってくるのね。tDiary-2.3系の目玉はセクション単位での編集機能だなあ。blogスタイルではできるんだろか? xmlrpc.rbではできるみたいだけど、日記更新のクライアントはブラウザという点は譲れない(下書きをするときはエディタを使う)。
パッと思いついたのは複数人で一つの日記を書いてる場合の更新の競合(セクションの削除と挿入が一番の問題。⁑)とかインターフェイスが分かりにくくならないかという問題。個人でちょちょいと自己責任でやる分には目をつむって(つぶる?)しまえるけど……。
一つ目のページで
- たぶん2.3行き
- 実装の要望が高いためいちおう検討されているもの
- モバイルモードでのセクション単位の編集(携帯で全文編集できないため)
RSS方面からの要望はないの? 「編集」するときは必ず「ちょっとした修正(RSSを更新しない)⁂」にチェックを入れてるって?
* テキスト形式のものを二回繰り返し、その後に HTML形式のものをくっつけた 80KB近いメールマガジンを送ってくる通販サイトがあるけれど、読めない無駄なものがくっついてるという点が似ている。そのメール、ソースを見ると vbCrLfとか見えてんだけど…… > Web!keさん
⁑ 日付とセクションナンバーに加えてサブタイトルも編集フォームに<input type="hidden">として埋め込んでおいて、サーバーで照合して不一致なら再編集、でほとんど問題なさそう。それとも本文も含めて MD5を算出するか。それなら同一セクションへの変更が競合したときに、先の変更が失われるのを防げるし。
⁂ Tab、スペース→プレビューという流れができあがっているので、これのチェックボックスの位置は少し邪魔。Alt+Shift+P(Fx on Windowsの場合)は面倒すぎるしぃ。
eval(<<__END_OF_TOPLEVEL__,TOPLEVEL_BINDING) module TDiary end __END_OF_TOPLEVEL__
に類する evalは
module ::TDiary end
でいいじゃない。
(行間に、構造に関係しないコードが省略されています)
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 ) )
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を使う。プラグインは増やすことはできても減らすと過去の日記でエラーが出たりするから。
なじみがあって処理内容が明確だから使いやすいのかも。同じことを find_allでやろうとするよりも(any?や findの結果から考えて)倍近く高速だろうことも想像ができるので、使いどころが正しければ優秀なメソッド。
2.2.0までは Ruby 1.6もサポートしていたので any?の使用は考えられないのだった。でも findは Ruby 1.6からあるようなのでここまで書いたことが全否定されたというわけでもない。よかった。
* ほんとうに?なんで?