liquid 4.0.1 → 4.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55d4aa3aeeef9a99c5601c5c8bbaab62b0c909c9aa3df0ac181e01373a6ed069
4
- data.tar.gz: 1e3eddbb13e867eca4f90efd32fbbc2609e4235b77d005efaa93711f8e476c40
3
+ metadata.gz: e3124f91e43cab43cf6782b093c0387d9189bd951b911ab1ade84024e5b84bf3
4
+ data.tar.gz: 40c04d46b3a1a51a775832cabf161fc870bde43b72d2857a8a8cf736e1909a43
5
5
  SHA512:
6
- metadata.gz: 349a44c983e69443a0350d3aa7de2fffed15bf67356a6ce490583413d60dd926cc32907b1fca4d04ca075dd92e17434176f94552237ec5ae549477ccbbff4042
7
- data.tar.gz: 31d9c4fbe841ad2c3a6549e1e6d02f580b31a538186e83140bd297fb47caf23387795a48468886e82562f661231890cb42637ecbe369dc4583d560055e94ca77
6
+ metadata.gz: 71deb80c6d970684e424fb8e4ca0a817399dc0b6564a7308093158a94ace0e24cac5d8e08a8b015327937b8dd45be25532dd8d9e02de0c3677f08297a08638dd
7
+ data.tar.gz: 79490a2f0db69914e01c69fdcf135cc3541609cfca29017154d3c33325c57f682c3d90bbfa0bca1a7f3d82e4cbe4af5259a77cf42861c142921491e21949bddf
data/History.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 4.0.2 / 2019-03-08
4
+
5
+ ### Changed
6
+ * Add `where` filter (#1026) [Samuel Doiron]
7
+ * Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber]
8
+ * Improve `strip_html` performance (#1032) [printercu]
9
+
10
+ ### Fixed
11
+ * Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang]
12
+ * Validate the character encoding in url_decode (#1070) [Clayton Smith]
13
+
14
+ ## 4.0.1 / 2018-10-09
15
+
16
+ ### Changed
17
+ * Add benchmark group in Gemfile (#855) [Jerry Liu]
18
+ * Allow benchmarks to benchmark render by itself (#851) [Jerry Liu]
19
+ * Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith]
20
+ * Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith]
21
+ * Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield]
22
+ * Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith]
23
+ * Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith]
24
+ * Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith]
25
+ * Remove Spy Gem (#896) [Dylan Thacker-Smith]
26
+ * Add `collection_name` and `variable_name` reader to `For` block (#909)
27
+ * Symbols render as strings (#920) [Justin Li]
28
+ * Remove default value from Hash objects (#932) [Maxime Bedard]
29
+ * Remove one level of nesting (#944) [Dylan Thacker-Smith]
30
+ * Update Rubocop version (#952) [Justin Li]
31
+ * Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal]
32
+ * Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith]
33
+ * Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith]
34
+ * Add tests against Ruby 2.4 (#963) and 2.5 (#981)
35
+ * Replace RegExp literals with constants (#988) [Ashwin Maroli]
36
+ * Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli]
37
+ * Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith]
38
+ * Refactor and optimize rendering (#1005) [Christopher Aue]
39
+ * Add installation instruction (#1006) [Ben Gift]
40
+ * Remove Circle CI (#1010)
41
+ * Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO]
42
+ * Rename deprecated Rubocop name (#1027) [Justin Li]
43
+
44
+ ### Fixed
45
+ * Handle `join` filter on non String joiners (#857) [Richard Monette]
46
+ * Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861)
47
+ * Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal]
48
+ * Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz]
49
+ * Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan]
50
+
3
51
  ## 4.0.0 / 2016-12-14 / branch "4-0-stable"
4
52
 
5
53
  ### Changed
@@ -45,6 +45,7 @@ module Liquid
45
45
  end
46
46
 
47
47
  require "liquid/version"
48
+ require 'liquid/parse_tree_visitor'
48
49
  require 'liquid/lexer'
49
50
  require 'liquid/parser'
50
51
  require 'liquid/i18n'
@@ -29,7 +29,7 @@ module Liquid
29
29
  @@operators
30
30
  end
31
31
 
32
- attr_reader :attachment
32
+ attr_reader :attachment, :child_condition
33
33
  attr_accessor :left, :operator, :right
34
34
 
35
35
  def initialize(left = nil, operator = nil, right = nil)
@@ -83,7 +83,7 @@ module Liquid
83
83
 
84
84
  protected
85
85
 
86
- attr_reader :child_relation, :child_condition
86
+ attr_reader :child_relation
87
87
 
88
88
  private
89
89
 
@@ -128,6 +128,15 @@ module Liquid
128
128
  end
129
129
  end
130
130
  end
131
+
132
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
133
+ def children
134
+ [
135
+ @node.left, @node.right,
136
+ @node.child_condition, @node.attachment
137
+ ].compact
138
+ end
139
+ end
131
140
  end
132
141
 
133
142
  class ElseCondition < Condition
@@ -19,7 +19,7 @@ module Liquid
19
19
  'false'.freeze => false,
20
20
  'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
21
  'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
22
- }
22
+ }.freeze
23
23
 
24
24
  SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
25
25
  DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
@@ -26,7 +26,7 @@ module Liquid
26
26
  def interpolate(name, vars)
27
27
  name.gsub(/%\{(\w+)\}/) do
28
28
  # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
29
- "#{vars[$1.to_sym]}"
29
+ (vars[$1.to_sym]).to_s
30
30
  end
31
31
  end
32
32
 
@@ -12,7 +12,7 @@ module Liquid
12
12
  ')'.freeze => :close_round,
13
13
  '?'.freeze => :question,
14
14
  '-'.freeze => :dash
15
- }
15
+ }.freeze
16
16
  IDENTIFIER = /[a-zA-Z_][\w-]*\??/
17
17
  SINGLE_STRING_LITERAL = /'[^\']*'/
18
18
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class ParseTreeVisitor
5
+ def self.for(node, callbacks = Hash.new(proc {}))
6
+ if defined?(node.class::ParseTreeVisitor)
7
+ node.class::ParseTreeVisitor
8
+ else
9
+ self
10
+ end.new(node, callbacks)
11
+ end
12
+
13
+ def initialize(node, callbacks)
14
+ @node = node
15
+ @callbacks = callbacks
16
+ end
17
+
18
+ def add_callback_for(*classes, &block)
19
+ callback = block
20
+ callback = ->(node, _) { yield node } if block.arity.abs == 1
21
+ callback = ->(_, _) { yield } if block.arity.zero?
22
+ classes.each { |klass| @callbacks[klass] = callback }
23
+ self
24
+ end
25
+
26
+ def visit(context = nil)
27
+ children.map do |node|
28
+ item, new_context = @callbacks[node.class].call(node, context)
29
+ [
30
+ item,
31
+ ParseTreeVisitor.for(node, @callbacks).visit(new_context || context)
32
+ ]
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def children
39
+ @node.respond_to?(:nodelist) ? Array(@node.nodelist) : []
40
+ end
41
+ end
42
+ end
@@ -9,8 +9,14 @@ module Liquid
9
9
  '<'.freeze => '&lt;'.freeze,
10
10
  '"'.freeze => '&quot;'.freeze,
11
11
  "'".freeze => '&#39;'.freeze
12
- }
12
+ }.freeze
13
13
  HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
14
+ STRIP_HTML_BLOCKS = Regexp.union(
15
+ /<script.*?<\/script>/m,
16
+ /<!--.*?-->/m,
17
+ /<style.*?<\/style>/m
18
+ )
19
+ STRIP_HTML_TAGS = /<.*?>/m
14
20
 
15
21
  # Return the size of an array or of an string
16
22
  def size(input)
@@ -46,7 +52,12 @@ module Liquid
46
52
  end
47
53
 
48
54
  def url_decode(input)
49
- CGI.unescape(input.to_s) unless input.nil?
55
+ return if input.nil?
56
+
57
+ result = CGI.unescape(input.to_s)
58
+ raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
59
+
60
+ result
50
61
  end
51
62
 
52
63
  def slice(input, offset, length = nil)
@@ -103,7 +114,9 @@ module Liquid
103
114
 
104
115
  def strip_html(input)
105
116
  empty = ''.freeze
106
- input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
117
+ result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
118
+ result.gsub!(STRIP_HTML_TAGS, empty)
119
+ result
107
120
  end
108
121
 
109
122
  # Remove all newlines from the string
@@ -120,25 +133,18 @@ module Liquid
120
133
  # provide optional property with which to sort an array of hashes or drops
121
134
  def sort(input, property = nil)
122
135
  ary = InputIterator.new(input)
136
+
137
+ return [] if ary.empty?
138
+
123
139
  if property.nil?
124
140
  ary.sort do |a, b|
125
- if !a.nil? && !b.nil?
126
- a <=> b
127
- else
128
- a.nil? ? 1 : -1
129
- end
141
+ nil_safe_compare(a, b)
130
142
  end
131
- elsif ary.empty? # The next two cases assume a non-empty array.
132
- []
133
143
  elsif ary.all? { |el| el.respond_to?(:[]) }
134
- ary.sort do |a, b|
135
- a = a[property]
136
- b = b[property]
137
- if !a.nil? && !b.nil?
138
- a <=> b
139
- else
140
- a.nil? ? 1 : -1
141
- end
144
+ begin
145
+ ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
146
+ rescue TypeError
147
+ raise_property_error(property)
142
148
  end
143
149
  end
144
150
  end
@@ -148,25 +154,39 @@ module Liquid
148
154
  def sort_natural(input, property = nil)
149
155
  ary = InputIterator.new(input)
150
156
 
157
+ return [] if ary.empty?
158
+
151
159
  if property.nil?
152
160
  ary.sort do |a, b|
153
- if !a.nil? && !b.nil?
154
- a.to_s.casecmp(b.to_s)
155
- else
156
- a.nil? ? 1 : -1
157
- end
161
+ nil_safe_casecmp(a, b)
158
162
  end
159
- elsif ary.empty? # The next two cases assume a non-empty array.
160
- []
161
163
  elsif ary.all? { |el| el.respond_to?(:[]) }
162
- ary.sort do |a, b|
163
- a = a[property]
164
- b = b[property]
165
- if !a.nil? && !b.nil?
166
- a.to_s.casecmp(b.to_s)
167
- else
168
- a.nil? ? 1 : -1
169
- end
164
+ begin
165
+ ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
166
+ rescue TypeError
167
+ raise_property_error(property)
168
+ end
169
+ end
170
+ end
171
+
172
+ # Filter the elements of an array to those with a certain property value.
173
+ # By default the target is any truthy value.
174
+ def where(input, property, target_value = nil)
175
+ ary = InputIterator.new(input)
176
+
177
+ if ary.empty?
178
+ []
179
+ elsif ary.first.respond_to?(:[]) && target_value.nil?
180
+ begin
181
+ ary.select { |item| item[property] }
182
+ rescue TypeError
183
+ raise_property_error(property)
184
+ end
185
+ elsif ary.first.respond_to?(:[])
186
+ begin
187
+ ary.select { |item| item[property] == target_value }
188
+ rescue TypeError
189
+ raise_property_error(property)
170
190
  end
171
191
  end
172
192
  end
@@ -181,7 +201,11 @@ module Liquid
181
201
  elsif ary.empty? # The next two cases assume a non-empty array.
182
202
  []
183
203
  elsif ary.first.respond_to?(:[])
184
- ary.uniq{ |a| a[property] }
204
+ begin
205
+ ary.uniq { |a| a[property] }
206
+ rescue TypeError
207
+ raise_property_error(property)
208
+ end
185
209
  end
186
210
  end
187
211
 
@@ -203,6 +227,8 @@ module Liquid
203
227
  r.is_a?(Proc) ? r.call : r
204
228
  end
205
229
  end
230
+ rescue TypeError
231
+ raise_property_error(property)
206
232
  end
207
233
 
208
234
  # Remove nils within an array
@@ -215,7 +241,11 @@ module Liquid
215
241
  elsif ary.empty? # The next two cases assume a non-empty array.
216
242
  []
217
243
  elsif ary.first.respond_to?(:[])
218
- ary.reject{ |a| a[property].nil? }
244
+ begin
245
+ ary.reject { |a| a[property].nil? }
246
+ rescue TypeError
247
+ raise_property_error(property)
248
+ end
219
249
  end
220
250
  end
221
251
 
@@ -399,11 +429,31 @@ module Liquid
399
429
 
400
430
  private
401
431
 
432
+ def raise_property_error(property)
433
+ raise Liquid::ArgumentError.new("cannot select the property '#{property}'")
434
+ end
435
+
402
436
  def apply_operation(input, operand, operation)
403
437
  result = Utils.to_number(input).send(operation, Utils.to_number(operand))
404
438
  result.is_a?(BigDecimal) ? result.to_f : result
405
439
  end
406
440
 
441
+ def nil_safe_compare(a, b)
442
+ if !a.nil? && !b.nil?
443
+ a <=> b
444
+ else
445
+ a.nil? ? 1 : -1
446
+ end
447
+ end
448
+
449
+ def nil_safe_casecmp(a, b)
450
+ if !a.nil? && !b.nil?
451
+ a.to_s.casecmp(b.to_s)
452
+ else
453
+ a.nil? ? 1 : -1
454
+ end
455
+ end
456
+
407
457
  class InputIterator
408
458
  include Enumerable
409
459
 
@@ -10,6 +10,8 @@ module Liquid
10
10
  class Assign < Tag
11
11
  Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
12
12
 
13
+ attr_reader :to, :from
14
+
13
15
  def initialize(tag_name, markup, options)
14
16
  super
15
17
  if markup =~ Syntax
@@ -45,6 +47,12 @@ module Liquid
45
47
  1
46
48
  end
47
49
  end
50
+
51
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
52
+ def children
53
+ [@node.from]
54
+ end
55
+ end
48
56
  end
49
57
 
50
58
  Template.register_tag('assign'.freeze, Assign)
@@ -3,6 +3,8 @@ module Liquid
3
3
  Syntax = /(#{QuotedFragment})/o
4
4
  WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
5
5
 
6
+ attr_reader :blocks, :left
7
+
6
8
  def initialize(tag_name, markup, options)
7
9
  super
8
10
  @blocks = []
@@ -80,6 +82,12 @@ module Liquid
80
82
  block.attach(BlockBody.new)
81
83
  @blocks << block
82
84
  end
85
+
86
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
87
+ def children
88
+ [@node.left] + @node.blocks
89
+ end
90
+ end
83
91
  end
84
92
 
85
93
  Template.register_tag('case'.freeze, Case)
@@ -15,6 +15,8 @@ module Liquid
15
15
  SimpleSyntax = /\A#{QuotedFragment}+/o
16
16
  NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
17
17
 
18
+ attr_reader :variables
19
+
18
20
  def initialize(tag_name, markup, options)
19
21
  super
20
22
  case markup
@@ -51,6 +53,12 @@ module Liquid
51
53
  $1 ? Expression.parse($1) : nil
52
54
  end.compact
53
55
  end
56
+
57
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
58
+ def children
59
+ Array(@node.variables)
60
+ end
61
+ end
54
62
  end
55
63
 
56
64
  Template.register_tag('cycle', Cycle)
@@ -46,8 +46,7 @@ module Liquid
46
46
  class For < Block
47
47
  Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
48
 
49
- attr_reader :collection_name
50
- attr_reader :variable_name
49
+ attr_reader :collection_name, :variable_name, :limit, :from
51
50
 
52
51
  def initialize(tag_name, markup, options)
53
52
  super
@@ -192,6 +191,12 @@ module Liquid
192
191
  def render_else(context)
193
192
  @else_block ? @else_block.render(context) : ''.freeze
194
193
  end
194
+
195
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
196
+ def children
197
+ (super + [@node.limit, @node.from, @node.collection_name]).compact
198
+ end
199
+ end
195
200
  end
196
201
 
197
202
  Template.register_tag('for'.freeze, For)
@@ -12,7 +12,9 @@ module Liquid
12
12
  class If < Block
13
13
  Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
14
14
  ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
15
- BOOLEAN_OPERATORS = %w(and or)
15
+ BOOLEAN_OPERATORS = %w(and or).freeze
16
+
17
+ attr_reader :blocks
16
18
 
17
19
  def initialize(tag_name, markup, options)
18
20
  super
@@ -20,15 +22,15 @@ module Liquid
20
22
  push_block('if'.freeze, markup)
21
23
  end
22
24
 
25
+ def nodelist
26
+ @blocks.map(&:attachment)
27
+ end
28
+
23
29
  def parse(tokens)
24
30
  while parse_body(@blocks.last.attachment, tokens)
25
31
  end
26
32
  end
27
33
 
28
- def nodelist
29
- @blocks.map(&:attachment)
30
- end
31
-
32
34
  def unknown_tag(tag, markup, tokens)
33
35
  if ['elsif'.freeze, 'else'.freeze].include?(tag)
34
36
  push_block(tag, markup)
@@ -108,6 +110,12 @@ module Liquid
108
110
  Condition.new(a)
109
111
  end
110
112
  end
113
+
114
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
115
+ def children
116
+ @node.blocks
117
+ end
118
+ end
111
119
  end
112
120
 
113
121
  Template.register_tag('if'.freeze, If)
@@ -16,6 +16,8 @@ module Liquid
16
16
  class Include < Tag
17
17
  Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
18
18
 
19
+ attr_reader :template_name_expr, :variable_name_expr, :attributes
20
+
19
21
  def initialize(tag_name, markup, options)
20
22
  super
21
23
 
@@ -107,6 +109,15 @@ module Liquid
107
109
 
108
110
  file_system.read_template_file(context.evaluate(@template_name_expr))
109
111
  end
112
+
113
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
114
+ def children
115
+ [
116
+ @node.template_name_expr,
117
+ @node.variable_name_expr
118
+ ] + @node.attributes.values
119
+ end
120
+ end
110
121
  end
111
122
 
112
123
  Template.register_tag('include'.freeze, Include)
@@ -2,6 +2,8 @@ module Liquid
2
2
  class TableRow < Block
3
3
  Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
4
4
 
5
+ attr_reader :variable_name, :collection_name, :attributes
6
+
5
7
  def initialize(tag_name, markup, options)
6
8
  super
7
9
  if markup =~ Syntax
@@ -48,6 +50,12 @@ module Liquid
48
50
  result << "</tr>\n"
49
51
  result
50
52
  end
53
+
54
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
55
+ def children
56
+ super + @node.attributes.values + [@node.collection_name]
57
+ end
58
+ end
51
59
  end
52
60
 
53
61
  Template.register_tag('tablerow'.freeze, TableRow)
@@ -0,0 +1,5 @@
1
+ module Liquid
2
+ module Truffle
3
+
4
+ end
5
+ end
@@ -138,5 +138,11 @@ module Liquid
138
138
  raise error
139
139
  end
140
140
  end
141
+
142
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
143
+ def children
144
+ [@node.name] + @node.filters.flatten
145
+ end
146
+ end
141
147
  end
142
148
  end
@@ -1,7 +1,7 @@
1
1
  module Liquid
2
2
  class VariableLookup
3
3
  SQUARE_BRACKETED = /\A\[(.*)\]\z/m
4
- COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
4
+ COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze
5
5
 
6
6
  attr_reader :name, :lookups
7
7
 
@@ -78,5 +78,11 @@ module Liquid
78
78
  def state
79
79
  [@name, @lookups, @command_flags]
80
80
  end
81
+
82
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
83
+ def children
84
+ @node.lookups
85
+ end
86
+ end
81
87
  end
82
88
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+
2
3
  module Liquid
3
- VERSION = "4.0.1"
4
+ VERSION = "4.0.2".freeze
4
5
  end
@@ -123,7 +123,7 @@ class ErrorHandlingTest < Minitest::Test
123
123
  ',
124
124
  error_mode: :warn,
125
125
  line_numbers: true
126
- )
126
+ )
127
127
 
128
128
  assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
129
129
  template.warnings.map(&:message)
@@ -140,7 +140,7 @@ class ErrorHandlingTest < Minitest::Test
140
140
  ',
141
141
  error_mode: :strict,
142
142
  line_numbers: true
143
- )
143
+ )
144
144
  end
145
145
 
146
146
  assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
@@ -158,7 +158,7 @@ class ErrorHandlingTest < Minitest::Test
158
158
  bla
159
159
  ',
160
160
  line_numbers: true
161
- )
161
+ )
162
162
  end
163
163
 
164
164
  assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class ParseTreeVisitorTest < Minitest::Test
6
+ include Liquid
7
+
8
+ def test_variable
9
+ assert_equal(
10
+ ["test"],
11
+ visit(%({{ test }}))
12
+ )
13
+ end
14
+
15
+ def test_varible_with_filter
16
+ assert_equal(
17
+ ["test", "infilter"],
18
+ visit(%({{ test | split: infilter }}))
19
+ )
20
+ end
21
+
22
+ def test_dynamic_variable
23
+ assert_equal(
24
+ ["test", "inlookup"],
25
+ visit(%({{ test[inlookup] }}))
26
+ )
27
+ end
28
+
29
+ def test_if_condition
30
+ assert_equal(
31
+ ["test"],
32
+ visit(%({% if test %}{% endif %}))
33
+ )
34
+ end
35
+
36
+ def test_complex_if_condition
37
+ assert_equal(
38
+ ["test"],
39
+ visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
40
+ )
41
+ end
42
+
43
+ def test_if_body
44
+ assert_equal(
45
+ ["test"],
46
+ visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
47
+ )
48
+ end
49
+
50
+ def test_unless_condition
51
+ assert_equal(
52
+ ["test"],
53
+ visit(%({% unless test %}{% endunless %}))
54
+ )
55
+ end
56
+
57
+ def test_complex_unless_condition
58
+ assert_equal(
59
+ ["test"],
60
+ visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
61
+ )
62
+ end
63
+
64
+ def test_unless_body
65
+ assert_equal(
66
+ ["test"],
67
+ visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
68
+ )
69
+ end
70
+
71
+ def test_elsif_condition
72
+ assert_equal(
73
+ ["test"],
74
+ visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
75
+ )
76
+ end
77
+
78
+ def test_complex_elsif_condition
79
+ assert_equal(
80
+ ["test"],
81
+ visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
82
+ )
83
+ end
84
+
85
+ def test_elsif_body
86
+ assert_equal(
87
+ ["test"],
88
+ visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
89
+ )
90
+ end
91
+
92
+ def test_else_body
93
+ assert_equal(
94
+ ["test"],
95
+ visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
96
+ )
97
+ end
98
+
99
+ def test_case_left
100
+ assert_equal(
101
+ ["test"],
102
+ visit(%({% case test %}{% endcase %}))
103
+ )
104
+ end
105
+
106
+ def test_case_condition
107
+ assert_equal(
108
+ ["test"],
109
+ visit(%({% case 1 %}{% when test %}{% endcase %}))
110
+ )
111
+ end
112
+
113
+ def test_case_when_body
114
+ assert_equal(
115
+ ["test"],
116
+ visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
117
+ )
118
+ end
119
+
120
+ def test_case_else_body
121
+ assert_equal(
122
+ ["test"],
123
+ visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
124
+ )
125
+ end
126
+
127
+ def test_for_in
128
+ assert_equal(
129
+ ["test"],
130
+ visit(%({% for x in test %}{% endfor %}))
131
+ )
132
+ end
133
+
134
+ def test_for_limit
135
+ assert_equal(
136
+ ["test"],
137
+ visit(%({% for x in (1..5) limit: test %}{% endfor %}))
138
+ )
139
+ end
140
+
141
+ def test_for_offset
142
+ assert_equal(
143
+ ["test"],
144
+ visit(%({% for x in (1..5) offset: test %}{% endfor %}))
145
+ )
146
+ end
147
+
148
+ def test_for_body
149
+ assert_equal(
150
+ ["test"],
151
+ visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
152
+ )
153
+ end
154
+
155
+ def test_tablerow_in
156
+ assert_equal(
157
+ ["test"],
158
+ visit(%({% tablerow x in test %}{% endtablerow %}))
159
+ )
160
+ end
161
+
162
+ def test_tablerow_limit
163
+ assert_equal(
164
+ ["test"],
165
+ visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
166
+ )
167
+ end
168
+
169
+ def test_tablerow_offset
170
+ assert_equal(
171
+ ["test"],
172
+ visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
173
+ )
174
+ end
175
+
176
+ def test_tablerow_body
177
+ assert_equal(
178
+ ["test"],
179
+ visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
180
+ )
181
+ end
182
+
183
+ def test_cycle
184
+ assert_equal(
185
+ ["test"],
186
+ visit(%({% cycle test %}))
187
+ )
188
+ end
189
+
190
+ def test_assign
191
+ assert_equal(
192
+ ["test"],
193
+ visit(%({% assign x = test %}))
194
+ )
195
+ end
196
+
197
+ def test_capture
198
+ assert_equal(
199
+ ["test"],
200
+ visit(%({% capture x %}{{ test }}{% endcapture %}))
201
+ )
202
+ end
203
+
204
+ def test_include
205
+ assert_equal(
206
+ ["test"],
207
+ visit(%({% include test %}))
208
+ )
209
+ end
210
+
211
+ def test_include_with
212
+ assert_equal(
213
+ ["test"],
214
+ visit(%({% include "hai" with test %}))
215
+ )
216
+ end
217
+
218
+ def test_include_for
219
+ assert_equal(
220
+ ["test"],
221
+ visit(%({% include "hai" for test %}))
222
+ )
223
+ end
224
+
225
+ def test_preserve_tree_structure
226
+ assert_equal(
227
+ [[nil, [
228
+ [nil, [[nil, [["other", []]]]]],
229
+ ["test", []],
230
+ ["xs", []]
231
+ ]]],
232
+ traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
233
+ )
234
+ end
235
+
236
+ private
237
+
238
+ def traversal(template)
239
+ ParseTreeVisitor
240
+ .for(Template.parse(template).root)
241
+ .add_callback_for(VariableLookup, &:name)
242
+ end
243
+
244
+ def visit(template)
245
+ traversal(template).visit.flatten.compact
246
+ end
247
+ end
@@ -99,7 +99,7 @@ class ParsingQuirksTest < Minitest::Test
99
99
  # After the messed up quotes a filter without parameters (reverse) should work
100
100
  # but one with parameters (remove) shouldn't be detected.
101
101
  assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
102
- assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
102
+ assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
103
103
  end
104
104
  end
105
105
 
@@ -158,6 +158,10 @@ class StandardFiltersTest < Minitest::Test
158
158
  assert_equal '1', @filters.url_decode(1)
159
159
  assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))
160
160
  assert_nil @filters.url_decode(nil)
161
+ exception = assert_raises Liquid::ArgumentError do
162
+ @filters.url_decode('%ff')
163
+ end
164
+ assert_equal 'Liquid error: invalid byte sequence in UTF-8', exception.message
161
165
  end
162
166
 
163
167
  def test_truncatewords
@@ -177,6 +181,9 @@ class StandardFiltersTest < Minitest::Test
177
181
  assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
178
182
  assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
179
183
  assert_equal '', @filters.strip_html(nil)
184
+
185
+ # Quirk of the existing implementation
186
+ assert_equal 'foo;', @filters.strip_html("<<<script </script>script>foo;</script>")
180
187
  end
181
188
 
182
189
  def test_join
@@ -268,10 +275,34 @@ class StandardFiltersTest < Minitest::Test
268
275
  assert_equal [], @filters.sort([], "a")
269
276
  end
270
277
 
278
+ def test_sort_invalid_property
279
+ foo = [
280
+ [1],
281
+ [2],
282
+ [3]
283
+ ]
284
+
285
+ assert_raises Liquid::ArgumentError do
286
+ @filters.sort(foo, "bar")
287
+ end
288
+ end
289
+
271
290
  def test_sort_natural_empty_array
272
291
  assert_equal [], @filters.sort_natural([], "a")
273
292
  end
274
293
 
294
+ def test_sort_natural_invalid_property
295
+ foo = [
296
+ [1],
297
+ [2],
298
+ [3]
299
+ ]
300
+
301
+ assert_raises Liquid::ArgumentError do
302
+ @filters.sort_natural(foo, "bar")
303
+ end
304
+ end
305
+
275
306
  def test_legacy_sort_hash
276
307
  assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
277
308
  end
@@ -295,10 +326,34 @@ class StandardFiltersTest < Minitest::Test
295
326
  assert_equal [], @filters.uniq([], "a")
296
327
  end
297
328
 
329
+ def test_uniq_invalid_property
330
+ foo = [
331
+ [1],
332
+ [2],
333
+ [3]
334
+ ]
335
+
336
+ assert_raises Liquid::ArgumentError do
337
+ @filters.uniq(foo, "bar")
338
+ end
339
+ end
340
+
298
341
  def test_compact_empty_array
299
342
  assert_equal [], @filters.compact([], "a")
300
343
  end
301
344
 
345
+ def test_compact_invalid_property
346
+ foo = [
347
+ [1],
348
+ [2],
349
+ [3]
350
+ ]
351
+
352
+ assert_raises Liquid::ArgumentError do
353
+ @filters.compact(foo, "bar")
354
+ end
355
+ end
356
+
302
357
  def test_reverse
303
358
  assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
304
359
  end
@@ -364,6 +419,29 @@ class StandardFiltersTest < Minitest::Test
364
419
  assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
365
420
  end
366
421
 
422
+ def test_map_returns_empty_on_2d_input_array
423
+ foo = [
424
+ [1],
425
+ [2],
426
+ [3]
427
+ ]
428
+
429
+ assert_raises Liquid::ArgumentError do
430
+ @filters.map(foo, "bar")
431
+ end
432
+ end
433
+
434
+ def test_map_returns_empty_with_no_property
435
+ foo = [
436
+ [1],
437
+ [2],
438
+ [3]
439
+ ]
440
+ assert_raises Liquid::ArgumentError do
441
+ @filters.map(foo, nil)
442
+ end
443
+ end
444
+
367
445
  def test_sort_works_on_enumerables
368
446
  assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
369
447
  end
@@ -394,9 +472,9 @@ class StandardFiltersTest < Minitest::Test
394
472
  assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
395
473
 
396
474
  assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
397
- assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
398
- assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
399
- assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
475
+ assert_equal Date.today.year.to_s, @filters.date('now', '%Y')
476
+ assert_equal Date.today.year.to_s, @filters.date('today', '%Y')
477
+ assert_equal Date.today.year.to_s, @filters.date('Today', '%Y')
400
478
 
401
479
  assert_nil @filters.date(nil, "%B")
402
480
 
@@ -614,6 +692,78 @@ class StandardFiltersTest < Minitest::Test
614
692
  assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
615
693
  end
616
694
 
695
+ def test_where
696
+ input = [
697
+ { "handle" => "alpha", "ok" => true },
698
+ { "handle" => "beta", "ok" => false },
699
+ { "handle" => "gamma", "ok" => false },
700
+ { "handle" => "delta", "ok" => true }
701
+ ]
702
+
703
+ expectation = [
704
+ { "handle" => "alpha", "ok" => true },
705
+ { "handle" => "delta", "ok" => true }
706
+ ]
707
+
708
+ assert_equal expectation, @filters.where(input, "ok", true)
709
+ assert_equal expectation, @filters.where(input, "ok")
710
+ end
711
+
712
+ def test_where_no_key_set
713
+ input = [
714
+ { "handle" => "alpha", "ok" => true },
715
+ { "handle" => "beta" },
716
+ { "handle" => "gamma" },
717
+ { "handle" => "delta", "ok" => true }
718
+ ]
719
+
720
+ expectation = [
721
+ { "handle" => "alpha", "ok" => true },
722
+ { "handle" => "delta", "ok" => true }
723
+ ]
724
+
725
+ assert_equal expectation, @filters.where(input, "ok", true)
726
+ assert_equal expectation, @filters.where(input, "ok")
727
+ end
728
+
729
+ def test_where_non_array_map_input
730
+ assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok")
731
+ assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok")
732
+ end
733
+
734
+ def test_where_indexable_but_non_map_value
735
+ assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) }
736
+ assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") }
737
+ end
738
+
739
+ def test_where_non_boolean_value
740
+ input = [
741
+ { "message" => "Bonjour!", "language" => "French" },
742
+ { "message" => "Hello!", "language" => "English" },
743
+ { "message" => "Hallo!", "language" => "German" }
744
+ ]
745
+
746
+ assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")
747
+ assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German")
748
+ assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English")
749
+ end
750
+
751
+ def test_where_array_of_only_unindexable_values
752
+ assert_nil @filters.where([nil], "ok", true)
753
+ assert_nil @filters.where([nil], "ok")
754
+ end
755
+
756
+ def test_where_no_target_value
757
+ input = [
758
+ { "foo" => false },
759
+ { "foo" => true },
760
+ { "foo" => "for sure" },
761
+ { "bar" => true }
762
+ ]
763
+
764
+ assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo")
765
+ end
766
+
617
767
  private
618
768
 
619
769
  def with_timezone(tz)
File without changes
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ class TruffleTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_truffle_works
7
+
8
+ end
9
+ end
@@ -24,9 +24,9 @@ class ConditionUnitTest < Minitest::Test
24
24
  assert_evaluates_true 1, '<=', 1
25
25
  # negative numbers
26
26
  assert_evaluates_true 1, '>', -1
27
- assert_evaluates_true (-1), '<', 1
27
+ assert_evaluates_true -1, '<', 1
28
28
  assert_evaluates_true 1.0, '>', -1.0
29
- assert_evaluates_true (-1.0), '<', 1.0
29
+ assert_evaluates_true -1.0, '<', 1.0
30
30
  end
31
31
 
32
32
  def test_default_operators_evalute_false
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-09 00:00:00.000000000 Z
11
+ date: 2019-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -67,6 +67,7 @@ files:
67
67
  - lib/liquid/lexer.rb
68
68
  - lib/liquid/locales/en.yml
69
69
  - lib/liquid/parse_context.rb
70
+ - lib/liquid/parse_tree_visitor.rb
70
71
  - lib/liquid/parser.rb
71
72
  - lib/liquid/parser_switching.rb
72
73
  - lib/liquid/profiler.rb
@@ -95,6 +96,7 @@ files:
95
96
  - lib/liquid/tags/unless.rb
96
97
  - lib/liquid/template.rb
97
98
  - lib/liquid/tokenizer.rb
99
+ - lib/liquid/truffle.rb
98
100
  - lib/liquid/utils.rb
99
101
  - lib/liquid/variable.rb
100
102
  - lib/liquid/variable_lookup.rb
@@ -111,6 +113,7 @@ files:
111
113
  - test/integration/filter_test.rb
112
114
  - test/integration/hash_ordering_test.rb
113
115
  - test/integration/output_test.rb
116
+ - test/integration/parse_tree_visitor_test.rb
114
117
  - test/integration/parsing_quirks_test.rb
115
118
  - test/integration/render_profiling_test.rb
116
119
  - test/integration/security_test.rb
@@ -130,6 +133,7 @@ files:
130
133
  - test/integration/trim_mode_test.rb
131
134
  - test/integration/variable_test.rb
132
135
  - test/test_helper.rb
136
+ - test/truffle/truffle_test.rb
133
137
  - test/unit/block_unit_test.rb
134
138
  - test/unit/condition_unit_test.rb
135
139
  - test/unit/context_unit_test.rb
@@ -165,8 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
169
  - !ruby/object:Gem::Version
166
170
  version: 1.3.7
167
171
  requirements: []
168
- rubyforge_project:
169
- rubygems_version: 2.7.6
172
+ rubygems_version: 3.0.2
170
173
  signing_key:
171
174
  specification_version: 4
172
175
  summary: A secure, non-evaling end user template engine with aesthetic markup.
@@ -191,6 +194,7 @@ test_files:
191
194
  - test/integration/hash_ordering_test.rb
192
195
  - test/integration/variable_test.rb
193
196
  - test/integration/blank_test.rb
197
+ - test/integration/parse_tree_visitor_test.rb
194
198
  - test/integration/assign_test.rb
195
199
  - test/integration/trim_mode_test.rb
196
200
  - test/integration/context_test.rb
@@ -216,5 +220,6 @@ test_files:
216
220
  - test/integration/render_profiling_test.rb
217
221
  - test/integration/parsing_quirks_test.rb
218
222
  - test/integration/filter_test.rb
223
+ - test/truffle/truffle_test.rb
219
224
  - test/fixtures/en_locale.yml
220
225
  - test/test_helper.rb