From: Masatoshi SEKI Date: 2009-01-01T10:01:43+09:00 Subject: [ruby-dev:37672] Re: M17N of erb(1) Was: Re: Current status of 1.9.1 RC1's issues --Apple-Mail-2--938525577 Content-Type: text/plain; charset=ISO-2022-JP; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit 咳といいます。 On 2008/12/29, at 16:18, Masatoshi SEKI wrote: > > > 確認しました。 > > このままcheckinしていただいてOKです。 Yuguiさん、申し訳ありません。 ruby_1_8で変更したERBの高速化が1.9側に入っていま せんでした。 こんな感じで変更したいのですが、良いですか? 1.9との差分、1.8との差分を添付します。 --Apple-Mail-2--938525577 Content-Disposition: attachment; filename=1_8.diff Content-Type: application/octet-stream; x-unix-mode=0644; name="1_8.diff" Content-Transfer-Encoding: quoted-printable ---=20/Users/mas/develop/lithium/ruby_1_8/lib/erb.rb=092008-12-21=20= 02:01:03.000000000=20+0900=0A+++=20erb.rb=092009-01-01=20= 09:50:41.000000000=20+0900=0A@@=20-58,6=20+58,24=20@@=0A=20#=0A=20#=20= See=20the=20ERB.new=20and=20ERB#result=20methods=20for=20more=20detail.=0A= =20#=0A+#=20=3D=3D=20Character=20encodings=0A+#=0A+#=20ERB=20(or=20ruby=20= code=20generated=20by=20ERB)=20returns=20a=20string=20in=20the=20same=0A= +#=20character=20encoding=20as=20the=20input=20string.=20=20When=20the=20= input=20string=20has=0A+#=20a=20magic=20comment,=20however,=20it=20= returns=20a=20string=20in=20the=20encoding=20specified=0A+#=20by=20the=20= magic=20comment.=0A+#=0A+#=20=20=20#=20-*-=20coding:=20UTF-8=20-*-=0A+#=20= =20=20require=20'erb'=0A+#=0A+#=20=20=20template=20=3D=20ERB.new=20<=0A+#=20=20=20=20=20= \_\_ENCODING\_\_=20is=20<%=3D=20\_\_ENCODING\_\_=20%>.=0A+#=20=20=20EOF=0A= +#=20=20=20puts=20template.result=0A+#=0A+#=20Prints:=20= \_\_ENCODING\_\_=20is=20Big5.=0A+#=0A=20#=0A=20#=20=3D=3D=20Examples=0A=20= #=0A@@=20-236,7=20+254,7=20@@=0A=20#=20Rails,=20the=20web=20application=20= framework,=20uses=20ERB=20to=20create=20views.=0A=20#=0A=20class=20ERB=0A= -=20=20Revision=20=3D=20'$Date:=202008-12-17=2020:07:32=20+0900=20(=E6=B0=B4= ,=2017=2012=202008)=20$'=20=09#'=0A+=20=20Revision=20=3D=20'$Date::=20= 2008-12-29=2018:26:34=20+0900#$'=20=09#'=0A=20=0A=20=20=20#=20Returns=20= revision=20information=20for=20the=20erb.rb=20module.=0A=20=20=20def=20= self.version=0A@@=20-487,10=20+505,10=20@@=0A=20=20=20=20=20end=0A=20=0A=20= =20=20=20=20class=20Buffer=20#=20:nodoc:=0A-=20=20=20=20=20=20def=20= initialize(compiler)=0A+=20=20=20=20=20=20def=20initialize(compiler,=20= enc=3Dnil)=0A=20=09@compiler=20=3D=20compiler=0A=20=09@line=20=3D=20[]=0A= -=09@script=20=3D=20""=0A+=20=20=20=20=20=20=20=20@script=20=3D=20enc=20= ?=20"#coding:#{enc.to_s}\n"=20:=20""=0A=20=09@compiler.pre_cmd.each=20do=20= |x|=0A=20=09=20=20push(x)=0A=20=09end=0A@@=20-527,7=20+545,11=20@@=0A=20=20= =20=20=20end=0A=20=0A=20=20=20=20=20def=20compile(s)=0A-=20=20=20=20=20=20= out=20=3D=20Buffer.new(self)=0A+=20=20=20=20=20=20enc=20=3D=20s.encoding=0A= +=20=20=20=20=20=20raise=20ArgumentError,=20"#{enc}=20is=20not=20ASCII=20= compatible"=20if=20enc.dummy?=0A+=20=20=20=20=20=20s=20=3D=20= s.dup.force_encoding("ASCII-8BIT")=20#=20don't=20use=20constant=20= Enoding::ASCII_8BIT=20for=20miniruby=0A+=20=20=20=20=20=20enc=20=3D=20= detect_magic_comment(s)=20||=20enc=0A+=20=20=20=20=20=20out=20=3D=20= Buffer.new(self,=20enc)=0A=20=0A=20=20=20=20=20=20=20content=20=3D=20''=0A= =20=20=20=20=20=20=20scanner=20=3D=20make_scanner(s)=0A@@=20-584,7=20= +606,7=20@@=0A=20=20=20=20=20=20=20end=0A=20=20=20=20=20=20=20= out.push("#{@put_cmd}=20#{content_dump(content)}")=20if=20content.size=20= >=200=0A=20=20=20=20=20=20=20out.close=0A-=20=20=20=20=20=20out.script=0A= +=20=20=20=20=20=20return=20out.script,=20enc=0A=20=20=20=20=20end=0A=20=0A= =20=20=20=20=20def=20prepare_trim_mode(mode)=0A@@=20-624,6=20+646,18=20= @@=0A=20=20=20=20=20end=0A=20=20=20=20=20attr_reader=20:percent,=20= :trim_mode=0A=20=20=20=20=20attr_accessor=20:put_cmd,=20:insert_cmd,=20= :pre_cmd,=20:post_cmd=0A+=0A+=20=20=20=20private=0A+=20=20=20=20def=20= detect_magic_comment(s)=0A+=20=20=20=20=20=20if=20/\A<%#(.*)%>/=20=3D~=20= s=20or=20(@percent=20and=20/\A%#(.*)/=20=3D~=20s)=0A+=09comment=20=3D=20= $1=0A+=09comment=20=3D=20$1=20if=20comment[/-\*-\s*(.*?)\s*-*-$/]=0A+=09= if=20%r"coding\s*[=3D:]\s*([[:alnum:]\-_]+)"=20=3D~=20comment=0A+=09=20=20= enc=20=3D=20$1.sub(/-(?:mac|dos|unix)/i,=20'')=0A+=09=20=20enc=20=3D=20= Encoding.find(enc)=0A+=09end=0A+=20=20=20=20=20=20end=0A+=20=20=20=20end=0A= =20=20=20end=0A=20end=0A=20=0A@@=20-699,7=20+733,7=20@@=0A=20=20=20=20=20= @safe_level=20=3D=20safe_level=0A=20=20=20=20=20compiler=20=3D=20= ERB::Compiler.new(trim_mode)=0A=20=20=20=20=20set_eoutvar(compiler,=20= eoutvar)=0A-=20=20=20=20@src=20=3D=20compiler.compile(str)=0A+=20=20=20=20= @src,=20@enc=20=3D=20*compiler.compile(str)=0A=20=20=20=20=20@filename=20= =3D=20nil=0A=20=20=20end=0A=20=0A@@=20-725,7=20+759,7=20@@=0A=20=20=20=20= =20compiler.pre_cmd=20=3D=20cmd=0A=20=0A=20=20=20=20=20cmd=20=3D=20[]=0A= -=20=20=20=20cmd.push(eoutvar)=0A+=20=20=20=20= cmd.push("#{eoutvar}.force_encoding(__ENCODING__)")=0A=20=0A=20=20=20=20=20= compiler.post_cmd=20=3D=20cmd=0A=20=20=20end=0A@@=20-747,10=20+781,10=20= @@=0A=20=20=20=20=20if=20@safe_level=0A=20=20=20=20=20=20=20proc=20{=20=0A= =20=09$SAFE=20=3D=20@safe_level=0A-=09eval(@src,=20b,=20(@filename=20||=20= '(erb)'),=201)=0A+=09eval(@src,=20b,=20(@filename=20||=20'(erb)'),=200)=0A= =20=20=20=20=20=20=20}.call=0A=20=20=20=20=20else=0A-=20=20=20=20=20=20= eval(@src,=20b,=20(@filename=20||=20'(erb)'),=201)=0A+=20=20=20=20=20=20= eval(@src,=20b,=20(@filename=20||=20'(erb)'),=200)=0A=20=20=20=20=20end=0A= =20=20=20end=0A=20=0A@@=20-762,7=20+796,11=20@@=0A=20=20=20#=20=20=20= erb.def_method(MyClass,=20'render(arg1,=20arg2)',=20filename)=0A=20=20=20= #=20=20=20print=20MyClass.new.render('foo',=20123)=0A=20=20=20def=20= def_method(mod,=20methodname,=20fname=3D'(ERB)')=0A-=20=20=20=20= mod.module_eval("def=20#{methodname}\n"=20+=20self.src=20+=20"\nend\n",=20= fname,=200)=0A+=20=20=20=20src=20=3D=20self.src=0A+=20=20=20=20= magic_comment=20=3D=20"#coding:#{@enc}\n"=0A+=20=20=20=20mod.module_eval=20= do=0A+=20=20=20=20=20=20eval(magic_comment=20+=20"def=20#{methodname}\n"=20= +=20src=20+=20"\nend\n",=20binding,=20fname,=20-2)=0A+=20=20=20=20end=0A=20= =20=20end=0A=20=0A=20=20=20#=20Create=20unnamed=20module,=20define=20= _methodname_=20as=20instance=20method=20of=20it,=20and=20return=20it.=0A= @@=20-839,7=20+877,9=20@@=0A=20=20=20=20=20#=20=09= Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide=0A=20= =20=20=20=20#=0A=20=20=20=20=20def=20url_encode(s)=0A-=20=20=20=20=20=20= s.to_s.gsub(/[^a-zA-Z0-9_\-.]/n){=20sprintf("%%%02X",=20= $&.unpack("C")[0])=20}=0A+=20=20=20=20=20=20= s.to_s.dup.force_encoding("ASCII-8BIT").gsub(/[^a-zA-Z0-9_\-.]/n)=20{=0A= +=20=20=20=20=20=20=20=20sprintf("%%%02X",=20$&.unpack("C")[0])=0A+=20=20= =20=20=20=20}=0A=20=20=20=20=20end=0A=20=20=20=20=20alias=20u=20= url_encode=0A=20=20=20=20=20module_function=20:u=0A= --Apple-Mail-2--938525577 Content-Disposition: attachment; filename=1_9.diff Content-Type: application/octet-stream; x-unix-mode=0644; name="1_9.diff" Content-Transfer-Encoding: 7bit Index: erb.rb =================================================================== --- erb.rb (revision 21220) +++ erb.rb (working copy) @@ -258,7 +258,7 @@ # Returns revision information for the erb.rb module. def self.version - "erb.rb [2.0.4 #{ERB::Revision.split[1]}]" + "erb.rb [2.1.0 #{ERB::Revision.split[1]}]" end end @@ -272,11 +272,13 @@ end attr_reader :value alias :to_s :value + + def empty? + @value.empty? + end end class Scanner # :nodoc: - SplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/ - @scanner_map = {} def self.regist_scanner(klass, trim_mode, percent) @scanner_map[[trim_mode, percent]] = klass @@ -301,8 +303,6 @@ end class TrimScanner < Scanner # :nodoc: - TrimSplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>\n)|(%>)|(\n)/ - def initialize(src, trim_mode, percent) super @trim_mode = trim_mode @@ -326,9 +326,7 @@ percent_line(line, &block) end else - @src.each_line do |line| - @scan_line.call(line, &block) - end + @scan_line.call(@src, &block) end nil end @@ -347,57 +345,66 @@ end def scan_line(line) - line.split(SplitRegexp).each do |token| - next if token.empty? - yield(token) + line.scan(/(.*?)(<%%|%%>|<%=|<%#|<%|%>|\n|\z)/m) do |tokens| + tokens.each do |token| + next if token.empty? + yield(token) + end end end def trim_line1(line) - line.split(TrimSplitRegexp).each do |token| - next if token.empty? - if token == "%>\n" - yield('%>') - yield(:cr) - break - end - yield(token) + line.scan(/(.*?)(<%%|%%>|<%=|<%#|<%|%>\n|%>|\n|\z)/m) do |tokens| + tokens.each do |token| + next if token.empty? + if token == "%>\n" + yield('%>') + yield(:cr) + else + yield(token) + end + end end end def trim_line2(line) head = nil - line.split(TrimSplitRegexp).each do |token| - next if token.empty? - head = token unless head - if token == "%>\n" - yield('%>') - if is_erb_stag?(head) - yield(:cr) - else - yield("\n") - end - break - end - yield(token) + line.scan(/(.*?)(<%%|%%>|<%=|<%#|<%|%>\n|%>|\n|\z)/m) do |tokens| + tokens.each do |token| + next if token.empty? + head = token unless head + if token == "%>\n" + yield('%>') + if is_erb_stag?(head) + yield(:cr) + else + yield("\n") + end + head = nil + else + yield(token) + head = nil if token == "\n" + end + end end end - ExplicitTrimRegexp = /(^[ \t]*<%-)|(-%>\n?\z)|(<%-)|(-%>)|(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/ def explicit_trim_line(line) - line.split(ExplicitTrimRegexp).each do |token| - next if token.empty? - if @stag.nil? && /[ \t]*<%-/ =~ token - yield('<%') - elsif @stag && /-%>\n/ =~ token - yield('%>') - yield(:cr) - elsif @stag && token == '-%>' - yield('%>') - else - yield(token) - end - end + line.scan(/(.*?)(^[ \t]*<%\-|<%\-|<%%|%%>|<%=|<%#|<%|-%>\n|-%>|%>|\z)/m) do |tokens| + tokens.each do |token| + next if token.empty? + if @stag.nil? && /[ \t]*<%-/ =~ token + yield('<%') + elsif @stag && token == "-%>\n" + yield('%>') + yield(:cr) + elsif @stag && token == '-%>' + yield('%>') + else + yield(token) + end + end + end end ERB_STAG = %w(<%= <%# <%) @@ -410,11 +417,11 @@ class SimpleScanner < Scanner # :nodoc: def scan - @src.each_line do |line| - line.split(SplitRegexp).each do |token| - next if token.empty? - yield(token) - end + @src.scan(/(.*?)(<%%|%%>|<%=|<%#|<%|%>|\n|\z)/m) do |tokens| + tokens.each do |token| + next if token.empty? + yield(token) + end end end end @@ -425,75 +432,69 @@ require 'strscan' class SimpleScanner2 < Scanner # :nodoc: def scan - stag_reg = /(.*?)(<%%|<%=|<%#|<%|\n|\z)/ - etag_reg = /(.*?)(%%>|%>|\n|\z)/ + stag_reg = /(.*?)(<%%|<%=|<%#|<%|\z)/m + etag_reg = /(.*?)(%%>|%>|\z)/m scanner = StringScanner.new(@src) while ! scanner.eos? scanner.scan(@stag ? etag_reg : stag_reg) - text = scanner[1] - elem = scanner[2] - yield(text) unless text.empty? - yield(elem) unless elem.empty? + yield(scanner[1]) + yield(scanner[2]) end end end Scanner.regist_scanner(SimpleScanner2, nil, false) class PercentScanner < Scanner # :nodoc: - def scan - new_line = true - stag_reg = /(.*?)(<%%|<%=|<%#|<%|\n|\z)/ - etag_reg = /(.*?)(%%>|%>|\n|\z)/ + def scan(&blk) + stag_reg = /(.*?)(^%%|^%|<%%|<%=|<%#|<%|\z)/m + etag_reg = /(.*?)(%%>|%>|\z)/m scanner = StringScanner.new(@src) while ! scanner.eos? - if new_line && @stag.nil? - if scanner.scan(/%%/) - yield('%') - new_line = false - next - elsif scanner.scan(/%/) - yield(PercentLine.new(scanner.scan(/.*?(\n|\z)/).chomp)) - next - end - end scanner.scan(@stag ? etag_reg : stag_reg) - text = scanner[1] + yield(scanner[1]) + elem = scanner[2] - yield(text) unless text.empty? - yield(elem) unless elem.empty? - new_line = (elem == "\n") + if elem == '%%' + yield('%') + inline_scan(scanner.scan(/.*?(\n|\z)/), &blk) + elsif elem == '%' + yield(PercentLine.new(scanner.scan(/.*?(\n|\z)/).chomp)) + else + yield(elem) + end end end + + def inline_scan(line) + stag_reg = /(.*?)(<%%|<%=|<%#|<%|\z)/m + etag_reg = /(.*?)(%%>|%>|\z)/m + scanner = StringScanner.new(line) + while ! scanner.eos? + scanner.scan(@stag ? etag_reg : stag_reg) + yield(scanner[1]) + yield(scanner[2]) + end + end end Scanner.regist_scanner(PercentScanner, nil, true) class ExplicitScanner < Scanner # :nodoc: def scan - new_line = true - stag_reg = /(.*?)(<%%|<%=|<%#|<%-|<%|\n|\z)/ - etag_reg = /(.*?)(%%>|-%>|%>|\n|\z)/ + stag_reg = /(.*?)(^[ \t]*<%-|<%%|<%=|<%#|<%-|<%|\z)/m + etag_reg = /(.*?)(%%>|-%>|%>|\z)/m scanner = StringScanner.new(@src) while ! scanner.eos? - if new_line && @stag.nil? && scanner.scan(/[ \t]*<%-/) - yield('<%') - new_line = false - next - end scanner.scan(@stag ? etag_reg : stag_reg) - text = scanner[1] + yield(scanner[1]) + elem = scanner[2] - new_line = (elem == "\n") - yield(text) unless text.empty? - if elem == '-%>' + if /[ \t]*<%-/ =~ elem + yield('<%') + elsif elem == '-%>' yield('%>') - if scanner.scan(/(\n|\z)/) - yield(:cr) - new_line = true - end - elsif elem == '<%-' - yield('<%') + yield(:cr) if scanner.scan(/(\n|\z)/) else - yield(elem) unless elem.empty? + yield(elem) end end end @@ -534,6 +535,15 @@ end end + def content_dump(s) + n = s.count("\n") + if n > 0 + s.dump + "\n" * n + else + s.dump + end + end + def compile(s) enc = s.encoding raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy? @@ -542,12 +552,14 @@ out = Buffer.new(self, enc) content = '' - scanner = make_scanner(s) + scanner = make_scanner(s) scanner.scan do |token| + next if token.nil? + next if token == '' if scanner.stag.nil? case token when PercentLine - out.push("#{@put_cmd} #{content.dump}") if content.size > 0 + out.push("#{@put_cmd} #{content_dump(content)}") if content.size > 0 content = '' out.push(token.to_s) out.cr @@ -555,12 +567,11 @@ out.cr when '<%', '<%=', '<%#' scanner.stag = token - out.push("#{@put_cmd} #{content.dump}") if content.size > 0 + out.push("#{@put_cmd} #{content_dump(content)}") if content.size > 0 content = '' when "\n" content << "\n" - out.push("#{@put_cmd} #{content.dump}") - out.cr + out.push("#{@put_cmd} #{content_dump(content)}") content = '' when '<%%' content << '<%' @@ -582,8 +593,7 @@ when '<%=' out.push("#{@insert_cmd}((#{content}).to_s)") when '<%#' - # content = content.force_encoding(@enc) - # out.push("# #{content.dump}") + # out.push("# #{content_dump(content)}") end scanner.stag = nil content = '' @@ -594,7 +604,7 @@ end end end - out.push("#{@put_cmd} #{content.dump}") if content.size > 0 + out.push("#{@put_cmd} #{content_dump(content)}") if content.size > 0 out.close return out.script, enc end @@ -769,17 +779,23 @@ # def result(b=TOPLEVEL_BINDING) if @safe_level - th = Thread.start { + proc { $SAFE = @safe_level eval(@src, b, (@filename || '(erb)'), 0) - } - return th.value + }.call else - return eval(@src, b, (@filename || '(erb)'), 0) + eval(@src, b, (@filename || '(erb)'), 0) 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)') src = self.src magic_comment = "#coding:#{@enc}\n" mod.module_eval do @@ -787,15 +803,38 @@ end 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 @@ -851,15 +890,45 @@ #-- # 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) - if erb.kind_of? String - fname = erb - File.open(fname) {|f| erb = ERB.new(f.read) } - erb.def_method(self, methodname, fname) + # define _methodname_ as instance method of current module, using ERB object or eRuby file + def def_erb_method(methodname, erb_or_fname) + if erb_or_fname.kind_of? String + 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 --Apple-Mail-2--938525577--