diff options
author | makoto kuwata <kwa@kuwata-lab.com> | 2007-09-18 08:02:49 +0000 |
---|---|---|
committer | makoto kuwata <kwa@kuwata-lab.com> | 2007-09-18 08:02:49 +0000 |
commit | 7cb00b66a50550da046928b5bf0059e4f00da88a (patch) | |
tree | 0b004bf50ee52e8469d6bb7219207ac20c3f6ee9 | |
parent | 5cdc7a11e351e6322fb1a4eb01532e49cdb7c804 (diff) | |
download | erubis-7cb00b66a50550da046928b5bf0059e4f00da88a.tar.gz |
- [change] add Erubis::EMPTY_BINDING
- [bugfix] use Erubis::EMPTY_BINDING instead of TOPLEVEL_BINDING in Evaluator#evaluate()
- [enhance] user's guide: new section 'evaluate(context) vs. result(binding)' added
- [update] CHANGES
-rw-r--r-- | CHANGES | 44 | ||||
-rw-r--r-- | ChangeLog.txt | 6 | ||||
-rw-r--r-- | doc/Rookbook.yaml | 12 | ||||
-rw-r--r-- | doc/users-guide.html | 93 | ||||
-rw-r--r-- | doc/users-guide.txt | 90 | ||||
-rw-r--r-- | lib/erubis/evaluator.rb | 4 | ||||
-rw-r--r-- | test/test-erubis.rb | 15 |
7 files changed, 256 insertions, 8 deletions
@@ -2,6 +2,50 @@ # $Release:$ # $Copyright$ +# +- release: 2.4.1 + date: + enhancements: + + - | + Add new section 'evaluate(context) v.s. result(binding)' to user's guide. + This section describes why Erubis::Eruby#evaluate(context) is recommended + rather than Erubis::Eruby#result(binding). + User's Guide : Other Topics : evaluate(context) v.s. result(binding) + http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-context-vs-binding + + bugfix: + + - | + When using Erubis::Eruby#evaluate(), changing local variables in + templates have affected to variables accessible with TOPLEVEL_BINDING. + It means that if you change variables in templates, it is possible to + change variables in main program. + This was a bug and is now fixed not to affect to variables in main + program. + + ex. template.rhtml + -------------------- + <% for x in @items %> + item = <%= x %> + <% end %> + -------------------- + + ex. main-program.rb + -------------------- + require 'erubis' + x = 10 + items = ['foo', 'bar', 'baz'] + eruby = Erubis::Eruby.new(File.read('template.rhtml')) + s = eruby.evaluate(:items=>items) + print s + $stderr.puts "*** debug: x=#{x.inspect}" #=> x="baz" (2.4.0) + #=> x=10 (2.4.1) + -------------------- + + - | + PercentLineEnhancer was very slow. Now performance problem is solved. + # - release: 2.4.0 diff --git a/ChangeLog.txt b/ChangeLog.txt index 4dc8609..3b87da8 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -2,6 +2,12 @@ .?lastupdate: $Date$ .?version: $Rev$ +: Rev.94 (2007-09-18) + .- [change] add Erubis::EMPTY_BINDING + .- [bugfix] use Erubis::EMPTY_BINDING instead of TOPLEVEL_BINDING in Evaluator#evaluate() + .- [enhance] user's guide: new section 'evaluate(context) vs. result(binding)' added + .- [update] CHANGES + : Rev.93 (2007-09-10) .- [bugfix] PercentLineEnhancer#add_text() was very slow (now very fast) diff --git a/doc/Rookbook.yaml b/doc/Rookbook.yaml index 5ae672c..c031a35 100644 --- a/doc/Rookbook.yaml +++ b/doc/Rookbook.yaml @@ -45,10 +45,11 @@ recipes: ingreds: [ $(u).txt ] byprods: [ $(u).toc.html ] method*: | - rm_rf "guide.d" - mkdir_p "guide.d" - sys "retrieve -d guide.d #{@ingred}" - for filename in Dir.glob("guide.d/*.*_filter") + dir = "../test/data/$(u)" + mkdir_p dir + rm_rf "#{dir}/*" + sys "retrieve -d #{dir} #{@ingred}" + for filename in Dir.glob("#{dir}/*.*_filter") content = File.read(filename) name = (filename =~ /\.(\w+)_filter$/) && $1 case name @@ -67,9 +68,6 @@ recipes: end File.unlink(filename) end - mkdir_p "../test/data/$(u)/" - rm_f "../test/data/$(u)/*" - cp_p "guide.d/*", "../test/data/$(u)/" sys "kwaser -t $(tagfile) -T #{@ingred} > #{@byprod}" sys "kwaser -t $(tagfile) #{@ingred} > #{@product}" rm "#{@byprod}" diff --git a/doc/users-guide.html b/doc/users-guide.html index a8706ce..62bfc50 100644 --- a/doc/users-guide.html +++ b/doc/users-guide.html @@ -151,6 +151,8 @@ It has the following features. </li> <li><a href="#topics">Other Topics</a> <ul> + <li><a href="#topics-context-vs-binding">evaluate(context) v.s. result(binding)</a> + </li> <li><a href="#topics-fasteruby">Class Erubis::FastEruby</a> </li> <li><a href="#topics-syntax">Syntax Checking</a> @@ -661,6 +663,9 @@ output</div> <li>ccc</li> </ul> </pre> +<p>It is recommended to use 'Erubis::Eruby#evaluate(context)' rather than 'Erubis::Eruby#result(binding())' because the latter has some problems. +See <a href="#topics-context-vs-binding">evaluate(context) v.s. result(binding)</a> section for details. +</p> <br> @@ -2487,6 +2492,94 @@ because tag helpers generate different html code when form parameter has errors <a name="topics"></a> <h2 class="section1">Other Topics</h2> +<a name="topics-context-vs-binding"></a> +<h3 class="section2">evaluate(context) v.s. result(binding)</h3> +<p>It is recommended to use 'Erubis::Eruby#evaluate(context)' instead of 'Erubis::Eruby#result(binding)' because Ruby's Binding object has some problems. +</p> +<ul type="disc"> +<li>It is not able to specify variables to use. + Using binding() method, all of local variables are passed to templates. +</li> +<li>Changing local variables in templates may affect to varialbes in main program. + If you assign '10' to local variable 'x' in templates, it may change variable 'x' in main program unintendedly. +</li> +</ul> +<p>The following example shows that assignment of some values into variable 'x' in templates affect to local variable 'x' in main program unintendedly. +</p> +<a name="template1.rhtml"></a> +<div class="program_caption"> +template1.rhtml (intended to be passed 'items' from main program)</div> +<pre class="program"><% for <strong>x</strong> in <strong>items</strong> %> +item = <%= x %> +<% end %> +** debug: local variables=<%= local_variables().inspect() %> +</pre> +<a name="main_program1.rb"></a> +<div class="program_caption"> +main_program1.rb (intended to pass 'items' to template)</div> +<pre class="program">require 'erubis' +eruby = Erubis::Eruby.new(File.read('template1.rhtml')) +items = ['foo', 'bar', 'baz'] +x = 1 +## local variable 'x' and 'eruby' are passed to template as well as 'items'! +print <strong>eruby.result(binding())</strong> +## local variable 'x' is changed unintendedly because it is changed in template! +puts "** debug: x=#{x.inspect}" #=> "baz" +</pre> +<a name="main_program1.result"></a> +<div class="terminal_caption"> +Result:</div> +<pre class="terminal">$ ruby main_program1.rb +item = foo +item = bar +item = baz +** debug: local variables=["eruby", "items", "x", "_buf"] +** debug: x="baz" +</pre> +<p>This problem is caused because Ruby's Binding class is poor to use in template engine. +Binding class should support the following features. +</p> +<pre class="program">b = Binding.new # create empty Binding object +b['x'] = 1 # set local variables using binding object +</pre> +<p>But the above features are not implemented in Ruby. +</p> +<p>A pragmatic solution is to use 'Erubis::Eruby#evaluate(context)' instead of 'Erubis::Eruby#result(binding)'. +'evaluate(context)' uses Erubis::Context object and instance variables instead of Binding object and local variables. +</p> +<a name="template2.rhtml"></a> +<div class="program_caption"> +template2.rhtml (intended to be passed '@items' from main program)</div> +<pre class="program"><% for <strong>x</strong> in <strong>@items</strong> %> +item = <%= x %> +<% end %> +** debug: local variables=<%= local_variables().inspect() %> +</pre> +<a name="main_program2.rb"></a> +<div class="program_caption"> +main_program2.rb (intended to pass '@items' to template)</div> +<pre class="program">require 'erubis' +eruby = Erubis::Eruby.new(File.read('template2.rhtml')) +items = ['foo', 'bar', 'baz'] +x = 1 +## only 'items' are passed to template +print <strong>eruby.evaluate(:items=>items)</strong> +## local variable 'x' is not changed! +puts "** debug: x=#{x.inspect}" #=> 1 +</pre> +<a name="main_program2.result"></a> +<div class="terminal_caption"> +Result:</div> +<pre class="terminal">$ ruby main_program2.rb +item = foo +item = bar +item = baz +** debug: local variables=["x", "_buf"] +** debug: x=1 +</pre> +<br> + + <a name="topics-fasteruby"></a> <h3 class="section2">Class Erubis::FastEruby</h3> <p>[experimental] diff --git a/doc/users-guide.txt b/doc/users-guide.txt index 5dcd267..6415bfb 100644 --- a/doc/users-guide.txt +++ b/doc/users-guide.txt @@ -539,6 +539,10 @@ $ ruby example6.rb .==================== +It is recommended to use 'Erubis::Eruby#evaluate(context)' rather than 'Erubis::Eruby#result(binding())' because the latter has some problems. +See {{<evaluate(context) v.s. result(binding)|#topics-context-vs-binding>}} section for details. + + .$$ Context Data File | tut-datafile @@ -2589,6 +2593,92 @@ If it is nil (default), Erubis prints converted Ruby code into log file only whe .$ Other Topics | topics +.$$ evaluate(context) v.s. result(binding) | topics-context-vs-binding + +It is recommended to use 'Erubis::Eruby#evaluate(context)' instead of 'Erubis::Eruby#result(binding)' because Ruby's Binding object has some problems. + +.* It is not able to specify variables to use. + Using binding() method, all of local variables are passed to templates. +.* Changing local variables in templates may affect to varialbes in main program. + If you assign '10' to local variable 'x' in templates, it may change variable 'x' in main program unintendedly. + +The following example shows that assignment of some values into variable 'x' in templates affect to local variable 'x' in main program unintendedly. + +.? template1.rhtml (intended to be passed 'items' from main program) +.-------------------- template1.rhtml +<% for {{*x*}} in {{*items*}} %> +item = <%= x %> +<% end %> +** debug: local variables=<%= local_variables().inspect() %> +.-------------------- + +.? main_program1.rb (intended to pass 'items' to template) +.-------------------- main_program1.rb +require 'erubis' +eruby = Erubis::Eruby.new(File.read('template1.rhtml')) +items = ['foo', 'bar', 'baz'] +x = 1 +## local variable 'x' and 'eruby' are passed to template as well as 'items'! +print {{*eruby.result(binding())*}} +## local variable 'x' is changed unintendedly because it is changed in template! +puts "** debug: x=#{x.inspect}" #=> "baz" +.-------------------- + +.? Result: +.==================== main_program1.result +$ ruby main_program1.rb +item = foo +item = bar +item = baz +** debug: local variables=["eruby", "items", "x", "_buf"] +** debug: x="baz" +.==================== + +This problem is caused because Ruby's Binding class is poor to use in template engine. +Binding class should support the following features. + +.-------------------- +b = Binding.new # create empty Binding object +b['x'] = 1 # set local variables using binding object +.-------------------- + +But the above features are not implemented in Ruby. + +A pragmatic solution is to use 'Erubis::Eruby#evaluate(context)' instead of 'Erubis::Eruby#result(binding)'. +'evaluate(context)' uses Erubis::Context object and instance variables instead of Binding object and local variables. + +.? template2.rhtml (intended to be passed '@items' from main program) +.-------------------- template2.rhtml +<% for {{*x*}} in {{*@items*}} %> +item = <%= x %> +<% end %> +** debug: local variables=<%= local_variables().inspect() %> +.-------------------- + +.? main_program2.rb (intended to pass '@items' to template) +.-------------------- main_program2.rb +require 'erubis' +eruby = Erubis::Eruby.new(File.read('template2.rhtml')) +items = ['foo', 'bar', 'baz'] +x = 1 +## only 'items' are passed to template +print {{*eruby.evaluate(:items=>items)*}} +## local variable 'x' is not changed! +puts "** debug: x=#{x.inspect}" #=> 1 +.-------------------- + +.? Result: +.==================== main_program2.result +$ ruby main_program2.rb +item = foo +item = bar +item = baz +** debug: local variables=["x", "_buf"] +** debug: x=1 +.==================== + + + .$$ Class Erubis::FastEruby | topics-fasteruby [experimental] diff --git a/lib/erubis/evaluator.rb b/lib/erubis/evaluator.rb index f946af1..8a76bc4 100644 --- a/lib/erubis/evaluator.rb +++ b/lib/erubis/evaluator.rb @@ -10,6 +10,8 @@ require 'erubis/context' module Erubis + EMPTY_BINDING = binding() + ## ## evaluate code @@ -64,7 +66,7 @@ module Erubis def evaluate(context=Context.new) context = Context.new(context) if context.is_a?(Hash) #return context.instance_eval(@src, @filename || '(erubis)') - @_proc ||= eval("proc { #{@src} }", TOPLEVEL_BINDING, @filename || '(erubis)') + @_proc ||= eval("proc { #{@src} }", Erubis::EMPTY_BINDING, @filename || '(erubis)') return context.instance_eval(&@_proc) end diff --git a/test/test-erubis.rb b/test/test-erubis.rb index 1642a71..4505b48 100644 --- a/test/test-erubis.rb +++ b/test/test-erubis.rb @@ -192,8 +192,23 @@ END assert_nil(eruby.instance_variable_get('@_proc')) end + #def test_toplevel_binding + # s = "locals = <%= local_variables().inspect %>\n<% x = 50 %>\n" + # eruby = Erubis::Eruby.new(s) + # _x = eval 'x', TOPLEVEL_BINDING + # _y = eval 'y', TOPLEVEL_BINDING + # actual = eruby.evaluate(:x=>_x, :y=>_y) + # _x = eval 'x', TOPLEVEL_BINDING + # _y = eval 'y', TOPLEVEL_BINDING + # puts "*** actual=#{actual.inspect}, x=#{_x.inspect}, y=#{_y.inspect}" + #end + end +x = 10 +y = 20 + + __END__ - name: basic1 input: &basic1_input| |