From: Makoto Kuwata Date: 2008-02-12T00:08:57+09:00 Subject: [ruby-dev:33702] Re: erb.rb の仕様について 桑田です。 2008/2/11 Masatoshi SEKI : > > それでは、Thread を使うかどうかは仕様ではなく、 > > 「safe_level を設定したときにそれがメインプログラムの > > $SAFE に影響を及ぼさないこと」が仕様だという理解で > > よろしいでしょうか。 > > はい。 了解です。 > > > > なんと、erb.rb の正式な仕様でしたか。了解しました。 > > では :nodoc: は取り除いた方がいいですね。 > > rdocへのパッチは全て取り込むつもりです。 > ぜひ適切なrdocを送ってください! > みなさん 送ります。 ついでに、ERB#filename の値が ERB#def_module と ERB#def_class に 反映されるように変更しています。 #パッチが壊れたら申し訳ありません。 Index: lib/erb.rb =================================================================== --- lib/erb.rb (revision 15382) +++ lib/erb.rb (working copy) @@ -744,19 +744,49 @@ end end - def def_method(mod, methodname, fname='(ERB)') # :nodoc: + # Define _methodname_ as instance method of _mod_ from compiled ruby source. + # + # example: + # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml + # erb = ERB.new(File.read(filename)) + # erb.def_method(MyClass, 'render(arg1, arg2)', filename) + # print MyClass.new.render('foo', 123) + def def_method(mod, methodname, fname='(ERB)') mod.module_eval("def #{methodname}\n" + self.src + "\nend\n", fname, 0) end - def def_module(methodname='erb') # :nodoc: + # Create unnamed module, define _methodname_ as instance method of it, and return it. + # + # example: + # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml + # erb = ERB.new(File.read(filename)) + # erb.filename = filename + # MyModule = erb.def_module('render(arg1, arg2)') + # class MyClass + # include MyModule + # end + def def_module(methodname='erb') mod = Module.new - def_method(mod, methodname) + def_method(mod, methodname, @filename || '(ERB)') mod end - def def_class(superklass=Object, methodname='result') # :nodoc: + # Define unnamed class which has _methodname_ as instance method, and return it. + # + # example: + # class MyClass_ + # def initialize(arg1, arg2) + # @arg1 = arg1; @arg2 = arg2 + # end + # end + # filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml + # erb = ERB.new(File.read(filename)) + # erb.filename = filename + # MyClass = erb.def_class(MyClass_, 'render()') + # print MyClass.new('foo', 123).render() + def def_class(superklass=Object, methodname='result') cls = Class.new(superklass) - def_method(cls, methodname) + def_method(cls, methodname, @filename || '(ERB)') cls end end @@ -810,15 +840,46 @@ #-- # ERB::DefMethod class ERB - module DefMethod # :nodoc: + + # Utility module to define eRuby script as instance method. + # + # === Example + # + # example.rhtml: + # <% for item in @items %> + # <%= item %> + # <% end %> + # + # example.rb: + # require 'erb' + # class MyClass + # extend ERB::DefMethod + # def_erb_method('render()', 'example.rhtml') + # def initialize(items) + # @items = items + # end + # end + # print MyClass.new([10,20,30]).render() + # + # result: + # + # 10 + # + # 20 + # + # 30 + # + module DefMethod public - def def_erb_method(methodname, erb) + # define _methodname_ as instance method of current module, using ERB object or eRuby file + def def_erb_method(methodname, erb_or_fname) if erb.kind_of? String - fname = erb - File.open(fname) {|f| erb = ERB.new(f.read) } - erb.def_method(self, methodname, fname) + fname = erb_or_fname + erb = ERB.new(File.read(fname)) + erb.def_method(self, methodname, fname) else - erb.def_method(self, methodname) + erb = erb_or_fname + erb.def_method(self, methodname, erb.filename || '(ERB)') end end module_function :def_erb_method =================================================================== > > これって、非公開としている Compiler オブジェクトが > > 引数に必要なんですよね。設計としてはあんまりよろしく > > ないと思いますが、どうでしょうか。 > > そういうものなのですか。 > compilerに対するセットアップをCompilerに持たせたくなくて > ERBに配置したんだけどだめでしょうか。 > privateにしてくれ! なら同意します。 private にしてもいいのなら、ぜひそうすることをお勧めします。 今だと RDoc がついているので、どうみても ERB の公式な仕様と 考えられます。 > #「気分的には非公開」といっても実際には公開ですよね。 技術的にはそうですけど、ドキュメントにまで積極的に記載する 必要はないですよね。 コードやドキュメントで「非公開である」と明記するだけで十分 だと思います。そのための :nodoc: です。 > #Rubyなので好きなように実行時に手が入れられます。 > #なので、どこをいじっても誰かが「ぎゃっ」と言うでしょう。 ドキュメントに記載してない機能をいじって「ぎゃっ」となれば、 それはその人の勝手です。 記載してある機能を使っていて「ぎゃっ」となれば、それは 開発者の責任です。 > ERB互換classが書きにくいって話ですか? いいえ、ERB の仕様を明確にしたいという話です。 今、ERB 用のテストスクリプトを書いていて、RDoc に載っている 機能を網羅しようとしていたので、質問させていただきました。 > 私はerubisをパクってCompilerを改造しようと思ってます。 Erubis は拡張性を重視してわりと複雑なことをしているので、 Compiler の参考にするなら、Erubis に含まれている tiny.rb を 参考にするのが簡単です。 tiny.rb は 50 行程度で eRuby のクラスを実装しています。 > > #そもそも set_eoutvar() は非公開メソッド? > > #なら :nodoc: つけてほしいです。 > > #丁寧にも RDoc がついているから、ERB の仕様だと > > #思ってしまいました。 > > rdocのパッチを募集します! 確認なんですけど、private にしてもよいということなので、 ERB#set_eoutvar() は仕様には含めなくていいんですよね? Index: lib/erb.rb =================================================================== --- lib/erb.rb (revision 15382) +++ lib/erb.rb (working copy) @@ -704,7 +704,7 @@ # to just use the constructor though, since calling this method requires the # setup of an ERB _compiler_ object. # - def set_eoutvar(compiler, eoutvar = '_erbout') + def set_eoutvar(compiler, eoutvar = '_erbout') # :nodoc: compiler.put_cmd = "#{eoutvar}.concat" compiler.insert_cmd = "#{eoutvar}.concat" =================================================================== > 私はeoutvarもsafe_levelも失敗だったと思ってます。 > もっとうまく使い方を誘導するべきでした。 そうなんですか?個人的にはそんなに問題とは思わないんですが。 失敗だとしたら、ERB#compile がなくて ERB#initialize でしか コンパイルできないことだと思います。 #これって CGI class (cgi.rb) も同じような設計なんですよね。 #CGI#initializeで全部パースするから困る。 > > Complierは一時的なコンテキストというか処理オブジェクトなので > eoutver=では手遅れみたいです。 ならやはり Compiler オブジェクトを指定できるように設計した方が よかったのかなと思います。 これだと、trim_mode と set_eoutvar の機能を ERB class から Compiler class へ分離でき、すっきりします。 compiler = Compiler.new(:trim=>'>', :eoutvar=>'_erbout) erb = ERB.new(eruby_src, safe_level, compiler) ああそうか、これだと Compiler class を公開しないといけないのか。 難しいですね。 じゃあそもそも、ERB#initialize の eoutvar 引数ではダメで、 わざわざ ERB#set_eoutvar() を用意しなければならないような 要求が具体的にどんなことだったのか、気になります。 > _erboutは変数名しか設定できないから > メソッドにしてフックできるようにした とありますが、フックして何をしようとしたのでしょうか。 ERB の仕様という話からそれるのでオフトピですけど、もし 差し支えなければ教えてください。 -- makoto kuwata