liquid 4.0.0.rc1 → 4.0.0.rc2
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 +4 -4
- data/History.md +8 -1
- data/README.md +31 -0
- data/lib/liquid/block_body.rb +3 -0
- data/lib/liquid/context.rb +9 -2
- data/lib/liquid/drop.rb +7 -5
- data/lib/liquid/errors.rb +4 -0
- data/lib/liquid/file_system.rb +1 -1
- data/lib/liquid/parse_context.rb +2 -2
- data/lib/liquid/standardfilters.rb +10 -10
- data/lib/liquid/strainer.rb +10 -3
- data/lib/liquid/tags/assign.rb +16 -4
- data/lib/liquid/tags/for.rb +2 -0
- data/lib/liquid/template.rb +13 -9
- data/lib/liquid/tokenizer.rb +1 -1
- data/lib/liquid/utils.rb +6 -2
- data/lib/liquid/variable_lookup.rb +4 -2
- data/lib/liquid/version.rb +1 -1
- data/test/integration/error_handling_test.rb +1 -1
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/security_test.rb +4 -0
- data/test/integration/standard_filter_test.rb +29 -4
- data/test/integration/tags/raw_tag_test.rb +3 -3
- data/test/integration/template_test.rb +81 -0
- data/test/unit/condition_unit_test.rb +55 -51
- data/test/unit/strainer_unit_test.rb +56 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a15813568dcc19866675ad50444b7939b5eecfa
|
4
|
+
data.tar.gz: acfb1e49596e266170a25c584978ec8ade8bcf01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20fa5f4ceec720a9c57219f71bb848c7b407529cf46bf7353fba196452bf9945e33d79e25aff8fb92aceb32ac0f6d2299e6204e7fc5d6eec04134224dae725ef
|
7
|
+
data.tar.gz: 4ed81d181edfaf21bd25b303ec4e5085c9e734e3fb9679aa3b7a88f664bced5ab60825bb163ffaf7498b650cedf779901ce78917abad1fcbc5be1ec7b27573ee
|
data/History.md
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
## 4.0.0 / not yet released / branch "master"
|
4
4
|
|
5
5
|
### Changed
|
6
|
+
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
7
|
+
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
8
|
+
* Improve loop performance (#681) [Florian Weingarten]
|
9
|
+
* Rename Drop method `before_method` to `liquid_method_missing` (#661) [Thierry Joyal]
|
6
10
|
* Add url_decode filter to invert url_encode (#645) [Larry Archer]
|
7
11
|
* Add global_filter to apply a filter to all output (#610) [Loren Hale]
|
8
12
|
* Add compact filter (#600) [Carson Reinke]
|
@@ -15,9 +19,12 @@
|
|
15
19
|
* Ruby 1.9 support dropped (#491) [Justin Li]
|
16
20
|
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
|
17
21
|
* Remove support for `liquid_methods`
|
18
|
-
*
|
22
|
+
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
|
19
23
|
|
20
24
|
### Fixed
|
25
|
+
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
|
26
|
+
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
|
27
|
+
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
|
21
28
|
* Fix sort filter behaviour with empty array input (#652) [Marcel Cary]
|
22
29
|
* Fix test failure under certain timezones (#631) [Dylan Thacker-Smith]
|
23
30
|
* Fix bug in uniq filter (#595) [Florian Weingarten]
|
data/README.md
CHANGED
@@ -73,3 +73,34 @@ This is useful for doing things like enabling strict mode only in the theme edit
|
|
73
73
|
|
74
74
|
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
|
75
75
|
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
|
76
|
+
|
77
|
+
### Undefined variables and filters
|
78
|
+
|
79
|
+
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
|
80
|
+
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
|
81
|
+
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.
|
82
|
+
Here are some examples:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
86
|
+
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
|
87
|
+
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
88
|
+
template.errors
|
89
|
+
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
90
|
+
```
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
94
|
+
template.render({ 'x' => 'foo' }, { strict_filters: true })
|
95
|
+
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
96
|
+
template.errors
|
97
|
+
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
98
|
+
```
|
99
|
+
|
100
|
+
If you want to raise on a first exception instead of pushing all of them in `errors`, you can use `render!` method:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
template = Liquid::Template.parse("{{x}} {{y}}")
|
104
|
+
template.render!({ 'x' => 1}, { strict_variables: true })
|
105
|
+
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
|
106
|
+
```
|
data/lib/liquid/block_body.rb
CHANGED
@@ -76,6 +76,9 @@ module Liquid
|
|
76
76
|
end
|
77
77
|
rescue MemoryError => e
|
78
78
|
raise e
|
79
|
+
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
80
|
+
context.handle_error(e, token.line_number)
|
81
|
+
output << nil
|
79
82
|
rescue ::StandardError => e
|
80
83
|
output << context.handle_error(e, token.line_number)
|
81
84
|
end
|
data/lib/liquid/context.rb
CHANGED
@@ -13,7 +13,7 @@ module Liquid
|
|
13
13
|
# context['bob'] #=> nil class Context
|
14
14
|
class Context
|
15
15
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
16
|
-
attr_accessor :exception_handler, :template_name, :partial, :global_filter
|
16
|
+
attr_accessor :exception_handler, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
17
17
|
|
18
18
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
19
19
|
@environments = [environments].flatten
|
@@ -21,6 +21,7 @@ module Liquid
|
|
21
21
|
@registers = registers
|
22
22
|
@errors = []
|
23
23
|
@partial = false
|
24
|
+
@strict_variables = false
|
24
25
|
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
25
26
|
squash_instance_assigns_with_environments
|
26
27
|
|
@@ -205,7 +206,13 @@ module Liquid
|
|
205
206
|
end
|
206
207
|
|
207
208
|
def lookup_and_evaluate(obj, key)
|
208
|
-
if
|
209
|
+
if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
|
210
|
+
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
211
|
+
end
|
212
|
+
|
213
|
+
value = obj[key]
|
214
|
+
|
215
|
+
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
209
216
|
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
210
217
|
else
|
211
218
|
value
|
data/lib/liquid/drop.rb
CHANGED
@@ -24,8 +24,9 @@ module Liquid
|
|
24
24
|
attr_writer :context
|
25
25
|
|
26
26
|
# Catch all for the method
|
27
|
-
def liquid_method_missing(
|
28
|
-
nil
|
27
|
+
def liquid_method_missing(method)
|
28
|
+
return nil unless @context && @context.strict_variables
|
29
|
+
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
|
29
30
|
end
|
30
31
|
|
31
32
|
# called by liquid to invoke a drop
|
@@ -61,16 +62,17 @@ module Liquid
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def self.invokable_methods
|
64
|
-
|
65
|
+
@invokable_methods ||= begin
|
65
66
|
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
67
|
+
|
66
68
|
if include?(Enumerable)
|
67
69
|
blacklist += Enumerable.public_instance_methods
|
68
70
|
blacklist -= [:sort, :count, :first, :min, :max, :include?]
|
69
71
|
end
|
72
|
+
|
70
73
|
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
71
|
-
|
74
|
+
Set.new(whitelist.map(&:to_s))
|
72
75
|
end
|
73
|
-
@invokable_methods
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
data/lib/liquid/errors.rb
CHANGED
@@ -56,4 +56,8 @@ module Liquid
|
|
56
56
|
MemoryError = Class.new(Error)
|
57
57
|
ZeroDivisionError = Class.new(Error)
|
58
58
|
FloatDomainError = Class.new(Error)
|
59
|
+
UndefinedVariable = Class.new(Error)
|
60
|
+
UndefinedDropMethod = Class.new(Error)
|
61
|
+
UndefinedFilter = Class.new(Error)
|
62
|
+
MethodOverrideError = Class.new(Error)
|
59
63
|
end
|
data/lib/liquid/file_system.rb
CHANGED
@@ -65,7 +65,7 @@ module Liquid
|
|
65
65
|
File.join(root, @pattern % template_path)
|
66
66
|
end
|
67
67
|
|
68
|
-
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path)
|
68
|
+
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
|
69
69
|
|
70
70
|
full_path
|
71
71
|
end
|
data/lib/liquid/parse_context.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Liquid
|
2
2
|
class ParseContext
|
3
|
-
attr_accessor :
|
4
|
-
attr_reader :warnings, :error_mode
|
3
|
+
attr_accessor :locale, :line_number
|
4
|
+
attr_reader :partial, :warnings, :error_mode
|
5
5
|
|
6
6
|
def initialize(options = {})
|
7
7
|
@template_options = options ? options.dup : {}
|
@@ -125,8 +125,6 @@ module Liquid
|
|
125
125
|
[]
|
126
126
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
127
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
128
|
end
|
131
129
|
end
|
132
130
|
|
@@ -141,8 +139,6 @@ module Liquid
|
|
141
139
|
[]
|
142
140
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
143
141
|
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
144
|
-
elsif ary.first.respond_to?(property)
|
145
|
-
ary.sort { |a, b| a.send(property).casecmp(b.send(property)) }
|
146
142
|
end
|
147
143
|
end
|
148
144
|
|
@@ -191,8 +187,6 @@ module Liquid
|
|
191
187
|
[]
|
192
188
|
elsif ary.first.respond_to?(:[])
|
193
189
|
ary.reject{ |a| a[property].nil? }
|
194
|
-
elsif ary.first.respond_to?(property)
|
195
|
-
ary.reject { |a| a.send(property).nil? }
|
196
190
|
end
|
197
191
|
end
|
198
192
|
|
@@ -222,6 +216,9 @@ module Liquid
|
|
222
216
|
end
|
223
217
|
|
224
218
|
def concat(input, array)
|
219
|
+
unless array.respond_to?(:to_ary)
|
220
|
+
raise ArgumentError.new("concat filter requires an array argument")
|
221
|
+
end
|
225
222
|
InputIterator.new(input).concat(array)
|
226
223
|
end
|
227
224
|
|
@@ -341,9 +338,12 @@ module Liquid
|
|
341
338
|
raise Liquid::FloatDomainError, e.message
|
342
339
|
end
|
343
340
|
|
344
|
-
def default(input, default_value =
|
345
|
-
|
346
|
-
|
341
|
+
def default(input, default_value = ''.freeze)
|
342
|
+
if !input || input.respond_to?(:empty?) && input.empty?
|
343
|
+
default_value
|
344
|
+
else
|
345
|
+
input
|
346
|
+
end
|
347
347
|
end
|
348
348
|
|
349
349
|
private
|
@@ -373,7 +373,7 @@ module Liquid
|
|
373
373
|
end
|
374
374
|
|
375
375
|
def concat(args)
|
376
|
-
to_a.concat
|
376
|
+
to_a.concat(args)
|
377
377
|
end
|
378
378
|
|
379
379
|
def reverse
|
data/lib/liquid/strainer.rb
CHANGED
@@ -26,10 +26,15 @@ module Liquid
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def self.add_filter(filter)
|
29
|
-
raise ArgumentError, "Expected module but got: #{
|
29
|
+
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
30
30
|
unless self.class.include?(filter)
|
31
|
-
|
32
|
-
|
31
|
+
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
32
|
+
if invokable_non_public_methods.any?
|
33
|
+
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
34
|
+
else
|
35
|
+
send(:include, filter)
|
36
|
+
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
37
|
+
end
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
@@ -48,6 +53,8 @@ module Liquid
|
|
48
53
|
def invoke(method, *args)
|
49
54
|
if self.class.invokable?(method)
|
50
55
|
send(method, *args)
|
56
|
+
elsif @context && @context.strict_filters
|
57
|
+
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
51
58
|
else
|
52
59
|
args.first
|
53
60
|
end
|
data/lib/liquid/tags/assign.rb
CHANGED
@@ -23,16 +23,28 @@ module Liquid
|
|
23
23
|
def render(context)
|
24
24
|
val = @from.render(context)
|
25
25
|
context.scopes.last[@to] = 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
|
-
|
26
|
+
context.resource_limits.assign_score += assign_score_of(val)
|
30
27
|
''.freeze
|
31
28
|
end
|
32
29
|
|
33
30
|
def blank?
|
34
31
|
true
|
35
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def assign_score_of(val)
|
37
|
+
if val.instance_of?(String)
|
38
|
+
val.length
|
39
|
+
elsif val.instance_of?(Array) || val.instance_of?(Hash)
|
40
|
+
sum = 1
|
41
|
+
# Uses #each to avoid extra allocations.
|
42
|
+
val.each { |child| sum += assign_score_of(child) }
|
43
|
+
sum
|
44
|
+
else
|
45
|
+
1
|
46
|
+
end
|
47
|
+
end
|
36
48
|
end
|
37
49
|
|
38
50
|
Template.register_tag('assign'.freeze, Assign)
|
data/lib/liquid/tags/for.rb
CHANGED
data/lib/liquid/template.rb
CHANGED
@@ -20,7 +20,7 @@ module Liquid
|
|
20
20
|
|
21
21
|
class TagRegistry
|
22
22
|
def initialize
|
23
|
-
@tags
|
23
|
+
@tags = {}
|
24
24
|
@cache = {}
|
25
25
|
end
|
26
26
|
|
@@ -80,11 +80,11 @@ module Liquid
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def error_mode
|
83
|
-
@error_mode
|
83
|
+
@error_mode ||= :lax
|
84
84
|
end
|
85
85
|
|
86
86
|
def taint_mode
|
87
|
-
@taint_mode
|
87
|
+
@taint_mode ||= :lax
|
88
88
|
end
|
89
89
|
|
90
90
|
# Pass a module with filter methods which should be available
|
@@ -107,6 +107,7 @@ module Liquid
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def initialize
|
110
|
+
@rethrow_errors = false
|
110
111
|
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
111
112
|
end
|
112
113
|
|
@@ -181,12 +182,7 @@ module Liquid
|
|
181
182
|
|
182
183
|
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
183
184
|
|
184
|
-
context
|
185
|
-
|
186
|
-
context.global_filter = options[:global_filter] if options[:global_filter]
|
187
|
-
|
188
|
-
context.exception_handler = options[:exception_handler] if options[:exception_handler]
|
189
|
-
|
185
|
+
apply_options_to_context(context, options)
|
190
186
|
when Module, Array
|
191
187
|
context.add_filters(args.pop)
|
192
188
|
end
|
@@ -235,5 +231,13 @@ module Liquid
|
|
235
231
|
yield
|
236
232
|
end
|
237
233
|
end
|
234
|
+
|
235
|
+
def apply_options_to_context(context, options)
|
236
|
+
context.add_filters(options[:filters]) if options[:filters]
|
237
|
+
context.global_filter = options[:global_filter] if options[:global_filter]
|
238
|
+
context.exception_handler = options[:exception_handler] if options[:exception_handler]
|
239
|
+
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
240
|
+
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
241
|
+
end
|
238
242
|
end
|
239
243
|
end
|
data/lib/liquid/tokenizer.rb
CHANGED
data/lib/liquid/utils.rb
CHANGED
@@ -50,9 +50,13 @@ module Liquid
|
|
50
50
|
when Numeric
|
51
51
|
obj
|
52
52
|
when String
|
53
|
-
(obj.strip =~ /\A
|
53
|
+
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
54
54
|
else
|
55
|
-
|
55
|
+
if obj.respond_to?(:to_number)
|
56
|
+
obj.to_number
|
57
|
+
else
|
58
|
+
0
|
59
|
+
end
|
56
60
|
end
|
57
61
|
end
|
58
62
|
|
@@ -55,9 +55,11 @@ module Liquid
|
|
55
55
|
object = object.send(key).to_liquid
|
56
56
|
|
57
57
|
# No key was present with the desired value and it wasn't one of the directly supported
|
58
|
-
# keywords either. The only thing we got left is to return nil
|
58
|
+
# keywords either. The only thing we got left is to return nil or
|
59
|
+
# raise an exception if `strict_variables` option is set to true
|
59
60
|
else
|
60
|
-
return nil
|
61
|
+
return nil unless context.strict_variables
|
62
|
+
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
61
63
|
end
|
62
64
|
|
63
65
|
# If we are dealing with a drop here we have to
|
data/lib/liquid/version.rb
CHANGED
@@ -94,7 +94,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
94
94
|
)
|
95
95
|
end
|
96
96
|
|
97
|
-
assert_match
|
97
|
+
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
98
98
|
end
|
99
99
|
|
100
100
|
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class HashOrderingTest < Minitest::Test
|
4
|
+
module MoneyFilter
|
5
|
+
def money(input)
|
6
|
+
sprintf(' %d$ ', input)
|
7
|
+
end
|
6
8
|
end
|
7
|
-
end
|
8
9
|
|
9
|
-
module CanadianMoneyFilter
|
10
|
-
|
11
|
-
|
10
|
+
module CanadianMoneyFilter
|
11
|
+
def money(input)
|
12
|
+
sprintf(' %d$ CAD ', input)
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
class HashOrderingTest < Minitest::Test
|
16
16
|
include Liquid
|
17
17
|
|
18
18
|
def test_global_register_order
|
@@ -41,6 +41,16 @@ class TestEnumerable < Liquid::Drop
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
class NumberLikeThing < Liquid::Drop
|
45
|
+
def initialize(amount)
|
46
|
+
@amount = amount
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_number
|
50
|
+
@amount
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
44
54
|
class StandardFiltersTest < Minitest::Test
|
45
55
|
include Liquid
|
46
56
|
|
@@ -364,20 +374,25 @@ class StandardFiltersTest < Minitest::Test
|
|
364
374
|
def test_plus
|
365
375
|
assert_template_result "2", "{{ 1 | plus:1 }}"
|
366
376
|
assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
|
377
|
+
|
378
|
+
assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
|
367
379
|
end
|
368
380
|
|
369
381
|
def test_minus
|
370
382
|
assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
|
371
383
|
assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
|
384
|
+
|
385
|
+
assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
|
372
386
|
end
|
373
387
|
|
374
388
|
def test_times
|
375
389
|
assert_template_result "12", "{{ 3 | times:4 }}"
|
376
390
|
assert_template_result "0", "{{ 'foo' | times:4 }}"
|
377
|
-
|
378
391
|
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
|
379
|
-
|
380
392
|
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
|
393
|
+
assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
|
394
|
+
assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
|
395
|
+
assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
|
381
396
|
end
|
382
397
|
|
383
398
|
def test_divided_by
|
@@ -391,6 +406,8 @@ class StandardFiltersTest < Minitest::Test
|
|
391
406
|
assert_raises(Liquid::ZeroDivisionError) do
|
392
407
|
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
393
408
|
end
|
409
|
+
|
410
|
+
assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
|
394
411
|
end
|
395
412
|
|
396
413
|
def test_modulo
|
@@ -398,6 +415,8 @@ class StandardFiltersTest < Minitest::Test
|
|
398
415
|
assert_raises(Liquid::ZeroDivisionError) do
|
399
416
|
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
400
417
|
end
|
418
|
+
|
419
|
+
assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
|
401
420
|
end
|
402
421
|
|
403
422
|
def test_round
|
@@ -407,6 +426,9 @@ class StandardFiltersTest < Minitest::Test
|
|
407
426
|
assert_raises(Liquid::FloatDomainError) do
|
408
427
|
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
|
409
428
|
end
|
429
|
+
|
430
|
+
assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
|
431
|
+
assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
|
410
432
|
end
|
411
433
|
|
412
434
|
def test_ceil
|
@@ -415,6 +437,8 @@ class StandardFiltersTest < Minitest::Test
|
|
415
437
|
assert_raises(Liquid::FloatDomainError) do
|
416
438
|
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
|
417
439
|
end
|
440
|
+
|
441
|
+
assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
|
418
442
|
end
|
419
443
|
|
420
444
|
def test_floor
|
@@ -423,6 +447,8 @@ class StandardFiltersTest < Minitest::Test
|
|
423
447
|
assert_raises(Liquid::FloatDomainError) do
|
424
448
|
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
|
425
449
|
end
|
450
|
+
|
451
|
+
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
426
452
|
end
|
427
453
|
|
428
454
|
def test_append
|
@@ -436,8 +462,7 @@ class StandardFiltersTest < Minitest::Test
|
|
436
462
|
assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
|
437
463
|
assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
|
438
464
|
|
439
|
-
assert_raises(
|
440
|
-
# no implicit conversion of Fixnum into Array
|
465
|
+
assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
|
441
466
|
@filters.concat([1, 2], 10)
|
442
467
|
end
|
443
468
|
end
|
@@ -24,8 +24,8 @@ class RawTagTest < Minitest::Test
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_invalid_raw
|
27
|
-
assert_match_syntax_error
|
28
|
-
assert_match_syntax_error
|
29
|
-
assert_match_syntax_error
|
27
|
+
assert_match_syntax_error(/tag was never closed/, '{% raw %} foo')
|
28
|
+
assert_match_syntax_error(/Valid syntax/, '{% raw } foo {% endraw %}')
|
29
|
+
assert_match_syntax_error(/Valid syntax/, '{% raw } foo %}{% endraw %}')
|
30
30
|
end
|
31
31
|
end
|
@@ -27,6 +27,12 @@ class ErroneousDrop < Liquid::Drop
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
class DropWithUndefinedMethod < Liquid::Drop
|
31
|
+
def foo
|
32
|
+
'foo'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
30
36
|
class TemplateTest < Minitest::Test
|
31
37
|
include Liquid
|
32
38
|
|
@@ -133,6 +139,17 @@ class TemplateTest < Minitest::Test
|
|
133
139
|
refute_nil t.resource_limits.assign_score
|
134
140
|
end
|
135
141
|
|
142
|
+
def test_resource_limits_assign_score_nested
|
143
|
+
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
|
144
|
+
|
145
|
+
t.resource_limits.assign_score_limit = 3
|
146
|
+
assert_equal "Liquid error: Memory limits exceeded", t.render
|
147
|
+
assert t.resource_limits.reached?
|
148
|
+
|
149
|
+
t.resource_limits.assign_score_limit = 5
|
150
|
+
assert_equal "", t.render!
|
151
|
+
end
|
152
|
+
|
136
153
|
def test_resource_limits_aborts_rendering_after_first_error
|
137
154
|
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
138
155
|
t.resource_limits.render_score_limit = 50
|
@@ -225,4 +242,68 @@ class TemplateTest < Minitest::Test
|
|
225
242
|
|
226
243
|
assert_equal 'BOB filtered', rendered_template
|
227
244
|
end
|
245
|
+
|
246
|
+
def test_undefined_variables
|
247
|
+
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
248
|
+
result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
249
|
+
|
250
|
+
assert_equal '33 32 ', result
|
251
|
+
assert_equal 3, t.errors.count
|
252
|
+
assert_instance_of Liquid::UndefinedVariable, t.errors[0]
|
253
|
+
assert_equal 'Liquid error: undefined variable y', t.errors[0].message
|
254
|
+
assert_instance_of Liquid::UndefinedVariable, t.errors[1]
|
255
|
+
assert_equal 'Liquid error: undefined variable b', t.errors[1].message
|
256
|
+
assert_instance_of Liquid::UndefinedVariable, t.errors[2]
|
257
|
+
assert_equal 'Liquid error: undefined variable d', t.errors[2].message
|
258
|
+
end
|
259
|
+
|
260
|
+
def test_undefined_variables_raise
|
261
|
+
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
262
|
+
|
263
|
+
assert_raises UndefinedVariable do
|
264
|
+
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_undefined_drop_methods
|
269
|
+
d = DropWithUndefinedMethod.new
|
270
|
+
t = Template.new.parse('{{ foo }} {{ woot }}')
|
271
|
+
result = t.render(d, { strict_variables: true })
|
272
|
+
|
273
|
+
assert_equal 'foo ', result
|
274
|
+
assert_equal 1, t.errors.count
|
275
|
+
assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_undefined_drop_methods_raise
|
279
|
+
d = DropWithUndefinedMethod.new
|
280
|
+
t = Template.new.parse('{{ foo }} {{ woot }}')
|
281
|
+
|
282
|
+
assert_raises UndefinedDropMethod do
|
283
|
+
t.render!(d, { strict_variables: true })
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def test_undefined_filters
|
288
|
+
t = Template.parse("{{a}} {{x | upcase | somefilter1 | somefilter2 | somefilter3}}")
|
289
|
+
filters = Module.new do
|
290
|
+
def somefilter3(v)
|
291
|
+
"-#{v}-"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true })
|
295
|
+
|
296
|
+
assert_equal '123 ', result
|
297
|
+
assert_equal 1, t.errors.count
|
298
|
+
assert_instance_of Liquid::UndefinedFilter, t.errors[0]
|
299
|
+
assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
|
300
|
+
end
|
301
|
+
|
302
|
+
def test_undefined_filters_raise
|
303
|
+
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
|
304
|
+
|
305
|
+
assert_raises UndefinedFilter do
|
306
|
+
t.render!({ 'x' => 'foo' }, { strict_filters: true })
|
307
|
+
end
|
308
|
+
end
|
228
309
|
end
|
@@ -3,50 +3,54 @@ require 'test_helper'
|
|
3
3
|
class ConditionUnitTest < Minitest::Test
|
4
4
|
include Liquid
|
5
5
|
|
6
|
+
def setup
|
7
|
+
@context = Liquid::Context.new
|
8
|
+
end
|
9
|
+
|
6
10
|
def test_basic_condition
|
7
11
|
assert_equal false, Condition.new(1, '==', 2).evaluate
|
8
12
|
assert_equal true, Condition.new(1, '==', 1).evaluate
|
9
13
|
end
|
10
14
|
|
11
15
|
def test_default_operators_evalute_true
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
assert_evaluates_true 1, '==', 1
|
17
|
+
assert_evaluates_true 1, '!=', 2
|
18
|
+
assert_evaluates_true 1, '<>', 2
|
19
|
+
assert_evaluates_true 1, '<', 2
|
20
|
+
assert_evaluates_true 2, '>', 1
|
21
|
+
assert_evaluates_true 1, '>=', 1
|
22
|
+
assert_evaluates_true 2, '>=', 1
|
23
|
+
assert_evaluates_true 1, '<=', 2
|
24
|
+
assert_evaluates_true 1, '<=', 1
|
21
25
|
# negative numbers
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
assert_evaluates_true 1, '>', -1
|
27
|
+
assert_evaluates_true (-1), '<', 1
|
28
|
+
assert_evaluates_true 1.0, '>', -1.0
|
29
|
+
assert_evaluates_true (-1.0), '<', 1.0
|
26
30
|
end
|
27
31
|
|
28
32
|
def test_default_operators_evalute_false
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
assert_evaluates_false 1, '==', 2
|
34
|
+
assert_evaluates_false 1, '!=', 1
|
35
|
+
assert_evaluates_false 1, '<>', 1
|
36
|
+
assert_evaluates_false 1, '<', 0
|
37
|
+
assert_evaluates_false 2, '>', 4
|
38
|
+
assert_evaluates_false 1, '>=', 3
|
39
|
+
assert_evaluates_false 2, '>=', 4
|
40
|
+
assert_evaluates_false 1, '<=', 0
|
41
|
+
assert_evaluates_false 1, '<=', 0
|
38
42
|
end
|
39
43
|
|
40
44
|
def test_contains_works_on_strings
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
assert_evaluates_true 'bob', 'contains', 'o'
|
46
|
+
assert_evaluates_true 'bob', 'contains', 'b'
|
47
|
+
assert_evaluates_true 'bob', 'contains', 'bo'
|
48
|
+
assert_evaluates_true 'bob', 'contains', 'ob'
|
49
|
+
assert_evaluates_true 'bob', 'contains', 'bob'
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
51
|
+
assert_evaluates_false 'bob', 'contains', 'bob2'
|
52
|
+
assert_evaluates_false 'bob', 'contains', 'a'
|
53
|
+
assert_evaluates_false 'bob', 'contains', '---'
|
50
54
|
end
|
51
55
|
|
52
56
|
def test_invalid_comparation_operator
|
@@ -65,29 +69,29 @@ class ConditionUnitTest < Minitest::Test
|
|
65
69
|
@context['array'] = [1, 2, 3, 4, 5]
|
66
70
|
array_expr = VariableLookup.new("array")
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
assert_evaluates_false array_expr, 'contains', 0
|
73
|
+
assert_evaluates_true array_expr, 'contains', 1
|
74
|
+
assert_evaluates_true array_expr, 'contains', 2
|
75
|
+
assert_evaluates_true array_expr, 'contains', 3
|
76
|
+
assert_evaluates_true array_expr, 'contains', 4
|
77
|
+
assert_evaluates_true array_expr, 'contains', 5
|
78
|
+
assert_evaluates_false array_expr, 'contains', 6
|
79
|
+
assert_evaluates_false array_expr, 'contains', "1"
|
76
80
|
end
|
77
81
|
|
78
82
|
def test_contains_returns_false_for_nil_operands
|
79
83
|
@context = Liquid::Context.new
|
80
|
-
|
81
|
-
|
84
|
+
assert_evaluates_false VariableLookup.new('not_assigned'), 'contains', '0'
|
85
|
+
assert_evaluates_false 0, 'contains', VariableLookup.new('not_assigned')
|
82
86
|
end
|
83
87
|
|
84
88
|
def test_contains_return_false_on_wrong_data_type
|
85
|
-
|
89
|
+
assert_evaluates_false 1, 'contains', 0
|
86
90
|
end
|
87
91
|
|
88
92
|
def test_contains_with_string_left_operand_coerces_right_operand_to_string
|
89
|
-
|
90
|
-
|
93
|
+
assert_evaluates_true ' 1 ', 'contains', 1
|
94
|
+
assert_evaluates_false ' 1 ', 'contains', 2
|
91
95
|
end
|
92
96
|
|
93
97
|
def test_or_condition
|
@@ -121,8 +125,8 @@ class ConditionUnitTest < Minitest::Test
|
|
121
125
|
def test_should_allow_custom_proc_operator
|
122
126
|
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
123
127
|
|
124
|
-
|
125
|
-
|
128
|
+
assert_evaluates_true 'bob', 'starts_with', 'b'
|
129
|
+
assert_evaluates_false 'bob', 'starts_with', 'o'
|
126
130
|
ensure
|
127
131
|
Condition.operators.delete 'starts_with'
|
128
132
|
end
|
@@ -131,24 +135,24 @@ class ConditionUnitTest < Minitest::Test
|
|
131
135
|
@context = Liquid::Context.new
|
132
136
|
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
|
133
137
|
|
134
|
-
|
138
|
+
assert_evaluates_true VariableLookup.new("one"), '==', VariableLookup.new("another")
|
135
139
|
end
|
136
140
|
|
137
141
|
private
|
138
142
|
|
139
|
-
def
|
140
|
-
assert Condition.new(left, op, right).evaluate(@context
|
143
|
+
def assert_evaluates_true(left, op, right)
|
144
|
+
assert Condition.new(left, op, right).evaluate(@context),
|
141
145
|
"Evaluated false: #{left} #{op} #{right}"
|
142
146
|
end
|
143
147
|
|
144
|
-
def
|
145
|
-
assert !Condition.new(left, op, right).evaluate(@context
|
148
|
+
def assert_evaluates_false(left, op, right)
|
149
|
+
assert !Condition.new(left, op, right).evaluate(@context),
|
146
150
|
"Evaluated true: #{left} #{op} #{right}"
|
147
151
|
end
|
148
152
|
|
149
153
|
def assert_evaluates_argument_error(left, op, right)
|
150
154
|
assert_raises(Liquid::ArgumentError) do
|
151
|
-
Condition.new(left, op, right).evaluate(@context
|
155
|
+
Condition.new(left, op, right).evaluate(@context)
|
152
156
|
end
|
153
157
|
end
|
154
158
|
end # ConditionTest
|
@@ -77,4 +77,60 @@ class StrainerUnitTest < Minitest::Test
|
|
77
77
|
assert_kind_of b, strainer
|
78
78
|
assert_kind_of Liquid::StandardFilters, strainer
|
79
79
|
end
|
80
|
+
|
81
|
+
def test_add_filter_when_wrong_filter_class
|
82
|
+
c = Context.new
|
83
|
+
s = c.strainer
|
84
|
+
wrong_filter = ->(v) { v.reverse }
|
85
|
+
|
86
|
+
assert_raises ArgumentError do
|
87
|
+
s.class.add_filter(wrong_filter)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module PrivateMethodOverrideFilter
|
92
|
+
private
|
93
|
+
|
94
|
+
def public_filter
|
95
|
+
"overriden as private"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
|
100
|
+
strainer = Context.new.strainer
|
101
|
+
|
102
|
+
error = assert_raises(Liquid::MethodOverrideError) do
|
103
|
+
strainer.class.add_filter(PrivateMethodOverrideFilter)
|
104
|
+
end
|
105
|
+
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
|
106
|
+
end
|
107
|
+
|
108
|
+
module ProtectedMethodOverrideFilter
|
109
|
+
protected
|
110
|
+
|
111
|
+
def public_filter
|
112
|
+
"overriden as protected"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
|
117
|
+
strainer = Context.new.strainer
|
118
|
+
|
119
|
+
error = assert_raises(Liquid::MethodOverrideError) do
|
120
|
+
strainer.class.add_filter(ProtectedMethodOverrideFilter)
|
121
|
+
end
|
122
|
+
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
|
123
|
+
end
|
124
|
+
|
125
|
+
module PublicMethodOverrideFilter
|
126
|
+
def public_filter
|
127
|
+
"public"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
132
|
+
strainer = Context.new.strainer
|
133
|
+
strainer.class.add_filter(PublicMethodOverrideFilter)
|
134
|
+
assert strainer.class.filter_methods.include?('public_filter')
|
135
|
+
end
|
80
136
|
end # StrainerTest
|
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.0.
|
4
|
+
version: 4.0.0.rc2
|
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: 2016-
|
11
|
+
date: 2016-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|