liquid 3.0.6 → 4.0.0.rc1

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 (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