最終更新: 2013-05-21T00:39+0900
<h4>と <h5>にもアンカー(<a name="..."></a>)を与える。href属性や中のテキスト( )は URLをコピーしやすくするためのおまけ。そもそも <h4>や <h5>に id属性を付加するのでなく <a>を利用しているところからが利便性目的以外のなにものでもない。<h4>と <h5>の中に <a>を置くことには、highlight.rbを修正しなくてもハイライトが機能するというメリットもある。デメリットは見出し語の先頭に余分な空白が挿入されている、ということ。セクションタイトル右端の編集用リンク「✍」は、<h3>に含まれないように気をつかっているのですよ。
def do_html4( date, idx, opt ) strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) } r = @html.lstrip r.sub!( %r!<h3>(.+?)</h3>!m ) do "<h3><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></h3>" end or r.sub!( %r!^<p>(.+?)</p>$!m ) do "<p><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></p>" end serial = [nil, nil, nil, '%02d'%idx, '00', '00'] dumped_param = strdump.call "#{date.strftime( '%Y%m%d' )}#p#{'%02d' % idx}" header_t = Struct.new(:opentag, :level) bqlevel = 0 r.gsub!( %r!(<blockquote\b)|(</blockquote>)|<h(4|5)[^>]*>!i ) do bqlevel += 1 if $1 bqlevel -= 1 if $2 next $& if bqlevel != 0 or $3.nil? h = header_t.new($&, $3.to_i) serial[h.level, serial.length-h.level] = Array.new(serial.length-h.level){|i| i==0 ? serial[h.level].succ : '00' } frag = 'p' + serial[3 .. h.level].join('.') %!#{h.tag}<a name="#{frag}" href="<%=anchor(#{dumped_param}).gsub(/#p#{'%02d'%idx}/, #{strdump.call('#'+frag)}) %>"> </a>! end r.gsub( /<(\/)?tdiary-section>/, '<\\1p>' ) end
2段階に評価されるせいで、すんごく読みにくい。
ハッシュテーブルのキーがシンボルか文字列か気にしたくないのと、括弧や引用符やコロンがじゃまくさいので、. ひとつで済む Structに登場してもらった。
lambdaの .call() も嫌いだなあ。[] で () を代用するおぞましさよりはマシだが。
試してないけど、「end or r.sub!(...」を複数の行に分けるとシンタックスエラーになりそうなのも嫌。
破壊的メソッドが返す nilを利用するコードを初めて書いた!
H4のシリアルナンバーが増加したときに H5のシリアルをリセットするように変更した。以前はこういう連番だった。
最後が p01.02.01になるように。
URLフォーマットが変わると影響範囲が広くて困る。自分の日記へのリンクを検出する正規表現をあちこちで書き直した。
Index: core/tdiary/wiki_style.rb =================================================================== --- core/tdiary/wiki_style.rb (リビジョン 43193) +++ core/tdiary/wiki_style.rb (作業コピー) @@ -149,7 +149,7 @@ hikihtml = HikiDoc::HTMLOutput.new html.gsub!( %r!<a href="(.*?)">(.*?)</a>! ) do k, u = hikihtml.unescape_html($2), hikihtml.unescape_html($1) - if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)[^\d]*?#?([pct]\d+)?$/ =~ u then + if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)\D*([pct]\d+(?:\.\d+)*)?$/ =~ u then %Q[<%=my '#{$1}#{$2}', '#{escape_quote CGI.escapeHTML k}' %>] elsif /:/ =~ u scheme, path = u.split( /:/, 2 ) Index: core/plugin/00default.rb =================================================================== --- core/plugin/00default.rb (リビジョン 43193) +++ core/plugin/00default.rb (作業コピー) @@ -480,7 +480,7 @@ # make anchor string # def anchor( s ) - if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then + if /^([\-\d]+)#?([pct][\d\.])?$/ =~ s then if $2 then "?date=#$1##$2" else @@ -540,7 +540,7 @@ # make anchor tag in my diary # def my( a, str, title = nil ) - date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4}|)\D*([pct]\d+)?$/ )[0] + date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4})\D*([pct]\d+(?:\.\d+)*)?$/ )[0] anc = frag ? "#{date}#{frag}" : date index = /^https?:/ =~ @index ? '' : @conf.base_url index += @index.sub(%r|^\./|, '') Index: plugin/html_anchor.rb =================================================================== --- plugin/html_anchor.rb (リビジョン 43193) +++ plugin/html_anchor.rb (作業コピー) @@ -14,7 +14,7 @@ if @conf.index.empty? or /\/$/ =~ @conf.index def anchor( s ) - if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then + if /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then if $2 then "#$1.html##$2" else Index: plugin/my-sequel.rb =================================================================== --- plugin/my-sequel.rb (リビジョン 43193) +++ plugin/my-sequel.rb (作業コピー) @@ -487,7 +487,7 @@ alias :my_sequel_orig_my :my unless defined?(my_sequel_orig_my) def my(*args) if @my_sequel_active and @my_sequel_date and @my_sequel_anchor and @mode != 'preview' then - dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+))?$/)[0] + dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+)(?:\.\d+)*)?$/)[0] if dst_date and dst_date < @my_sequel_date then dst_anchor = "#{dst_date}#{frag ? "#p%02d" % frag.to_i : ''}" @my_sequel.add(@my_sequel_anchor, dst_anchor) Index: plugin/my-ex.rb =================================================================== --- plugin/my-ex.rb (リビジョン 43193) +++ plugin/my-ex.rb (作業コピー) @@ -13,9 +13,10 @@ unless @conf.mobile_agent? +alias :my_overwritten_by_my_ex :my def my( a, str, title = nil ) m = /^(\d{8})\D*(?:([pct])(\d+))?$/.match( a ) - return '' unless m + return my_overwritten_by_my_ex( a, str, title ) unless m _, date, place, frag = *m if title.nil? and date and frag and @diaries[date] then
misc/plugin/html_anchor.rbと plugin/00default.rbプラグインを再び修正。
元々 | if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then |
修正ミス | if /^([\-\d]+)#?([pct]\d+(?:\.\d+)*)?$/ =~ s then |
OK | if /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then |
「ツッコミを入れる」リンクは YYYYMMDD#c へのリンクなので、cの後ろに数字が続いていない。これをうっかり除外してしまっていた。
最終更新: 2014-12-18T13:09+0900
すぐ上のセクションで出てきた(俺が書いたんだけど)こういうの、
alias :my_overwritten_by_my_ex :my def my ... end
こういう「エイリアス&上書き」が嫌いで、不完全ながらこういうものを書いたことがある。(今でも使っている。セミコロンを抑制する努力をしていない頃に書いたもののようだ。Rubyのバージョンで nameをシンボルにするか文字列にするか振り分けているが、今ならやらない。先頭要素を取り出してそれがシンボルか文字列かを調べる。そして、代入を ifで囲うのではなく ifの結果を代入する)
class ::Object def overwrite(name, &block) if('1.9.0'<=RUBY_VERSION); name = name.to_sym; else name = name.to_s; end if(self.singleton_methods(false).member?(name)) $dirty = [name, block]; class <<self name, block = *$dirty; old = instance_method(name); remove_method(name); # define_method(name){|*params| # block.call(old.bind(self), *params); # ブロックは渡せない? 1.9ならできる? # } define_method("__overwrite__#{name}__#{block.object_id}", &block); define_method(name){|*params| send("__overwrite__#{name}__#{block.object_id}", old.bind(self), *params); } end else if(true != self.class.ancestors.each{|klass| if(klass.methods(false).member?(name)) klass.instance_eval{ old = method(name); remove_method(name); # define_method(name){|*params| # block.call(old, *params); # ブロックは渡せない? 1.9ならできる? # } define_method("__overwrite__#{name}__#{block.object_id}", &block); define_method(name){|*params| send("__overwrite__#{name}__#{block.object_id}", old, *params); } } break true; end }) # failure raise; end end end private :overwrite end
グローバル変数は赤色でよく目立つね。$dirtyっていう変数はスコープの断絶を乗り越えるために使ってるんだろう。汚いやり方だ。
もはや自分でも読み解けないが、こうして使う(んだったかな? define_methodしたときはブロックで returnが使えた気がする)。
overwrite(:my){|old, *params| # 新しいmyを以下に…… # 必要なら古い定義も呼び出せる。 return old.call(*params) }
継承による overrideであれば、隠された同名のメソッドを superで呼び出せるけど、メソッドの上書きではそれができなくて古い定義を呼び出せない。かといって「alias :foo_bar :foo」「alias :foo_baz :foo」式の無秩序なやり方は見るに堪えない。overwrite.rbがやってることも機械的に名前を付ける以外は同じではあるけども(まだましでしょ?)。
<追記@2009-12-19>コメント部分がなんでコメントアウトされているのかやっと思い出せた気がする。奇しくも自分でその理由を書いていたりするのだが。>「define_methodしたときはブロックで returnが使えた気がする」つまり、新メソッド定義(overwriteメソッドに与えられたブロック)を define_methodでメソッド化しておかなければ returnが使えないからだ。かといって古いメソッド定義を渡すために一段余計な処理を挟まなければいけないから、新メソッド定義(ブロック)をそのままの名前でメソッドにすることもできない。ブロックが保持する bindingに変数(旧メソッドを指している)を注入する方法があれば、ダミーの長ったらしい名前を持つメソッドをこっそり定義する今のやり方から抜け出せるかも。</追記>
俺が知らないだけで 1.9以降では解決策があるんだろうか。こういう現実の問題に対処してほしいなあ。わりと見かける aliasの使い方で、問題含みだと思うんだけど。
一人見つけた。
super=承継元クラスの同名メソッドを呼び出す=とは別に、 overridden=上書きされたメソッドを呼び出す=というのを作るというのはどうでしょう?
こちらはちょっと違う。
aliasによるメソッドの再定義は危険なのでUnboundMethodかextendを使おう - (rubikitch loves (Emacs Ruby CUI))
再ロード対策は考えていなかった。
extendは知らないではないけど……(これが求めていたものなんだろうか?)。
新しい名前を導入しないように extendでやってみた。エラーは出ないけどリンク先のサブタイトルが取得できていない(my-exによる拡張が機能していない)。instance_evalを module_evalにしてみてもだめ。
extend Module.new{|mod| mod.instance_eval{ define_method(:my){|*params| a, str, title = *params # myプラグインを拡張し、リンク先が自分の日記のとき、 # 可能ならサブタイトルやコメントを取得して title属性にセットします。 } }}
オーソドックスにやってみても同じこと。
module MyEx def my( a, str, title = nil ) end end extend MyEx
extendする対象を間違えてるんだろうか。Pluginのインスタンスに特異メソッドを定義してるつもりでいるんだけど。いずれにしろ答えにたどり着けないのでは使えないよ。def...end と書いてメソッドの定義を上書きするよりも安全で洗練された代替が欲しいんだから、def...endと書く以上の複雑さは受け容れられない。
なんでも、aliasするときにメソッド名にコロンを付けてはいけないらしい。
http://www.loveruby.net/w/RubyCodingStyle.html
なぜ付けるかといえば、ほとんどすべてが「実行される」Rubyにおいて、クラスやメソッドの定義(class...end, def...end)ですら実行時の条件によってふりわけられる Rubyにおいて、構文であること、特別扱いされることが許せないからだ。「alias はメソッド呼び出しではなくて構文である。だからこそ引数の間にカンマがないのだ。そこはわかっていながらなぜエセメソッドのような使いかたをするのか。」それは構文であることへの抵抗なのです(俺の場合)。
「ちなみにどうしてもコロンが付けたければ alias_method を使えばいいんではないかと思う。」
そいつ( alias_method )は知らなかった。コロンだけでなくメソッド名とメソッド名の間にカンマを置くこともできるんだから、喜んでそちらを使いますとも。alias_methodと aliasは使える場所や対象が完全には一致していないけども、aliasでしかできないことをした経験はないから問題はなし。
「aliasでしかできないことをした経験はないから問題はなし」なんて書いたそばからこんな例が……
特異メソッドをalias_methodを使用して別名をつけようとするとエラーになる。(中略) 特異メソッドに別名をつけて退避したい場合は、alias_method (_chain)メソッドではなくてaliasをinstance_evalのブロック内で使用するとよい。
特異メソッドは別、でした。想像だけど extendでプラグインメソッドの上書きができなかったのも特異メソッドがらみだと思うな。
昨日の文章はおかしいな。class...endや def...endという構文を認めながら aliasだけを白眼視する説明になっていないような。
class MyClass; endと書いた場合、MyClassは Classオブジェクトの入った定数(という言語のビルディングブロック)だと認識している。def my_method; endと書いた場合、my_methodはメソッド(という言語の(略)であり、以後、my_methodや my_method()と書いた場合それはメソッドの呼び出しである、というのが Rubyという言語の約束だと認識している。「alias alias_of_my_method my_method」という構文は他の構文との共通性がなく例外的なルールに感じられ、(同じコンテクストで用いられる)includeや private、attr_accessorが Moduleのプライベートメソッドとしてあるなかで構文である必然性が認められず、メソッド名に関するルールを破ってさえいる。そこが許せない。aliasの(メソッドではないから正確には違うが)引数の「alias_of_my_method」や「my_method」を言語としてどう位置づけるのか、そこををはっきりさせないのならばそれは使えない、という主張が aliasのメソッド名部分にコロンを付ける理由。
undefや defined?にも aliasと同じ疑問がある(ただし、undefは defの対称として容易に受け入れられるし、defined?の場合、必然性だけは認められる)。JavaScriptの typeof演算子や delete演算子にすら同じ違和感を覚えている*。コンパイル型言語のように、コンパイル時に評価されるのだという言い訳ができないインタプリタ型言語の弱みだろうか。こういうものを見ると Perlに感じたように言語が突然平板なものに見えてしまって嫌なのだ。そこには対象となるメモリやオブジェクトが存在せず、ソースコードの字面しかないでしょう。
* きちんと定義はされています >「[[8.7 Reference 型 (Reference Type)|http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/8_Types.html#section-8.7]]」
最終更新: 2009-12-28T23:38+0900
もうひとつ、最初のセクションからネタ拾い。こんなのや、
strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) }
別の場所から持ってきたこんなの
jsstr = lambda{|s| s.gsub(/["\\]/){|x| "\\#{x}" } }
を、自分はときどき書くようだ。[1,2,3].map{|x| ... } みたいなのを書くことはもっとある。「|x| x.」の部分が冗長だね、っていう話。
省きたいけど、単純に考えるとブロックの中が x. で始まっていなければいけない。適用範囲が狭いんじゃないか?と疑問がわく。Rubyは「オブジェクト指向スクリプト言語」をうたっているからメソッドチェインを促す意味でサポートを行う名目はあるだろう。
メソッド呼び出しの構文をとりながら自身を引数にしてよそのメソッドを呼べるようにしたり(C#3.0の extension method風)、Hikiがすでにやっているように
class String def escapeHTML CGI::escapeHTML(self) end end
本当にインスタンスメソッドにしてしまったり。CGI::escapeHTML("str") より "str".escapeHTML にしておいた方が(1引数ラムダでの利用に)便利ですよ、という動機付けを言語として行ってもいいと思う。あとは引数の束縛やらラムダの .callと []以外の呼び出し方、ブロックやラムダを用いて定義したブロックを引数にしてメソッドを呼び出したりできれば何かができそうな気がしなくもない予感がなきにしもあらず。
全然関係あるのかないのか、Haskellのポイントフリースタイル(初めて目にしたのは 12月15日頃)を検索してみた。$ で順々に処理をつなげているものがあれば . を使って、途中の処理で必要な引数を押し出していくようにしながら一つの処理に書き換えるのだろうか。ポイントフリーなのにポイント塗れとはこれ如何に? 「firstNLines = (.lines).((unlines.).take)」のように明示されなくなった引数 Nはわかりにくくない? 2引数の関数はポイントフリースタイルで書けるの?『ふつける』を通し読みしただけで、モナドもわからず、一行たりとも Haskellコードを書いたことのない人間には疑問がいっぱいです。
最終更新: 2010-01-15T21:20+0900
上前津伏見くんの写真の中に、トラ技を背景にする Interface誌を見つけた。その数日前に「ときどきの雑記帖 i戦士篇 2009年11月(上旬)」で同誌の連載について触れられているのを読んでいた。こういう巡り合わせには積極的に乗っかることにしている。
家の本棚にはトランジスタ技術があったけど、読んだのは C言語特集だけ。volatileというキーワードをそこで発見して、十年以上前の Cにはそんなキーワードがあったのかと思ったら、単なる自分の知識の穴であって、その後 volatileを付けないと期待通りに動作しないケースに自分で遭遇することもあったり。ともかく、ハードウェアはわからない。
あらら、今月は隔月連載の載ってない月だ。来月号も買って、それで一年はお腹いっぱい。
「ラプラス変換がラブプラス変換に見えて困る」(某所にて) に対して、ラプラス変換なんて見たことねーよ、と心中で突っ込んでいたら、モーターのところで出てきた。
最終更新: 2010-01-15T21:20+0900
読みやすくも正確にも書けないのでリンクだけ。
サクラエディタの肥大化する一方の CEditView(描画、ダイアログの表示、文字列検索、単語判定、URL判定、ドラッグアンドドロップ対応、などなどなんでもござれ)を機能ごとに分割したいなあ、と考えていて、モデルとして想定していたのが Rubyの Enumerableモジュールのミックスイン。Enumerableを ミックスイン(include)するクラスが eachメソッドを提供するだけで、およそシーケンシャルアクセス可能なコレクションに対して行いたい操作(max, min, find, map,...)のすべてが可能になるという仕組み。
C++でやるにはどうするのかな、と想像していたときに「これってあれじゃね?」と結びついたのが「奇妙に再帰したテンプレートパターン(CRTP)」。このパターンは「[単行本(ソフトカバー)] ビョルン・カールソン【Boost C++をチューンアップする最先端ライブラリ】 ピアソンエデュケーション」で仕入れた。
仮に Enumerableモジュールのようなものをこのパターンで実現できたとして、その Enumerableテンプレート(eachメソッドが定義されたクラスをテンプレートパラメータとして受け取る)が配列っぽいあれやこれやのクラスにミックスイン(継承)されたらオブジェクトファイルがすごく肥大化しそうだという気がする。
純粋仮想関数にした eachに依存する形で Enumerableの各メソッドを実装すれば、テンプレートを使ったときと違って実装の共有ができるだろうか。
というようなことを想像してるだけ。
eachが yieldする型は何になるんだ?(C++で yieldなんてできないという話はおいておいて) 結局、型安全性を求めたらテンプレートになって、型ごとに実装(テンプレートのインスタンス)が作られるんじゃないか。
と、ここで boost::anyを使ってはどうか。Enumerableのメソッドの実装は要素の型に依存しないから anyのまま扱う。Enumerableのメソッドを呼び出す側が要素の型を知っているから anyから正しい型のオブジェクトを取り出せる。
と思ったら、「Enumerableのメソッドの実装は要素の型に依存しない」は嘘だ。比較できないと最大値や最小値が求まらん。Enumerableがコレクションに対して求めてるのは eachだけだけど、その要素に対しては比較できることや加算できることなど、いろいろ要求している。これじゃあテンプレートでないとやってられない。
いやいやいや、min, maxをはじめ Enumerableのほとんどのメソッドは要素を引数にとるブロックを受け付けて、その結果を基に処理を行っている。ブロックのコンテキストは Enumerableのメソッドを呼び出す側だから要素の型を知っている。やっぱり Enumerableの実装は要素の型を知らなくてもいい。
ブロックの代わりになるのは関数オブジェクトか traitsかな。
とまあ、かように面倒くさそうなことになるから CEditViewクラスになんでも突っ込む結果になるのも当然。言語の不備だ、とか言ってみる。
あるクラス(CEditDoc, CEditView)にいろいろな特性(検索可能性、ドラッグアンドドロップ可能性、範囲選択可能性、……)を、簡単にクラス外から付加する確立した手順が必要。……と、あくまで想像するだけ。
それに分割してしまうと機能間での連携(依存ともいう)に苦労することになるのが目に見えている。ANSI版の CEditViewはなんでも一カ所につっこめる限界点を超えているとは思うが。
mix-inや traitsは、使われる場所でいろいろ意味が違うみたい。自分は mix-inといえば Rubyのしか知らないし、traitsといえば std::stringの char_traitsしか知らない(それすら知っているとはいえない)。