liquid 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +130 -62
- data/README.md +31 -0
- data/lib/liquid/block.rb +31 -124
- data/lib/liquid/block_body.rb +75 -59
- data/lib/liquid/condition.rb +23 -22
- data/lib/liquid/context.rb +51 -60
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +17 -16
- data/lib/liquid/errors.rb +20 -24
- data/lib/liquid/expression.rb +15 -3
- data/lib/liquid/extensions.rb +13 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +5 -5
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +6 -4
- data/lib/liquid/locales/en.yml +5 -1
- data/lib/liquid/parse_context.rb +37 -0
- data/lib/liquid/parser.rb +1 -1
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +121 -61
- data/lib/liquid/strainer.rb +32 -29
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +17 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +2 -2
- data/lib/liquid/tags/case.rb +19 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +6 -6
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +95 -75
- data/lib/liquid/tags/if.rb +48 -43
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +61 -52
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +12 -31
- data/lib/liquid/tags/unless.rb +4 -5
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +46 -45
- data/lib/liquid/variable_lookup.rb +9 -5
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +9 -7
- data/test/integration/assign_test.rb +18 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/capture_test.rb +10 -0
- data/test/integration/context_test.rb +2 -2
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +42 -40
- data/test/integration/error_handling_test.rb +99 -46
- data/test/integration/filter_test.rb +72 -19
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +34 -27
- data/test/integration/parsing_quirks_test.rb +15 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +9 -7
- data/test/integration/standard_filter_test.rb +198 -42
- data/test/integration/tags/break_tag_test.rb +1 -2
- data/test/integration/tags/continue_tag_test.rb +0 -1
- data/test/integration/tags/for_tag_test.rb +133 -98
- data/test/integration/tags/if_else_tag_test.rb +96 -77
- data/test/integration/tags/include_tag_test.rb +34 -30
- data/test/integration/tags/increment_tag_test.rb +10 -11
- data/test/integration/tags/raw_tag_test.rb +7 -1
- data/test/integration/tags/standard_tag_test.rb +121 -122
- data/test/integration/tags/statements_test.rb +3 -5
- data/test/integration/tags/table_row_test.rb +20 -19
- data/test/integration/tags/unless_else_tag_test.rb +6 -6
- data/test/integration/template_test.rb +190 -49
- data/test/integration/trim_mode_test.rb +525 -0
- data/test/integration/variable_test.rb +23 -13
- data/test/test_helper.rb +44 -9
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +86 -77
- data/test/unit/context_unit_test.rb +48 -57
- data/test/unit/file_system_unit_test.rb +3 -3
- data/test/unit/i18n_unit_test.rb +2 -2
- data/test/unit/lexer_unit_test.rb +11 -8
- data/test/unit/parser_unit_test.rb +2 -2
- data/test/unit/regexp_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +85 -8
- data/test/unit/tag_unit_test.rb +7 -2
- data/test/unit/tags/case_tag_unit_test.rb +1 -1
- data/test/unit/tags/for_tag_unit_test.rb +2 -2
- data/test/unit/tags/if_tag_unit_test.rb +1 -1
- data/test/unit/template_unit_test.rb +14 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +66 -43
- metadata +55 -50
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/token.rb +0 -18
- data/test/unit/module_ex_unit_test.rb +0 -87
- /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -6,10 +6,10 @@ module Liquid
|
|
6
6
|
super
|
7
7
|
if markup =~ Syntax
|
8
8
|
@variable_name = $1
|
9
|
-
@collection_name = $2
|
9
|
+
@collection_name = Expression.parse($2)
|
10
10
|
@attributes = {}
|
11
11
|
markup.scan(TagAttributes) do |key, value|
|
12
|
-
@attributes[key] = value
|
12
|
+
@attributes[key] = Expression.parse(value)
|
13
13
|
end
|
14
14
|
else
|
15
15
|
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
|
@@ -17,51 +17,32 @@ module Liquid
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def render(context)
|
20
|
-
collection = context
|
20
|
+
collection = context.evaluate(@collection_name) or return ''.freeze
|
21
21
|
|
22
|
-
from = @attributes
|
23
|
-
to = @attributes
|
22
|
+
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
|
23
|
+
to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
|
24
24
|
|
25
25
|
collection = Utils.slice_collection(collection, from, to)
|
26
26
|
|
27
27
|
length = collection.length
|
28
28
|
|
29
|
-
cols = context
|
30
|
-
|
31
|
-
row = 1
|
32
|
-
col = 0
|
29
|
+
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
33
30
|
|
34
31
|
result = "<tr class=\"row1\">\n"
|
35
32
|
context.stack do
|
33
|
+
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
34
|
+
context['tablerowloop'.freeze] = tablerowloop
|
36
35
|
|
37
36
|
collection.each_with_index do |item, index|
|
38
37
|
context[@variable_name] = item
|
39
|
-
context['tablerowloop'.freeze] = {
|
40
|
-
'length'.freeze => length,
|
41
|
-
'index'.freeze => index + 1,
|
42
|
-
'index0'.freeze => index,
|
43
|
-
'col'.freeze => col + 1,
|
44
|
-
'col0'.freeze => col,
|
45
|
-
'index0'.freeze => index,
|
46
|
-
'rindex'.freeze => length - index,
|
47
|
-
'rindex0'.freeze => length - index - 1,
|
48
|
-
'first'.freeze => (index == 0),
|
49
|
-
'last'.freeze => (index == length - 1),
|
50
|
-
'col_first'.freeze => (col == 0),
|
51
|
-
'col_last'.freeze => (col == cols - 1)
|
52
|
-
}
|
53
|
-
|
54
|
-
|
55
|
-
col += 1
|
56
38
|
|
57
|
-
result << "<td class=\"col#{col}\">" << super << '</td>'
|
39
|
+
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
58
40
|
|
59
|
-
if
|
60
|
-
|
61
|
-
row += 1
|
62
|
-
result << "</tr>\n<tr class=\"row#{row}\">"
|
41
|
+
if tablerowloop.col_last && !tablerowloop.last
|
42
|
+
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
63
43
|
end
|
64
44
|
|
45
|
+
tablerowloop.send(:increment!)
|
65
46
|
end
|
66
47
|
end
|
67
48
|
result << "</tr>\n"
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -1,24 +1,23 @@
|
|
1
|
-
|
1
|
+
require_relative 'if'
|
2
2
|
|
3
3
|
module Liquid
|
4
4
|
# Unless is a conditional just like 'if' but works on the inverse logic.
|
5
5
|
#
|
6
|
-
# {% unless x < 0 %} x is greater than zero {%
|
6
|
+
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
7
7
|
#
|
8
8
|
class Unless < If
|
9
9
|
def render(context)
|
10
10
|
context.stack do
|
11
|
-
|
12
11
|
# First condition is interpreted backwards ( if not )
|
13
12
|
first_block = @blocks.first
|
14
13
|
unless first_block.evaluate(context)
|
15
|
-
return
|
14
|
+
return first_block.attachment.render(context)
|
16
15
|
end
|
17
16
|
|
18
17
|
# After the first condition unless works just like if
|
19
18
|
@blocks[1..-1].each do |block|
|
20
19
|
if block.evaluate(context)
|
21
|
-
return
|
20
|
+
return block.attachment.render(context)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
data/lib/liquid/template.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# Templates are central to liquid.
|
4
3
|
# Interpretating templates is a two step process. First you compile the
|
5
4
|
# source code you got. During compile time some extensive error checking is performed.
|
@@ -14,21 +13,21 @@ module Liquid
|
|
14
13
|
# template.render('user_name' => 'bob')
|
15
14
|
#
|
16
15
|
class Template
|
17
|
-
|
18
|
-
|
19
|
-
}
|
16
|
+
attr_accessor :root
|
17
|
+
attr_reader :resource_limits, :warnings
|
20
18
|
|
21
|
-
attr_accessor :root, :resource_limits
|
22
19
|
@@file_system = BlankFileSystem.new
|
23
20
|
|
24
21
|
class TagRegistry
|
22
|
+
include Enumerable
|
23
|
+
|
25
24
|
def initialize
|
26
|
-
@tags
|
25
|
+
@tags = {}
|
27
26
|
@cache = {}
|
28
27
|
end
|
29
28
|
|
30
29
|
def [](tag_name)
|
31
|
-
return nil unless @tags.
|
30
|
+
return nil unless @tags.key?(tag_name)
|
32
31
|
return @cache[tag_name] if Liquid.cache_classes
|
33
32
|
|
34
33
|
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
@@ -44,6 +43,10 @@ module Liquid
|
|
44
43
|
@cache.delete(tag_name)
|
45
44
|
end
|
46
45
|
|
46
|
+
def each(&block)
|
47
|
+
@tags.each(&block)
|
48
|
+
end
|
49
|
+
|
47
50
|
private
|
48
51
|
|
49
52
|
def lookup_class(name)
|
@@ -66,6 +69,11 @@ module Liquid
|
|
66
69
|
# :error raises an error when tainted output is used
|
67
70
|
attr_writer :taint_mode
|
68
71
|
|
72
|
+
attr_accessor :default_exception_renderer
|
73
|
+
Template.default_exception_renderer = lambda do |exception|
|
74
|
+
exception
|
75
|
+
end
|
76
|
+
|
69
77
|
def file_system
|
70
78
|
@@file_system
|
71
79
|
end
|
@@ -83,11 +91,11 @@ module Liquid
|
|
83
91
|
end
|
84
92
|
|
85
93
|
def error_mode
|
86
|
-
@error_mode
|
94
|
+
@error_mode ||= :lax
|
87
95
|
end
|
88
96
|
|
89
97
|
def taint_mode
|
90
|
-
@taint_mode
|
98
|
+
@taint_mode ||= :lax
|
91
99
|
end
|
92
100
|
|
93
101
|
# Pass a module with filter methods which should be available
|
@@ -110,7 +118,8 @@ module Liquid
|
|
110
118
|
end
|
111
119
|
|
112
120
|
def initialize
|
113
|
-
@
|
121
|
+
@rethrow_errors = false
|
122
|
+
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
114
123
|
end
|
115
124
|
|
116
125
|
# Parse source code.
|
@@ -119,16 +128,12 @@ module Liquid
|
|
119
128
|
@options = options
|
120
129
|
@profiling = options[:profile]
|
121
130
|
@line_numbers = options[:line_numbers] || @profiling
|
122
|
-
|
123
|
-
@
|
131
|
+
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
132
|
+
@root = Document.parse(tokenize(source), parse_context)
|
133
|
+
@warnings = parse_context.warnings
|
124
134
|
self
|
125
135
|
end
|
126
136
|
|
127
|
-
def warnings
|
128
|
-
return [] unless @root
|
129
|
-
@warnings ||= @root.warnings
|
130
|
-
end
|
131
|
-
|
132
137
|
def registers
|
133
138
|
@registers ||= {}
|
134
139
|
end
|
@@ -167,7 +172,7 @@ module Liquid
|
|
167
172
|
c = args.shift
|
168
173
|
|
169
174
|
if @rethrow_errors
|
170
|
-
c.
|
175
|
+
c.exception_renderer = ->(e) { raise }
|
171
176
|
end
|
172
177
|
|
173
178
|
c
|
@@ -186,27 +191,20 @@ module Liquid
|
|
186
191
|
when Hash
|
187
192
|
options = args.pop
|
188
193
|
|
189
|
-
if options[:registers].is_a?(Hash)
|
190
|
-
self.registers.merge!(options[:registers])
|
191
|
-
end
|
192
|
-
|
193
|
-
if options[:filters]
|
194
|
-
context.add_filters(options[:filters])
|
195
|
-
end
|
194
|
+
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
196
195
|
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
when Module
|
201
|
-
context.add_filters(args.pop)
|
202
|
-
when Array
|
196
|
+
apply_options_to_context(context, options)
|
197
|
+
when Module, Array
|
203
198
|
context.add_filters(args.pop)
|
204
199
|
end
|
205
200
|
|
201
|
+
# Retrying a render resets resource usage
|
202
|
+
context.resource_limits.reset
|
203
|
+
|
206
204
|
begin
|
207
205
|
# render the nodelist.
|
208
206
|
# for performance reasons we get an array back here. join will make a string out of it.
|
209
|
-
result = with_profiling do
|
207
|
+
result = with_profiling(context) do
|
210
208
|
@root.render(context)
|
211
209
|
end
|
212
210
|
result.respond_to?(:join) ? result.join : result
|
@@ -224,32 +222,14 @@ module Liquid
|
|
224
222
|
|
225
223
|
private
|
226
224
|
|
227
|
-
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
228
225
|
def tokenize(source)
|
229
|
-
|
230
|
-
return [] if source.to_s.empty?
|
231
|
-
|
232
|
-
tokens = calculate_line_numbers(source.split(TemplateParser))
|
233
|
-
|
234
|
-
# removes the rogue empty element at the beginning of the array
|
235
|
-
tokens.shift if tokens[0] and tokens[0].empty?
|
236
|
-
|
237
|
-
tokens
|
226
|
+
Tokenizer.new(source, @line_numbers)
|
238
227
|
end
|
239
228
|
|
240
|
-
def
|
241
|
-
|
229
|
+
def with_profiling(context)
|
230
|
+
if @profiling && !context.partial
|
231
|
+
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
242
232
|
|
243
|
-
current_line = 1
|
244
|
-
raw_tokens.map do |token|
|
245
|
-
Token.new(token, current_line).tap do
|
246
|
-
current_line += token.count("\n")
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
def with_profiling
|
252
|
-
if @profiling && !@options[:included]
|
253
233
|
@profiler = Profiler.new
|
254
234
|
@profiler.start
|
255
235
|
|
@@ -262,5 +242,13 @@ module Liquid
|
|
262
242
|
yield
|
263
243
|
end
|
264
244
|
end
|
245
|
+
|
246
|
+
def apply_options_to_context(context, options)
|
247
|
+
context.add_filters(options[:filters]) if options[:filters]
|
248
|
+
context.global_filter = options[:global_filter] if options[:global_filter]
|
249
|
+
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
250
|
+
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
251
|
+
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
252
|
+
end
|
265
253
|
end
|
266
254
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Liquid
|
2
|
+
class Tokenizer
|
3
|
+
attr_reader :line_number
|
4
|
+
|
5
|
+
def initialize(source, line_numbers = false)
|
6
|
+
@source = source
|
7
|
+
@line_number = line_numbers ? 1 : nil
|
8
|
+
@tokens = tokenize
|
9
|
+
end
|
10
|
+
|
11
|
+
def shift
|
12
|
+
token = @tokens.shift
|
13
|
+
@line_number += token.count("\n") if @line_number && token
|
14
|
+
token
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def tokenize
|
20
|
+
@source = @source.source if @source.respond_to?(:source)
|
21
|
+
return [] if @source.to_s.empty?
|
22
|
+
|
23
|
+
tokens = @source.split(TemplateParser)
|
24
|
+
|
25
|
+
# removes the rogue empty element at the beginning of the array
|
26
|
+
tokens.shift if tokens[0] && tokens[0].empty?
|
27
|
+
|
28
|
+
tokens
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/liquid/utils.rb
CHANGED
@@ -1,27 +1,24 @@
|
|
1
1
|
module Liquid
|
2
2
|
module Utils
|
3
|
-
|
4
3
|
def self.slice_collection(collection, from, to)
|
5
|
-
if (from != 0 || to
|
4
|
+
if (from != 0 || !to.nil?) && collection.respond_to?(:load_slice)
|
6
5
|
collection.load_slice(from, to)
|
7
6
|
else
|
8
7
|
slice_collection_using_each(collection, from, to)
|
9
8
|
end
|
10
9
|
end
|
11
10
|
|
12
|
-
def self.non_blank_string?(collection)
|
13
|
-
collection.is_a?(String) && collection != ''.freeze
|
14
|
-
end
|
15
|
-
|
16
11
|
def self.slice_collection_using_each(collection, from, to)
|
17
12
|
segments = []
|
18
13
|
index = 0
|
19
14
|
|
20
15
|
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
21
|
-
|
16
|
+
if collection.is_a?(String)
|
17
|
+
return collection.empty? ? [] : [collection]
|
18
|
+
end
|
19
|
+
return [] unless collection.respond_to?(:each)
|
22
20
|
|
23
21
|
collection.each do |item|
|
24
|
-
|
25
22
|
if to && to <= index
|
26
23
|
break
|
27
24
|
end
|
@@ -35,5 +32,52 @@ module Liquid
|
|
35
32
|
|
36
33
|
segments
|
37
34
|
end
|
35
|
+
|
36
|
+
def self.to_integer(num)
|
37
|
+
return num if num.is_a?(Integer)
|
38
|
+
num = num.to_s
|
39
|
+
begin
|
40
|
+
Integer(num)
|
41
|
+
rescue ::ArgumentError
|
42
|
+
raise Liquid::ArgumentError, "invalid integer"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.to_number(obj)
|
47
|
+
case obj
|
48
|
+
when Float
|
49
|
+
BigDecimal.new(obj.to_s)
|
50
|
+
when Numeric
|
51
|
+
obj
|
52
|
+
when String
|
53
|
+
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
54
|
+
else
|
55
|
+
if obj.respond_to?(:to_number)
|
56
|
+
obj.to_number
|
57
|
+
else
|
58
|
+
0
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.to_date(obj)
|
64
|
+
return obj if obj.respond_to?(:strftime)
|
65
|
+
|
66
|
+
if obj.is_a?(String)
|
67
|
+
return nil if obj.empty?
|
68
|
+
obj = obj.downcase
|
69
|
+
end
|
70
|
+
|
71
|
+
case obj
|
72
|
+
when 'now'.freeze, 'today'.freeze
|
73
|
+
Time.now
|
74
|
+
when /\A\d+\z/, Integer
|
75
|
+
Time.at(obj.to_i)
|
76
|
+
when String
|
77
|
+
Time.parse(obj)
|
78
|
+
end
|
79
|
+
rescue ::ArgumentError
|
80
|
+
nil
|
81
|
+
end
|
38
82
|
end
|
39
83
|
end
|
data/lib/liquid/variable.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# Holds variables. Variables are only loaded "just in time"
|
4
3
|
# and are not evaluated as part of the render stage
|
5
4
|
#
|
@@ -12,15 +11,16 @@ module Liquid
|
|
12
11
|
#
|
13
12
|
class Variable
|
14
13
|
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
attr_accessor :filters, :name, :line_number
|
15
|
+
attr_reader :parse_context
|
16
|
+
alias_method :options, :parse_context
|
18
17
|
include ParserSwitching
|
19
18
|
|
20
|
-
def initialize(markup,
|
19
|
+
def initialize(markup, parse_context)
|
21
20
|
@markup = markup
|
22
21
|
@name = nil
|
23
|
-
@
|
22
|
+
@parse_context = parse_context
|
23
|
+
@line_number = parse_context.line_number
|
24
24
|
|
25
25
|
parse_with_selected_parser(markup)
|
26
26
|
end
|
@@ -35,35 +35,27 @@ module Liquid
|
|
35
35
|
|
36
36
|
def lax_parse(markup)
|
37
37
|
@filters = []
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
38
|
+
return unless markup =~ /(#{QuotedFragment})(.*)/om
|
39
|
+
|
40
|
+
name_markup = $1
|
41
|
+
filter_markup = $2
|
42
|
+
@name = Expression.parse(name_markup)
|
43
|
+
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
|
44
|
+
filters = $1.scan(FilterParser)
|
45
|
+
filters.each do |f|
|
46
|
+
next unless f =~ /\w+/
|
47
|
+
filtername = Regexp.last_match(0)
|
48
|
+
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
49
|
+
@filters << parse_filter_expressions(filtername, filterargs)
|
51
50
|
end
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
55
54
|
def strict_parse(markup)
|
56
|
-
# Very simple valid cases
|
57
|
-
if markup =~ EasyParse
|
58
|
-
@name = Expression.parse($1)
|
59
|
-
@filters = []
|
60
|
-
return
|
61
|
-
end
|
62
|
-
|
63
55
|
@filters = []
|
64
56
|
p = Parser.new(markup)
|
65
|
-
|
66
|
-
@name =
|
57
|
+
|
58
|
+
@name = Expression.parse(p.expression)
|
67
59
|
while p.consume?(:pipe)
|
68
60
|
filtername = p.consume(:id)
|
69
61
|
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
@@ -76,17 +68,21 @@ module Liquid
|
|
76
68
|
# first argument
|
77
69
|
filterargs = [p.argument]
|
78
70
|
# followed by comma separated others
|
79
|
-
while p.consume?(:comma)
|
80
|
-
filterargs << p.argument
|
81
|
-
end
|
71
|
+
filterargs << p.argument while p.consume?(:comma)
|
82
72
|
filterargs
|
83
73
|
end
|
84
74
|
|
85
75
|
def render(context)
|
86
|
-
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
76
|
+
obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
87
77
|
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
88
|
-
|
89
|
-
end
|
78
|
+
context.invoke(filter_name, output, *filter_args)
|
79
|
+
end
|
80
|
+
|
81
|
+
obj = context.apply_global_filter(obj)
|
82
|
+
|
83
|
+
taint_check(context, obj)
|
84
|
+
|
85
|
+
obj
|
90
86
|
end
|
91
87
|
|
92
88
|
private
|
@@ -118,17 +114,22 @@ module Liquid
|
|
118
114
|
parsed_args
|
119
115
|
end
|
120
116
|
|
121
|
-
def taint_check(obj)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
117
|
+
def taint_check(context, obj)
|
118
|
+
return unless obj.tainted?
|
119
|
+
return if Template.taint_mode == :lax
|
120
|
+
|
121
|
+
@markup =~ QuotedFragment
|
122
|
+
name = Regexp.last_match(0)
|
123
|
+
|
124
|
+
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
|
125
|
+
error.line_number = line_number
|
126
|
+
error.template_name = context.template_name
|
127
|
+
|
128
|
+
case Template.taint_mode
|
129
|
+
when :warn
|
130
|
+
context.warnings << error
|
131
|
+
when :error
|
132
|
+
raise error
|
132
133
|
end
|
133
134
|
end
|
134
135
|
end
|
@@ -3,6 +3,8 @@ module Liquid
|
|
3
3
|
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
4
4
|
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
5
5
|
|
6
|
+
attr_reader :name, :lookups
|
7
|
+
|
6
8
|
def self.parse(markup)
|
7
9
|
new(markup)
|
8
10
|
end
|
@@ -39,8 +41,8 @@ module Liquid
|
|
39
41
|
# If object is a hash- or array-like object we look for the
|
40
42
|
# presence of the key and if its available we return it
|
41
43
|
if object.respond_to?(:[]) &&
|
42
|
-
|
43
|
-
|
44
|
+
((object.respond_to?(:key?) && object.key?(key)) ||
|
45
|
+
(object.respond_to?(:fetch) && key.is_a?(Integer)))
|
44
46
|
|
45
47
|
# if its a proc we will replace the entry with the proc
|
46
48
|
res = context.lookup_and_evaluate(object, key)
|
@@ -53,9 +55,11 @@ module Liquid
|
|
53
55
|
object = object.send(key).to_liquid
|
54
56
|
|
55
57
|
# No key was present with the desired value and it wasn't one of the directly supported
|
56
|
-
# 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
|
57
60
|
else
|
58
|
-
return nil
|
61
|
+
return nil unless context.strict_variables
|
62
|
+
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
59
63
|
end
|
60
64
|
|
61
65
|
# If we are dealing with a drop here we have to
|
@@ -66,7 +70,7 @@ module Liquid
|
|
66
70
|
end
|
67
71
|
|
68
72
|
def ==(other)
|
69
|
-
self.class == other.class &&
|
73
|
+
self.class == other.class && state == other.state
|
70
74
|
end
|
71
75
|
|
72
76
|
protected
|
data/lib/liquid/version.rb
CHANGED
data/lib/liquid.rb
CHANGED
@@ -24,6 +24,7 @@ module Liquid
|
|
24
24
|
ArgumentSeparator = ','.freeze
|
25
25
|
FilterArgumentSeparator = ':'.freeze
|
26
26
|
VariableAttributeSeparator = '.'.freeze
|
27
|
+
WhitespaceControl = '-'.freeze
|
27
28
|
TagStart = /\{\%/
|
28
29
|
TagEnd = /\%\}/
|
29
30
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
@@ -34,7 +35,7 @@ module Liquid
|
|
34
35
|
QuotedString = /"[^"]*"|'[^']*'/
|
35
36
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
36
37
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
37
|
-
AnyStartingTag =
|
38
|
+
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
38
39
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
39
40
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
40
41
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
@@ -48,6 +49,8 @@ require 'liquid/lexer'
|
|
48
49
|
require 'liquid/parser'
|
49
50
|
require 'liquid/i18n'
|
50
51
|
require 'liquid/drop'
|
52
|
+
require 'liquid/tablerowloop_drop'
|
53
|
+
require 'liquid/forloop_drop'
|
51
54
|
require 'liquid/extensions'
|
52
55
|
require 'liquid/errors'
|
53
56
|
require 'liquid/interrupts'
|
@@ -57,21 +60,20 @@ require 'liquid/context'
|
|
57
60
|
require 'liquid/parser_switching'
|
58
61
|
require 'liquid/tag'
|
59
62
|
require 'liquid/block'
|
63
|
+
require 'liquid/block_body'
|
60
64
|
require 'liquid/document'
|
61
65
|
require 'liquid/variable'
|
62
66
|
require 'liquid/variable_lookup'
|
63
67
|
require 'liquid/range_lookup'
|
64
68
|
require 'liquid/file_system'
|
69
|
+
require 'liquid/resource_limits'
|
65
70
|
require 'liquid/template'
|
66
71
|
require 'liquid/standardfilters'
|
67
72
|
require 'liquid/condition'
|
68
|
-
require 'liquid/module_ex'
|
69
73
|
require 'liquid/utils'
|
70
|
-
require 'liquid/
|
74
|
+
require 'liquid/tokenizer'
|
75
|
+
require 'liquid/parse_context'
|
71
76
|
|
72
77
|
# Load all the tags of the standard library
|
73
78
|
#
|
74
|
-
Dir[
|
75
|
-
|
76
|
-
require 'liquid/profiler'
|
77
|
-
require 'liquid/profiler/hooks'
|
79
|
+
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|