liquid 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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