liquid 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,9 +1,13 @@
1
+ * Ruby 1.9.1 bugfixes
2
+
3
+ * Fix LiquidView for Rails 2.2. Fix local assigns for all versions of Rails
4
+
1
5
  * Fixed gem install rake task
2
6
  * Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
3
7
 
4
8
  * Added If with or / and expressions
5
9
 
6
- * Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
10
+ * Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
7
11
 
8
12
  * Added more tags to standard library
9
13
 
@@ -22,17 +26,17 @@
22
26
  * Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
23
27
 
24
28
  * Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond]
25
-
26
- {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
27
-
28
29
 
29
- * Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]
30
+ {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
31
+
32
+
33
+ * Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]
30
34
 
31
35
  class ProductDrop < Liquid::Drop
32
36
  def top_sales
33
37
  Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
34
38
  end
35
- end
39
+ end
36
40
  t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
37
41
  t.render('product' => ProductDrop.new )
38
42
 
data/Rakefile CHANGED
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
3
  require 'rake'
4
- require 'hoe'
4
+ require 'rake/testtask'
5
+ require 'rake/gempackagetask'
5
6
 
6
- PKG_VERSION = "2.0.0"
7
- PKG_NAME = "liquid"
8
- PKG_DESC = "A secure non evaling end user template engine with aesthetic markup."
7
+ task :default => 'test'
9
8
 
10
9
  Rake::TestTask.new(:test) do |t|
11
10
  t.libs << "lib"
@@ -14,11 +13,27 @@ Rake::TestTask.new(:test) do |t|
14
13
  t.verbose = false
15
14
  end
16
15
 
17
- Hoe.new(PKG_NAME, PKG_VERSION) do |p|
18
- p.rubyforge_name = PKG_NAME
19
- p.summary = PKG_DESC
20
- p.description = PKG_DESC
21
- p.author = "Tobias Luetke"
22
- p.email = "tobi@leetsoft.com"
23
- p.url = "http://www.liquidmarkup.org"
24
- end
16
+ gemspec = eval(File.read('liquid.gemspec'))
17
+ Rake::GemPackageTask.new(gemspec) do |pkg|
18
+ pkg.gem_spec = gemspec
19
+ end
20
+
21
+ namespace :profile do
22
+
23
+
24
+ task :default => [:run]
25
+
26
+ desc "Run the liquid profile/perforamce coverage"
27
+ task :run do
28
+
29
+ ruby "performance/shopify.rb"
30
+
31
+ end
32
+
33
+ desc "Run KCacheGrind"
34
+ task :grind => :run do
35
+ system "kcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
36
+ end
37
+ end
38
+
39
+
@@ -5,32 +5,43 @@
5
5
  #
6
6
  # ActionView::Base::register_template_handler :liquid, LiquidView
7
7
  class LiquidView
8
+ PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
9
+ _response url _request _cookies variables_added _flash params _headers request cookies
10
+ ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
11
+ PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
12
+ @helpers @assigns_added @template @_render_stack @template_format @assigns )
13
+
14
+ def self.call(template)
15
+ "LiquidView.new(self).render(template, local_assigns)"
16
+ end
8
17
 
9
- def initialize(action_view)
10
- @action_view = action_view
18
+ def initialize(view)
19
+ @view = view
11
20
  end
12
21
 
13
-
14
- def render(template, local_assigns_for_rails_less_than_2_1_0 = nil)
15
- @action_view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
16
- assigns = @action_view.assigns.dup
22
+ def render(template, local_assigns = nil)
23
+ @view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
17
24
 
18
- # template is a Template object in Rails >=2.1.0, a source string previously.
19
- if template.respond_to? :source
20
- source = template.source
21
- local_assigns = template.locals
25
+ # Rails 2.2 Template has source, but not locals
26
+ if template.respond_to?(:source) && !template.respond_to?(:locals)
27
+ assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
28
+ hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
29
+ hash
30
+ end
22
31
  else
23
- source = template
24
- local_assigns = local_assigns_for_rails_less_than_2_1_0
32
+ assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
25
33
  end
26
-
27
- if content_for_layout = @action_view.instance_variable_get("@content_for_layout")
34
+
35
+ source = template.respond_to?(:source) ? template.source : template
36
+ local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
37
+
38
+ if content_for_layout = @view.instance_variable_get("@content_for_layout")
28
39
  assigns['content_for_layout'] = content_for_layout
29
40
  end
30
- assigns.merge!(local_assigns)
41
+ assigns.merge!(local_assigns.stringify_keys)
31
42
 
32
43
  liquid = Liquid::Template.parse(source)
33
- liquid.render(assigns, :filters => [@action_view.controller.master_helper_module], :registers => {:action_view => @action_view, :controller => @action_view.controller})
44
+ liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
34
45
  end
35
46
 
36
47
  def compilable?
data/lib/liquid.rb CHANGED
@@ -29,11 +29,11 @@ module Liquid
29
29
  TagStart = /\{\%/
30
30
  TagEnd = /\%\}/
31
31
  VariableSignature = /\(?[\w\-\.\[\]]\)?/
32
- VariableSegment = /[\w\-]\??/
32
+ VariableSegment = /[\w\-]/
33
33
  VariableStart = /\{\{/
34
34
  VariableEnd = /\}\}/
35
35
  VariableIncompleteEnd = /\}\}?/
36
- QuotedString = /"[^"]+"|'[^']+'/
36
+ QuotedString = /"[^"]*"|'[^']*'/
37
37
  QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
38
38
  StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
39
39
  FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
@@ -44,7 +44,7 @@ module Liquid
44
44
  AnyStartingTag = /\{\{|\{\%/
45
45
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
46
46
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
47
- VariableParser = /\[[^\]]+\]|#{VariableSegment}+/
47
+ VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
48
48
  end
49
49
 
50
50
  require 'liquid/drop'
data/lib/liquid/block.rb CHANGED
@@ -1,19 +1,23 @@
1
1
  module Liquid
2
-
2
+
3
3
  class Block < Tag
4
+ IsTag = /^#{TagStart}/
5
+ IsVariable = /^#{VariableStart}/
6
+ FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
7
+ ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/
4
8
 
5
9
  def parse(tokens)
6
10
  @nodelist ||= []
7
11
  @nodelist.clear
8
12
 
9
- while token = tokens.shift
13
+ while token = tokens.shift
10
14
 
11
15
  case token
12
- when /^#{TagStart}/
13
- if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
16
+ when IsTag
17
+ if token =~ FullToken
14
18
 
15
19
  # if we found the proper block delimitor just end parsing here and let the outer block
16
- # proceed
20
+ # proceed
17
21
  if block_delimiter == $1
18
22
  end_tag
19
23
  return
@@ -23,33 +27,33 @@ module Liquid
23
27
  if tag = Template.tags[$1]
24
28
  @nodelist << tag.new($1, $2, tokens)
25
29
  else
26
- # this tag is not registered with the system
30
+ # this tag is not registered with the system
27
31
  # pass it to the current block for special handling or error reporting
28
32
  unknown_tag($1, $2, tokens)
29
- end
33
+ end
30
34
  else
31
35
  raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
32
36
  end
33
- when /^#{VariableStart}/
37
+ when IsVariable
34
38
  @nodelist << create_variable(token)
35
39
  when ''
36
40
  # pass
37
41
  else
38
42
  @nodelist << token
39
43
  end
40
- end
41
-
42
- # Make sure that its ok to end parsing in the current block.
43
- # Effectively this method will throw and exception unless the current block is
44
- # of type Document
44
+ end
45
+
46
+ # Make sure that its ok to end parsing in the current block.
47
+ # Effectively this method will throw and exception unless the current block is
48
+ # of type Document
45
49
  assert_missing_delimitation!
46
- end
47
-
48
- def end_tag
50
+ end
51
+
52
+ def end_tag
49
53
  end
50
54
 
51
55
  def unknown_tag(tag, params, tokens)
52
- case tag
56
+ case tag
53
57
  when 'else'
54
58
  raise SyntaxError, "#{block_name} tag does not expect else tag"
55
59
  when 'end'
@@ -61,14 +65,14 @@ module Liquid
61
65
 
62
66
  def block_delimiter
63
67
  "end#{block_name}"
64
- end
68
+ end
65
69
 
66
70
  def block_name
67
71
  @tag_name
68
72
  end
69
73
 
70
74
  def create_variable(token)
71
- token.scan(/^#{VariableStart}(.*)#{VariableEnd}$/) do |content|
75
+ token.scan(ContentOfVariable) do |content|
72
76
  return Variable.new(content.first)
73
77
  end
74
78
  raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
@@ -77,7 +81,7 @@ module Liquid
77
81
  def render(context)
78
82
  render_all(@nodelist, context)
79
83
  end
80
-
84
+
81
85
  protected
82
86
 
83
87
  def assert_missing_delimitation!
@@ -86,12 +90,12 @@ module Liquid
86
90
 
87
91
  def render_all(list, context)
88
92
  list.collect do |token|
89
- begin
93
+ begin
90
94
  token.respond_to?(:render) ? token.render(context) : token
91
- rescue Exception => e
95
+ rescue Exception => e
92
96
  context.handle_error(e)
93
- end
94
- end
97
+ end
98
+ end
95
99
  end
96
- end
97
- end
100
+ end
101
+ end
@@ -3,7 +3,7 @@ module Liquid
3
3
  #
4
4
  # Example:
5
5
  #
6
- # c = Condition.new('1', '==', '1')
6
+ # c = Condition.new('1', '==', '1')
7
7
  # c.evaluate #=> true
8
8
  #
9
9
  class Condition #:nodoc:
@@ -15,35 +15,35 @@ module Liquid
15
15
  '>' => :>,
16
16
  '>=' => :>=,
17
17
  '<=' => :<=,
18
- 'contains' => lambda { |cond, left, right| left.include?(right) },
18
+ 'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
19
19
  }
20
-
20
+
21
21
  def self.operators
22
22
  @@operators
23
23
  end
24
24
 
25
25
  attr_reader :attachment
26
26
  attr_accessor :left, :operator, :right
27
-
27
+
28
28
  def initialize(left = nil, operator = nil, right = nil)
29
29
  @left, @operator, @right = left, operator, right
30
30
  @child_relation = nil
31
31
  @child_condition = nil
32
32
  end
33
-
33
+
34
34
  def evaluate(context = Context.new)
35
- result = interpret_condition(left, right, operator, context)
36
-
35
+ result = interpret_condition(left, right, operator, context)
36
+
37
37
  case @child_relation
38
- when :or
38
+ when :or
39
39
  result || @child_condition.evaluate(context)
40
- when :and
40
+ when :and
41
41
  result && @child_condition.evaluate(context)
42
42
  else
43
43
  result
44
- end
45
- end
46
-
44
+ end
45
+ end
46
+
47
47
  def or(condition)
48
48
  @child_relation, @child_condition = :or, condition
49
49
  end
@@ -51,25 +51,25 @@ module Liquid
51
51
  def and(condition)
52
52
  @child_relation, @child_condition = :and, condition
53
53
  end
54
-
54
+
55
55
  def attach(attachment)
56
56
  @attachment = attachment
57
57
  end
58
-
58
+
59
59
  def else?
60
60
  false
61
- end
62
-
61
+ end
62
+
63
63
  def inspect
64
64
  "#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
65
65
  end
66
-
66
+
67
67
  private
68
-
68
+
69
69
  def equal_variables(left, right)
70
70
  if left.is_a?(Symbol)
71
71
  if right.respond_to?(left)
72
- return right.send(left.to_s)
72
+ return right.send(left.to_s)
73
73
  else
74
74
  return nil
75
75
  end
@@ -77,47 +77,44 @@ module Liquid
77
77
 
78
78
  if right.is_a?(Symbol)
79
79
  if left.respond_to?(right)
80
- return left.send(right.to_s)
80
+ return left.send(right.to_s)
81
81
  else
82
82
  return nil
83
83
  end
84
84
  end
85
85
 
86
- left == right
87
- end
86
+ left == right
87
+ end
88
88
 
89
89
  def interpret_condition(left, right, op, context)
90
-
91
- # If the operator is empty this means that the decision statement is just
92
- # a single variable. We can just poll this variable from the context and
90
+ # If the operator is empty this means that the decision statement is just
91
+ # a single variable. We can just poll this variable from the context and
93
92
  # return this as the result.
94
- return context[left] if op == nil
93
+ return context[left] if op == nil
95
94
 
96
95
  left, right = context[left], context[right]
97
-
98
96
 
99
97
  operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
100
98
 
101
99
  if operation.respond_to?(:call)
102
100
  operation.call(self, left, right)
103
- elsif left.respond_to?(operation) and right.respond_to?(operation)
101
+ elsif left.respond_to?(operation) and right.respond_to?(operation)
104
102
  left.send(operation, right)
105
103
  else
106
104
  nil
107
105
  end
108
- end
109
- end
106
+ end
107
+ end
110
108
 
111
109
 
112
110
  class ElseCondition < Condition
113
-
114
- def else?
111
+ def else?
115
112
  true
116
113
  end
117
-
114
+
118
115
  def evaluate(context)
119
116
  true
120
117
  end
121
118
  end
122
119
 
123
- end
120
+ end
@@ -13,14 +13,15 @@ module Liquid
13
13
  #
14
14
  # context['bob'] #=> nil class Context
15
15
  class Context
16
- attr_reader :scopes
17
- attr_reader :errors, :registers
16
+ attr_reader :scopes, :errors, :registers, :environments
18
17
 
19
- def initialize(assigns = {}, registers = {}, rethrow_errors = false)
20
- @scopes = [(assigns || {})]
21
- @registers = registers
22
- @errors = []
18
+ def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
19
+ @environments = [environments].flatten
20
+ @scopes = [(outer_scope || {})]
21
+ @registers = registers
22
+ @errors = []
23
23
  @rethrow_errors = rethrow_errors
24
+ squash_instance_assigns_with_environments
24
25
  end
25
26
 
26
27
  def strainer
@@ -61,9 +62,9 @@ module Liquid
61
62
  end
62
63
 
63
64
  # push new local scope on the stack. use <tt>Context#stack</tt> instead
64
- def push
65
+ def push(new_scope={})
65
66
  raise StackLevelError, "Nesting too deep" if @scopes.length > 100
66
- @scopes.unshift({})
67
+ @scopes.unshift(new_scope)
67
68
  end
68
69
 
69
70
  # merge a hash of variables in the current local scope
@@ -86,9 +87,9 @@ module Liquid
86
87
  # end
87
88
  # context['var] #=> nil
88
89
  #
89
- def stack(&block)
90
+ def stack(new_scope={},&block)
90
91
  result = nil
91
- push
92
+ push(new_scope)
92
93
  begin
93
94
  result = yield
94
95
  ensure
@@ -96,6 +97,10 @@ module Liquid
96
97
  end
97
98
  result
98
99
  end
100
+
101
+ def clear_instance_assigns
102
+ @scopes[0] = {}
103
+ end
99
104
 
100
105
  # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
101
106
  def []=(key, value)
@@ -133,9 +138,6 @@ module Liquid
133
138
  :blank?
134
139
  when 'empty'
135
140
  :empty?
136
- # filtered variables
137
- when SpacelessFilter
138
- filtered_variable(key)
139
141
  # Single quoted strings
140
142
  when /^'(.*)'$/
141
143
  $1.to_s
@@ -159,16 +161,21 @@ module Liquid
159
161
  # fetches an object starting at the local scope and then moving up
160
162
  # the hierachy
161
163
  def find_variable(key)
162
- @scopes.each do |scope|
163
- if scope.has_key?(key)
164
- variable = scope[key]
165
- variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
166
- variable = variable.to_liquid
167
- variable.context = self if variable.respond_to?(:context=)
168
- return variable
164
+ scope = @scopes.find { |s| s.has_key?(key) }
165
+ if scope.nil?
166
+ @environments.each do |e|
167
+ if variable = lookup_and_evaluate(e, key)
168
+ scope = e
169
+ break
170
+ end
169
171
  end
170
172
  end
171
- nil
173
+ scope ||= @environments.last || @scopes.last
174
+ variable ||= lookup_and_evaluate(scope, key)
175
+
176
+ variable = variable.to_liquid
177
+ variable.context = self if variable.respond_to?(:context=)
178
+ return variable
172
179
  end
173
180
 
174
181
  # resolves namespaced queries gracefully.
@@ -200,8 +207,7 @@ module Liquid
200
207
  (object.respond_to?(:fetch) and part.is_a?(Integer)))
201
208
 
202
209
  # if its a proc we will replace the entry with the proc
203
- res = object[part]
204
- res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=)
210
+ res = lookup_and_evaluate(object, part)
205
211
  object = res.to_liquid
206
212
 
207
213
  # Some special cases. If the part wasn't in square brackets and
@@ -225,8 +231,24 @@ module Liquid
225
231
  object
226
232
  end
227
233
 
228
- def filtered_variable(markup)
229
- Variable.new(markup).render(self)
234
+ def lookup_and_evaluate(obj, key)
235
+ if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
236
+ obj[key] = value.call(self)
237
+ else
238
+ value
239
+ end
230
240
  end
241
+
242
+ def squash_instance_assigns_with_environments
243
+ @scopes.last.each_key do |k|
244
+ @environments.each do |env|
245
+ if env.has_key?(k)
246
+ scopes.last[k] = lookup_and_evaluate(env, k)
247
+ break
248
+ end
249
+ end
250
+ end
251
+ end
252
+
231
253
  end
232
254
  end