liquid 3.0.6 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +89 -58
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/lib/liquid.rb +7 -6
  5. data/lib/liquid/block.rb +31 -124
  6. data/lib/liquid/block_body.rb +54 -57
  7. data/lib/liquid/condition.rb +23 -22
  8. data/lib/liquid/context.rb +50 -42
  9. data/lib/liquid/document.rb +19 -9
  10. data/lib/liquid/drop.rb +12 -13
  11. data/lib/liquid/errors.rb +16 -17
  12. data/lib/liquid/expression.rb +15 -3
  13. data/lib/liquid/extensions.rb +7 -7
  14. data/lib/liquid/file_system.rb +3 -3
  15. data/lib/liquid/forloop_drop.rb +42 -0
  16. data/lib/liquid/i18n.rb +5 -5
  17. data/lib/liquid/interrupts.rb +1 -2
  18. data/lib/liquid/lexer.rb +6 -4
  19. data/lib/liquid/locales/en.yml +3 -1
  20. data/lib/liquid/parse_context.rb +37 -0
  21. data/lib/liquid/parser_switching.rb +4 -4
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/profiler/hooks.rb +7 -7
  24. data/lib/liquid/range_lookup.rb +16 -1
  25. data/lib/liquid/resource_limits.rb +23 -0
  26. data/lib/liquid/standardfilters.rb +101 -56
  27. data/lib/liquid/strainer.rb +4 -5
  28. data/lib/liquid/tablerowloop_drop.rb +62 -0
  29. data/lib/liquid/tag.rb +9 -8
  30. data/lib/liquid/tags/assign.rb +5 -4
  31. data/lib/liquid/tags/break.rb +0 -3
  32. data/lib/liquid/tags/capture.rb +1 -1
  33. data/lib/liquid/tags/case.rb +19 -12
  34. data/lib/liquid/tags/comment.rb +2 -2
  35. data/lib/liquid/tags/cycle.rb +6 -6
  36. data/lib/liquid/tags/decrement.rb +1 -4
  37. data/lib/liquid/tags/for.rb +93 -75
  38. data/lib/liquid/tags/if.rb +49 -44
  39. data/lib/liquid/tags/ifchanged.rb +0 -2
  40. data/lib/liquid/tags/include.rb +60 -52
  41. data/lib/liquid/tags/raw.rb +26 -4
  42. data/lib/liquid/tags/table_row.rb +12 -30
  43. data/lib/liquid/tags/unless.rb +3 -4
  44. data/lib/liquid/template.rb +23 -50
  45. data/lib/liquid/tokenizer.rb +31 -0
  46. data/lib/liquid/utils.rb +48 -8
  47. data/lib/liquid/variable.rb +46 -45
  48. data/lib/liquid/variable_lookup.rb +3 -3
  49. data/lib/liquid/version.rb +1 -1
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +64 -45
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/output_test.rb +26 -27
  58. data/test/integration/parsing_quirks_test.rb +15 -13
  59. data/test/integration/render_profiling_test.rb +20 -20
  60. data/test/integration/security_test.rb +5 -7
  61. data/test/integration/standard_filter_test.rb +119 -37
  62. data/test/integration/tags/break_tag_test.rb +1 -2
  63. data/test/integration/tags/continue_tag_test.rb +0 -1
  64. data/test/integration/tags/for_tag_test.rb +133 -98
  65. data/test/integration/tags/if_else_tag_test.rb +75 -77
  66. data/test/integration/tags/include_tag_test.rb +23 -30
  67. data/test/integration/tags/increment_tag_test.rb +10 -11
  68. data/test/integration/tags/raw_tag_test.rb +7 -1
  69. data/test/integration/tags/standard_tag_test.rb +121 -122
  70. data/test/integration/tags/statements_test.rb +3 -5
  71. data/test/integration/tags/table_row_test.rb +20 -19
  72. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  73. data/test/integration/template_test.rb +91 -45
  74. data/test/integration/variable_test.rb +23 -13
  75. data/test/test_helper.rb +33 -5
  76. data/test/unit/block_unit_test.rb +6 -5
  77. data/test/unit/condition_unit_test.rb +82 -77
  78. data/test/unit/context_unit_test.rb +48 -57
  79. data/test/unit/file_system_unit_test.rb +3 -3
  80. data/test/unit/i18n_unit_test.rb +2 -2
  81. data/test/unit/lexer_unit_test.rb +11 -8
  82. data/test/unit/parser_unit_test.rb +2 -2
  83. data/test/unit/regexp_unit_test.rb +1 -1
  84. data/test/unit/strainer_unit_test.rb +13 -2
  85. data/test/unit/tag_unit_test.rb +7 -2
  86. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  87. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  88. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  89. data/test/unit/template_unit_test.rb +6 -5
  90. data/test/unit/tokenizer_unit_test.rb +24 -7
  91. data/test/unit/variable_unit_test.rb +60 -43
  92. metadata +44 -41
  93. data/lib/liquid/module_ex.rb +0 -62
  94. data/lib/liquid/token.rb +0 -18
  95. data/test/unit/module_ex_unit_test.rb +0 -87
@@ -2,7 +2,6 @@ require 'cgi'
2
2
  require 'bigdecimal'
3
3
 
4
4
  module Liquid
5
-
6
5
  module StandardFilters
7
6
  HTML_ESCAPE = {
8
7
  '&'.freeze => '&'.freeze,
@@ -34,7 +33,7 @@ module Liquid
34
33
  end
35
34
 
36
35
  def escape(input)
37
- CGI.escapeHTML(input).untaint rescue input
36
+ CGI.escapeHTML(input).untaint unless input.nil?
38
37
  end
39
38
  alias_method :h, :escape
40
39
 
@@ -43,12 +42,16 @@ module Liquid
43
42
  end
44
43
 
45
44
  def url_encode(input)
46
- CGI.escape(input) rescue input
45
+ CGI.escape(input) unless input.nil?
46
+ end
47
+
48
+ def url_decode(input)
49
+ CGI.unescape(input) unless input.nil?
47
50
  end
48
51
 
49
- def slice(input, offset, length=nil)
50
- offset = Integer(offset)
51
- length = length ? Integer(length) : 1
52
+ def slice(input, offset, length = nil)
53
+ offset = Utils.to_integer(offset)
54
+ length = length ? Utils.to_integer(length) : 1
52
55
 
53
56
  if input.is_a?(Array)
54
57
  input.slice(offset, length) || []
@@ -59,16 +62,19 @@ module Liquid
59
62
 
60
63
  # Truncate a string down to x characters
61
64
  def truncate(input, length = 50, truncate_string = "...".freeze)
62
- if input.nil? then return end
63
- l = length.to_i - truncate_string.length
65
+ return if input.nil?
66
+ input_str = input.to_s
67
+ length = Utils.to_integer(length)
68
+ l = length - truncate_string.length
64
69
  l = 0 if l < 0
65
- input.length > length.to_i ? input[0...l] + truncate_string : input
70
+ input_str.length > length ? input_str[0...l] + truncate_string : input_str
66
71
  end
67
72
 
68
73
  def truncatewords(input, words = 15, truncate_string = "...".freeze)
69
- if input.nil? then return end
74
+ return if input.nil?
70
75
  wordlist = input.to_s.split
71
- l = words.to_i - 1
76
+ words = Utils.to_integer(words)
77
+ l = words - 1
72
78
  l = 0 if l < 0
73
79
  wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
74
80
  end
@@ -115,10 +121,28 @@ module Liquid
115
121
  ary = InputIterator.new(input)
116
122
  if property.nil?
117
123
  ary.sort
124
+ elsif ary.empty? # The next two cases assume a non-empty array.
125
+ []
126
+ elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
127
+ ary.sort { |a, b| a[property] <=> b[property] }
128
+ elsif ary.first.respond_to?(property)
129
+ ary.sort { |a, b| a.send(property) <=> b.send(property) }
130
+ end
131
+ end
132
+
133
+ # Sort elements of an array ignoring case if strings
134
+ # provide optional property with which to sort an array of hashes or drops
135
+ def sort_natural(input, property = nil)
136
+ ary = InputIterator.new(input)
137
+
138
+ if property.nil?
139
+ ary.sort { |a, b| a.casecmp(b) }
140
+ elsif ary.empty? # The next two cases assume a non-empty array.
141
+ []
118
142
  elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
119
- ary.sort {|a,b| a[property] <=> b[property] }
143
+ ary.sort { |a, b| a[property].casecmp(b[property]) }
120
144
  elsif ary.first.respond_to?(property)
121
- ary.sort {|a,b| a.send(property) <=> b.send(property) }
145
+ ary.sort { |a, b| a.send(property).casecmp(b.send(property)) }
122
146
  end
123
147
  end
124
148
 
@@ -126,10 +150,13 @@ module Liquid
126
150
  # provide optional property with which to determine uniqueness
127
151
  def uniq(input, property = nil)
128
152
  ary = InputIterator.new(input)
153
+
129
154
  if property.nil?
130
- input.uniq
131
- elsif input.first.respond_to?(:[])
132
- input.uniq{ |a| a[property] }
155
+ ary.uniq
156
+ elsif ary.empty? # The next two cases assume a non-empty array.
157
+ []
158
+ elsif ary.first.respond_to?(:[])
159
+ ary.uniq{ |a| a[property] }
133
160
  end
134
161
  end
135
162
 
@@ -147,29 +174,46 @@ module Liquid
147
174
  if property == "to_liquid".freeze
148
175
  e
149
176
  elsif e.respond_to?(:[])
150
- e[property]
177
+ r = e[property]
178
+ r.is_a?(Proc) ? r.call : r
151
179
  end
152
180
  end
153
181
  end
154
182
 
183
+ # Remove nils within an array
184
+ # provide optional property with which to check for nil
185
+ def compact(input, property = nil)
186
+ ary = InputIterator.new(input)
187
+
188
+ if property.nil?
189
+ ary.compact
190
+ elsif ary.empty? # The next two cases assume a non-empty array.
191
+ []
192
+ elsif ary.first.respond_to?(:[])
193
+ ary.reject{ |a| a[property].nil? }
194
+ elsif ary.first.respond_to?(property)
195
+ ary.reject { |a| a.send(property).nil? }
196
+ end
197
+ end
198
+
155
199
  # Replace occurrences of a string with another
156
200
  def replace(input, string, replacement = ''.freeze)
157
- input.to_s.gsub(string, replacement.to_s)
201
+ input.to_s.gsub(string.to_s, replacement.to_s)
158
202
  end
159
203
 
160
204
  # Replace the first occurrences of a string with another
161
205
  def replace_first(input, string, replacement = ''.freeze)
162
- input.to_s.sub(string, replacement.to_s)
206
+ input.to_s.sub(string.to_s, replacement.to_s)
163
207
  end
164
208
 
165
209
  # remove a substring
166
210
  def remove(input, string)
167
- input.to_s.gsub(string, ''.freeze)
211
+ input.to_s.gsub(string.to_s, ''.freeze)
168
212
  end
169
213
 
170
214
  # remove the first occurrences of a substring
171
215
  def remove_first(input, string)
172
- input.to_s.sub(string, ''.freeze)
216
+ input.to_s.sub(string.to_s, ''.freeze)
173
217
  end
174
218
 
175
219
  # add one string to another
@@ -177,6 +221,10 @@ module Liquid
177
221
  input.to_s + string.to_s
178
222
  end
179
223
 
224
+ def concat(input, array)
225
+ InputIterator.new(input).concat(array)
226
+ end
227
+
180
228
  # prepend a string to another
181
229
  def prepend(input, string)
182
230
  string.to_s + input.to_s
@@ -221,7 +269,7 @@ module Liquid
221
269
  def date(input, format)
222
270
  return input if format.to_s.empty?
223
271
 
224
- return input unless date = to_date(input)
272
+ return input unless date = Utils.to_date(input)
225
273
 
226
274
  date.strftime(format.to_s)
227
275
  end
@@ -262,25 +310,35 @@ module Liquid
262
310
  # division
263
311
  def divided_by(input, operand)
264
312
  apply_operation(input, operand, :/)
313
+ rescue ::ZeroDivisionError => e
314
+ raise Liquid::ZeroDivisionError, e.message
265
315
  end
266
316
 
267
317
  def modulo(input, operand)
268
318
  apply_operation(input, operand, :%)
319
+ rescue ::ZeroDivisionError => e
320
+ raise Liquid::ZeroDivisionError, e.message
269
321
  end
270
322
 
271
323
  def round(input, n = 0)
272
- result = to_number(input).round(to_number(n))
324
+ result = Utils.to_number(input).round(Utils.to_number(n))
273
325
  result = result.to_f if result.is_a?(BigDecimal)
274
326
  result = result.to_i if n == 0
275
327
  result
328
+ rescue ::FloatDomainError => e
329
+ raise Liquid::FloatDomainError, e.message
276
330
  end
277
331
 
278
332
  def ceil(input)
279
- to_number(input).ceil.to_i
333
+ Utils.to_number(input).ceil.to_i
334
+ rescue ::FloatDomainError => e
335
+ raise Liquid::FloatDomainError, e.message
280
336
  end
281
337
 
282
338
  def floor(input)
283
- to_number(input).floor.to_i
339
+ Utils.to_number(input).floor.to_i
340
+ rescue ::FloatDomainError => e
341
+ raise Liquid::FloatDomainError, e.message
284
342
  end
285
343
 
286
344
  def default(input, default_value = "".freeze)
@@ -290,38 +348,8 @@ module Liquid
290
348
 
291
349
  private
292
350
 
293
- def to_number(obj)
294
- case obj
295
- when Float
296
- BigDecimal.new(obj.to_s)
297
- when Numeric
298
- obj
299
- when String
300
- (obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
301
- else
302
- 0
303
- end
304
- end
305
-
306
- def to_date(obj)
307
- return obj if obj.respond_to?(:strftime)
308
-
309
- case obj
310
- when 'now'.freeze, 'today'.freeze
311
- Time.now
312
- when /\A\d+\z/, Integer
313
- Time.at(obj.to_i)
314
- when String
315
- Time.parse(obj)
316
- else
317
- nil
318
- end
319
- rescue ::ArgumentError
320
- nil
321
- end
322
-
323
351
  def apply_operation(input, operand, operation)
324
- result = to_number(input).send(operation, to_number(operand))
352
+ result = Utils.to_number(input).send(operation, Utils.to_number(operand))
325
353
  result.is_a?(BigDecimal) ? result.to_f : result
326
354
  end
327
355
 
@@ -344,10 +372,27 @@ module Liquid
344
372
  to_a.join(glue)
345
373
  end
346
374
 
375
+ def concat(args)
376
+ to_a.concat args
377
+ end
378
+
347
379
  def reverse
348
380
  reverse_each.to_a
349
381
  end
350
382
 
383
+ def uniq(&block)
384
+ to_a.uniq(&block)
385
+ end
386
+
387
+ def compact
388
+ to_a.compact
389
+ end
390
+
391
+ def empty?
392
+ @input.each { return false }
393
+ true
394
+ end
395
+
351
396
  def each
352
397
  @input.each do |e|
353
398
  yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
@@ -1,7 +1,6 @@
1
1
  require 'set'
2
2
 
3
3
  module Liquid
4
-
5
4
  # Strainer is the parent class for the filters system.
6
5
  # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
7
6
  #
@@ -22,14 +21,14 @@ module Liquid
22
21
  @context = context
23
22
  end
24
23
 
25
- def self.filter_methods
26
- @filter_methods
24
+ class << self
25
+ attr_reader :filter_methods
27
26
  end
28
27
 
29
28
  def self.add_filter(filter)
30
29
  raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
31
30
  unless self.class.include?(filter)
32
- self.send(:include, filter)
31
+ send(:include, filter)
33
32
  @filter_methods.merge(filter.public_instance_methods.map(&:to_s))
34
33
  end
35
34
  end
@@ -53,7 +52,7 @@ module Liquid
53
52
  args.first
54
53
  end
55
54
  rescue ::ArgumentError => e
56
- raise Liquid::ArgumentError.new(e.message)
55
+ raise Liquid::ArgumentError, e.message, e.backtrace
57
56
  end
58
57
  end
59
58
  end
@@ -0,0 +1,62 @@
1
+ module Liquid
2
+ class TablerowloopDrop < Drop
3
+ def initialize(length, cols)
4
+ @length = length
5
+ @row = 1
6
+ @col = 1
7
+ @cols = cols
8
+ @index = 0
9
+ end
10
+
11
+ attr_reader :length, :col, :row
12
+
13
+ def index
14
+ @index + 1
15
+ end
16
+
17
+ def index0
18
+ @index
19
+ end
20
+
21
+ def col0
22
+ @col - 1
23
+ end
24
+
25
+ def rindex
26
+ @length - @index
27
+ end
28
+
29
+ def rindex0
30
+ @length - @index - 1
31
+ end
32
+
33
+ def first
34
+ @index == 0
35
+ end
36
+
37
+ def last
38
+ @index == @length - 1
39
+ end
40
+
41
+ def col_first
42
+ @col == 1
43
+ end
44
+
45
+ def col_last
46
+ @col == @cols
47
+ end
48
+
49
+ protected
50
+
51
+ def increment!
52
+ @index += 1
53
+
54
+ if @col == @cols
55
+ @col = 1
56
+ @row += 1
57
+ else
58
+ @col += 1
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,26 +1,27 @@
1
1
  module Liquid
2
2
  class Tag
3
- attr_accessor :options, :line_number
4
- attr_reader :nodelist, :warnings
3
+ attr_reader :nodelist, :tag_name, :line_number, :parse_context
4
+ alias_method :options, :parse_context
5
5
  include ParserSwitching
6
6
 
7
7
  class << self
8
- def parse(tag_name, markup, tokens, options)
8
+ def parse(tag_name, markup, tokenizer, options)
9
9
  tag = new(tag_name, markup, options)
10
- tag.parse(tokens)
10
+ tag.parse(tokenizer)
11
11
  tag
12
12
  end
13
13
 
14
14
  private :new
15
15
  end
16
16
 
17
- def initialize(tag_name, markup, options)
17
+ def initialize(tag_name, markup, parse_context)
18
18
  @tag_name = tag_name
19
19
  @markup = markup
20
- @options = options
20
+ @parse_context = parse_context
21
+ @line_number = parse_context.line_number
21
22
  end
22
23
 
23
- def parse(tokens)
24
+ def parse(_tokens)
24
25
  end
25
26
 
26
27
  def raw
@@ -31,7 +32,7 @@ module Liquid
31
32
  self.class.name.downcase
32
33
  end
33
34
 
34
- def render(context)
35
+ def render(_context)
35
36
  ''.freeze
36
37
  end
37
38
 
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Assign sets a variable in your template.
4
3
  #
5
4
  # {% assign foo = 'monkey' %}
@@ -15,8 +14,7 @@ module Liquid
15
14
  super
16
15
  if markup =~ Syntax
17
16
  @to = $1
18
- @from = Variable.new($2,options)
19
- @from.line_number = line_number
17
+ @from = Variable.new($2, options)
20
18
  else
21
19
  raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
22
20
  end
@@ -25,7 +23,10 @@ module Liquid
25
23
  def render(context)
26
24
  val = @from.render(context)
27
25
  context.scopes.last[@to] = val
28
- context.increment_used_resources(:assign_score_current, val)
26
+
27
+ inc = val.instance_of?(String) || val.instance_of?(Array) || val.instance_of?(Hash) ? val.length : 1
28
+ context.resource_limits.assign_score += inc
29
+
29
30
  ''.freeze
30
31
  end
31
32