liquid 1.9.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Manifest.txt +0 -31
  2. data/Rakefile +1 -1
  3. data/lib/extras/liquid_view.rb +15 -2
  4. data/lib/liquid.rb +15 -15
  5. data/lib/liquid/block.rb +1 -2
  6. data/lib/liquid/context.rb +89 -99
  7. data/lib/liquid/drop.rb +6 -4
  8. data/lib/liquid/errors.rb +1 -0
  9. data/lib/liquid/standardfilters.rb +56 -11
  10. data/lib/liquid/strainer.rb +1 -1
  11. data/lib/liquid/tags/assign.rb +1 -1
  12. data/lib/liquid/tags/case.rb +2 -2
  13. data/lib/liquid/tags/cycle.rb +3 -4
  14. data/lib/liquid/tags/for.rb +53 -35
  15. data/lib/liquid/tags/if.rb +3 -3
  16. data/lib/liquid/template.rb +8 -7
  17. data/lib/liquid/variable.rb +10 -11
  18. metadata +5 -35
  19. data/example/server/example_servlet.rb +0 -37
  20. data/example/server/liquid_servlet.rb +0 -28
  21. data/example/server/server.rb +0 -12
  22. data/example/server/templates/index.liquid +0 -6
  23. data/example/server/templates/products.liquid +0 -45
  24. data/test/block_test.rb +0 -58
  25. data/test/condition_test.rb +0 -109
  26. data/test/context_test.rb +0 -418
  27. data/test/drop_test.rb +0 -141
  28. data/test/error_handling_test.rb +0 -78
  29. data/test/extra/breakpoint.rb +0 -547
  30. data/test/extra/caller.rb +0 -80
  31. data/test/file_system_test.rb +0 -30
  32. data/test/filter_test.rb +0 -98
  33. data/test/helper.rb +0 -20
  34. data/test/html_tag_test.rb +0 -31
  35. data/test/if_else_test.rb +0 -127
  36. data/test/include_tag_test.rb +0 -114
  37. data/test/module_ex_test.rb +0 -89
  38. data/test/output_test.rb +0 -121
  39. data/test/parsing_quirks_test.rb +0 -29
  40. data/test/regexp_test.rb +0 -40
  41. data/test/security_test.rb +0 -41
  42. data/test/standard_filter_test.rb +0 -126
  43. data/test/standard_tag_test.rb +0 -383
  44. data/test/statements_test.rb +0 -137
  45. data/test/strainer_test.rb +0 -16
  46. data/test/template_test.rb +0 -26
  47. data/test/unless_else_test.rb +0 -27
  48. data/test/variable_test.rb +0 -135
@@ -4,11 +4,6 @@ MIT-LICENSE
4
4
  Manifest.txt
5
5
  README.txt
6
6
  Rakefile
7
- example/server/example_servlet.rb
8
- example/server/liquid_servlet.rb
9
- example/server/server.rb
10
- example/server/templates/index.liquid
11
- example/server/templates/products.liquid
12
7
  init.rb
13
8
  lib/extras/liquid_view.rb
14
9
  lib/liquid.rb
@@ -37,29 +32,3 @@ lib/liquid/tags/include.rb
37
32
  lib/liquid/tags/unless.rb
38
33
  lib/liquid/template.rb
39
34
  lib/liquid/variable.rb
40
- test/block_test.rb
41
- test/condition_test.rb
42
- test/context_test.rb
43
- test/drop_test.rb
44
- test/error_handling_test.rb
45
- test/extra/breakpoint.rb
46
- test/extra/caller.rb
47
- test/file_system_test.rb
48
- test/filter_test.rb
49
- test/helper.rb
50
- test/html_tag_test.rb
51
- test/if_else_test.rb
52
- test/include_tag_test.rb
53
- test/module_ex_test.rb
54
- test/output_test.rb
55
- test/parsing_quirks_test.rb
56
- test/regexp_test.rb
57
- test/security_test.rb
58
- test/standard_filter_test.rb
59
- test/standard_tag_test.rb
60
- test/statements_test.rb
61
- test/strainer_test.rb
62
- test/template_test.rb
63
- test/test_helper.rb
64
- test/unless_else_test.rb
65
- test/variable_test.rb
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
3
3
  require 'rake'
4
4
  require 'hoe'
5
5
 
6
- PKG_VERSION = "1.9.0"
6
+ PKG_VERSION = "2.0.0"
7
7
  PKG_NAME = "liquid"
8
8
  PKG_DESC = "A secure non evaling end user template engine with aesthetic markup."
9
9
 
@@ -11,17 +11,30 @@ class LiquidView
11
11
  end
12
12
 
13
13
 
14
- def render(template, local_assigns)
14
+ def render(template, local_assigns_for_rails_less_than_2_1_0 = nil)
15
15
  @action_view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
16
16
  assigns = @action_view.assigns.dup
17
17
 
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
22
+ else
23
+ source = template
24
+ local_assigns = local_assigns_for_rails_less_than_2_1_0
25
+ end
26
+
18
27
  if content_for_layout = @action_view.instance_variable_get("@content_for_layout")
19
28
  assigns['content_for_layout'] = content_for_layout
20
29
  end
21
30
  assigns.merge!(local_assigns)
22
31
 
23
- liquid = Liquid::Template.parse(template)
32
+ liquid = Liquid::Template.parse(source)
24
33
  liquid.render(assigns, :filters => [@action_view.controller.master_helper_module], :registers => {:action_view => @action_view, :controller => @action_view.controller})
25
34
  end
26
35
 
36
+ def compilable?
37
+ false
38
+ end
39
+
27
40
  end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2005 Tobias Luetke
2
- #
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
5
5
  # "Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@
7
7
  # distribute, sublicense, and/or sell copies of the Software, and to
8
8
  # permit persons to whom the Software is furnished to do so, subject to
9
9
  # the following conditions:
10
- #
10
+ #
11
11
  # The above copyright notice and this permission notice shall be
12
12
  # included in all copies or substantial portions of the Software.
13
- #
13
+ #
14
14
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
15
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
16
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -22,7 +22,7 @@
22
22
  $LOAD_PATH.unshift(File.dirname(__FILE__))
23
23
 
24
24
  module Liquid
25
- FilterSperator = /\|/
25
+ FilterSeparator = /\|/
26
26
  ArgumentSeparator = ','
27
27
  FilterArgumentSeparator = ':'
28
28
  VariableAttributeSeparator = '.'
@@ -33,9 +33,17 @@ module Liquid
33
33
  VariableStart = /\{\{/
34
34
  VariableEnd = /\}\}/
35
35
  VariableIncompleteEnd = /\}\}?/
36
- QuotedFragment = /"[^"]+"|'[^']+'|[^\s,|]+/
36
+ QuotedString = /"[^"]+"|'[^']+'/
37
+ QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
38
+ StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
39
+ FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
40
+ OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
41
+ SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
42
+ Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
37
43
  TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
38
- TemplateParser = /(#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd})/
44
+ AnyStartingTag = /\{\{|\{\%/
45
+ PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
46
+ TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
39
47
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+/
40
48
  end
41
49
 
@@ -55,14 +63,6 @@ require 'liquid/standardfilters'
55
63
  require 'liquid/condition'
56
64
  require 'liquid/module_ex'
57
65
 
58
- # Load all the tags of the standard library
66
+ # Load all the tags of the standard library
59
67
  #
60
68
  Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
@@ -90,8 +90,7 @@ module Liquid
90
90
  token.respond_to?(:render) ? token.render(context) : token
91
91
  rescue Exception => e
92
92
  context.handle_error(e)
93
- end
94
-
93
+ end
95
94
  end
96
95
  end
97
96
  end
@@ -1,13 +1,13 @@
1
1
  module Liquid
2
-
2
+
3
3
  # Context keeps the variable stack and resolves variables, as well as keywords
4
4
  #
5
5
  # context['variable'] = 'testing'
6
6
  # context['variable'] #=> 'testing'
7
7
  # context['true'] #=> true
8
8
  # context['10.2232'] #=> 10.2232
9
- #
10
- # context.stack do
9
+ #
10
+ # context.stack do
11
11
  # context['bob'] = 'bobsen'
12
12
  # end
13
13
  #
@@ -15,70 +15,73 @@ module Liquid
15
15
  class Context
16
16
  attr_reader :scopes
17
17
  attr_reader :errors, :registers
18
-
18
+
19
19
  def initialize(assigns = {}, registers = {}, rethrow_errors = false)
20
20
  @scopes = [(assigns || {})]
21
21
  @registers = registers
22
22
  @errors = []
23
23
  @rethrow_errors = rethrow_errors
24
- end
25
-
24
+ end
25
+
26
26
  def strainer
27
27
  @strainer ||= Strainer.create(self)
28
- end
29
-
30
- # adds filters to this context.
31
- # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
28
+ end
29
+
30
+ # adds filters to this context.
31
+ # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
32
32
  # for that
33
33
  def add_filters(filters)
34
34
  filters = [filters].flatten.compact
35
-
36
- filters.each do |f|
35
+
36
+ filters.each do |f|
37
37
  raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
38
38
  strainer.extend(f)
39
- end
39
+ end
40
40
  end
41
-
41
+
42
42
  def handle_error(e)
43
43
  errors.push(e)
44
44
  raise if @rethrow_errors
45
-
45
+
46
46
  case e
47
- when SyntaxError then "Liquid syntax error: #{e.message}"
48
- else "Liquid error: #{e.message}"
47
+ when SyntaxError
48
+ "Liquid syntax error: #{e.message}"
49
+ else
50
+ "Liquid error: #{e.message}"
49
51
  end
50
52
  end
51
-
52
-
53
+
54
+
53
55
  def invoke(method, *args)
54
56
  if strainer.respond_to?(method)
55
57
  strainer.__send__(method, *args)
56
58
  else
57
59
  args.first
58
- end
60
+ end
59
61
  end
60
62
 
61
63
  # push new local scope on the stack. use <tt>Context#stack</tt> instead
62
64
  def push
65
+ raise StackLevelError, "Nesting too deep" if @scopes.length > 100
63
66
  @scopes.unshift({})
64
67
  end
65
-
68
+
66
69
  # merge a hash of variables in the current local scope
67
70
  def merge(new_scopes)
68
71
  @scopes[0].merge!(new_scopes)
69
72
  end
70
-
73
+
71
74
  # pop from the stack. use <tt>Context#stack</tt> instead
72
75
  def pop
73
- raise ContextError if @scopes.size == 1
76
+ raise ContextError if @scopes.size == 1
74
77
  @scopes.shift
75
78
  end
76
-
79
+
77
80
  # pushes a new local scope on the stack, pops it at the end of the block
78
81
  #
79
82
  # Example:
80
83
  #
81
- # context.stack do
84
+ # context.stack do
82
85
  # context['var'] = 'hi'
83
86
  # end
84
87
  # context['var] #=> nil
@@ -88,33 +91,33 @@ module Liquid
88
91
  push
89
92
  begin
90
93
  result = yield
91
- ensure
94
+ ensure
92
95
  pop
93
96
  end
94
- result
97
+ result
95
98
  end
96
-
99
+
97
100
  # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
98
101
  def []=(key, value)
99
102
  @scopes[0][key] = value
100
103
  end
101
-
104
+
102
105
  def [](key)
103
106
  resolve(key)
104
107
  end
105
-
108
+
106
109
  def has_key?(key)
107
110
  resolve(key) != nil
108
111
  end
109
-
112
+
110
113
  private
111
-
112
- # Look up variable, either resolve directly after considering the name. We can directly handle
113
- # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
114
+
115
+ # Look up variable, either resolve directly after considering the name. We can directly handle
116
+ # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
114
117
  # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
115
118
  # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
116
119
  #
117
- # Example:
120
+ # Example:
118
121
  #
119
122
  # products == empty #=> products.empty?
120
123
  #
@@ -125,37 +128,40 @@ module Liquid
125
128
  when 'true'
126
129
  true
127
130
  when 'false'
128
- false
131
+ false
129
132
  when 'blank'
130
- :blank?
133
+ :blank?
131
134
  when 'empty'
132
135
  :empty?
136
+ # filtered variables
137
+ when SpacelessFilter
138
+ filtered_variable(key)
133
139
  # Single quoted strings
134
140
  when /^'(.*)'$/
135
141
  $1.to_s
136
142
  # Double quoted strings
137
143
  when /^"(.*)"$/
138
- $1.to_s
144
+ $1.to_s
139
145
  # Integer and floats
140
- when /^(\d+)$/
146
+ when /^(\d+)$/
141
147
  $1.to_i
142
148
  # Ranges
143
- when /^\((\S+)\.\.(\S+)\)$/
149
+ when /^\((\S+)\.\.(\S+)\)$/
144
150
  (resolve($1).to_i..resolve($2).to_i)
145
151
  # Floats
146
- when /^(\d[\d\.]+)$/
152
+ when /^(\d[\d\.]+)$/
147
153
  $1.to_f
148
154
  else
149
155
  variable(key)
150
- end
156
+ end
151
157
  end
152
-
153
- # fetches an object starting at the local scope and then moving up
154
- # the hierachy
158
+
159
+ # fetches an object starting at the local scope and then moving up
160
+ # the hierachy
155
161
  def find_variable(key)
156
- @scopes.each do |scope|
162
+ @scopes.each do |scope|
157
163
  if scope.has_key?(key)
158
- variable = scope[key]
164
+ variable = scope[key]
159
165
  variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
160
166
  variable = variable.to_liquid
161
167
  variable.context = self if variable.respond_to?(:context=)
@@ -166,77 +172,61 @@ module Liquid
166
172
  end
167
173
 
168
174
  # resolves namespaced queries gracefully.
169
- #
175
+ #
170
176
  # Example
171
- #
177
+ #
172
178
  # @context['hash'] = {"name" => 'tobi'}
173
179
  # assert_equal 'tobi', @context['hash.name']
174
- # assert_equal 'tobi', @context['hash[name]']
180
+ # assert_equal 'tobi', @context['hash["name"]']
175
181
  #
176
182
  def variable(markup)
177
183
  parts = markup.scan(VariableParser)
178
184
  square_bracketed = /^\[(.*)\]$/
179
-
185
+
180
186
  first_part = parts.shift
181
187
  if first_part =~ square_bracketed
182
188
  first_part = resolve($1)
183
189
  end
184
-
190
+
185
191
  if object = find_variable(first_part)
186
-
187
- parts.each do |part|
188
-
189
- # If object is a hash we look for the presence of the key and if its available
190
- # we return it
191
-
192
- if part =~ square_bracketed
193
- part = resolve($1)
194
-
195
- object[pos] = object[part].call(self) if object[part].is_a?(Proc) and object.respond_to?(:[]=)
196
- object = object[part].to_liquid
197
-
198
- else
199
192
 
200
- # Hash
201
- if object.respond_to?(:has_key?) and object.has_key?(part)
202
-
203
- # if its a proc we will replace the entry in the hash table with the proc
204
- res = object[part]
205
- res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=)
206
- object = res.to_liquid
207
-
208
- # Array
209
- elsif object.respond_to?(:fetch) and part =~ /^\d+$/
210
- pos = part.to_i
211
-
212
- object[pos] = object[pos].call(self) if object[pos].is_a?(Proc) and object.respond_to?(:[]=)
213
- object = object[pos].to_liquid
214
-
215
- # Some special cases. If no key with the same name was found we interpret following calls
216
- # as commands and call them on the current object
217
- elsif object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
218
-
219
- object = object.send(part.intern).to_liquid
220
-
221
- # No key was present with the desired value and it wasn't one of the directly supported
222
- # keywords either. The only thing we got left is to return nil
223
- else
224
- return nil
225
- end
193
+ parts.each do |part|
194
+ part = resolve($1) if part_resolved = (part =~ square_bracketed)
195
+
196
+ # If object is a hash- or array-like object we look for the
197
+ # presence of the key and if its available we return it
198
+ if object.respond_to?(:[]) and
199
+ ((object.respond_to?(:has_key?) and object.has_key?(part)) or
200
+ (object.respond_to?(:fetch) and part.is_a?(Integer)))
201
+
202
+ # 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?(:[]=)
205
+ object = res.to_liquid
206
+
207
+ # Some special cases. If the part wasn't in square brackets and
208
+ # no key with the same name was found we interpret following calls
209
+ # as commands and call them on the current object
210
+ elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
211
+
212
+ object = object.send(part.intern).to_liquid
213
+
214
+ # No key was present with the desired value and it wasn't one of the directly supported
215
+ # keywords either. The only thing we got left is to return nil
216
+ else
217
+ return nil
226
218
  end
227
-
228
- # If we are dealing with a drop here we have to
219
+
220
+ # If we are dealing with a drop here we have to
229
221
  object.context = self if object.respond_to?(:context=)
230
222
  end
231
223
  end
232
-
224
+
233
225
  object
234
- end
235
-
236
- private
226
+ end
237
227
 
238
- def execute_proc(proc)
239
- proc.call(self)
228
+ def filtered_variable(markup)
229
+ Variable.new(markup).render(self)
240
230
  end
241
231
  end
242
232
  end