liquid 3.0.6 → 4.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +89 -58
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/lib/liquid.rb +7 -6
- data/lib/liquid/block.rb +31 -124
- data/lib/liquid/block_body.rb +54 -57
- data/lib/liquid/condition.rb +23 -22
- data/lib/liquid/context.rb +50 -42
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +12 -13
- data/lib/liquid/errors.rb +16 -17
- data/lib/liquid/expression.rb +15 -3
- data/lib/liquid/extensions.rb +7 -7
- data/lib/liquid/file_system.rb +3 -3
- 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 +3 -1
- data/lib/liquid/parse_context.rb +37 -0
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +101 -56
- data/lib/liquid/strainer.rb +4 -5
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +5 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- 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 +93 -75
- data/lib/liquid/tags/if.rb +49 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +60 -52
- data/lib/liquid/tags/raw.rb +26 -4
- data/lib/liquid/tags/table_row.rb +12 -30
- data/lib/liquid/tags/unless.rb +3 -4
- data/lib/liquid/template.rb +23 -50
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +48 -8
- data/lib/liquid/variable.rb +46 -45
- data/lib/liquid/variable_lookup.rb +3 -3
- data/lib/liquid/version.rb +1 -1
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- 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 +64 -45
- data/test/integration/filter_test.rb +60 -20
- data/test/integration/output_test.rb +26 -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 +5 -7
- data/test/integration/standard_filter_test.rb +119 -37
- 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 +75 -77
- data/test/integration/tags/include_tag_test.rb +23 -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 +91 -45
- data/test/integration/variable_test.rb +23 -13
- data/test/test_helper.rb +33 -5
- data/test/unit/block_unit_test.rb +6 -5
- data/test/unit/condition_unit_test.rb +82 -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 +13 -2
- 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 +6 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +60 -43
- metadata +44 -41
- 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/lib/liquid/tags/include.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# Include allows templates to relate with other templates
|
4
3
|
#
|
5
4
|
# Simply include another template:
|
@@ -22,12 +21,15 @@ module Liquid
|
|
22
21
|
|
23
22
|
if markup =~ Syntax
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
template_name = $1
|
25
|
+
variable_name = $3
|
26
|
+
|
27
|
+
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
28
|
+
@template_name_expr = Expression.parse(template_name)
|
29
|
+
@attributes = {}
|
28
30
|
|
29
31
|
markup.scan(TagAttributes) do |key, value|
|
30
|
-
@attributes[key] = value
|
32
|
+
@attributes[key] = Expression.parse(value)
|
31
33
|
end
|
32
34
|
|
33
35
|
else
|
@@ -35,69 +37,75 @@ module Liquid
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
def parse(
|
40
|
+
def parse(_tokens)
|
39
41
|
end
|
40
42
|
|
41
43
|
def render(context)
|
42
|
-
|
43
|
-
|
44
|
+
template_name = context.evaluate(@template_name_expr)
|
45
|
+
partial = load_cached_partial(template_name, context)
|
44
46
|
|
45
|
-
|
46
|
-
@attributes.each do |key, value|
|
47
|
-
context[key] = context[value]
|
48
|
-
end
|
47
|
+
context_variable_name = template_name.split('/'.freeze).last
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
variable = if @variable_name_expr
|
50
|
+
context.evaluate(@variable_name_expr)
|
51
|
+
else
|
52
|
+
context.find_variable(template_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
old_template_name = context.template_name
|
56
|
+
old_partial = context.partial
|
57
|
+
begin
|
58
|
+
context.template_name = template_name
|
59
|
+
context.partial = true
|
60
|
+
context.stack do
|
61
|
+
@attributes.each do |key, value|
|
62
|
+
context[key] = context.evaluate(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
if variable.is_a?(Array)
|
66
|
+
variable.collect do |var|
|
67
|
+
context[context_variable_name] = var
|
68
|
+
partial.render(context)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
context[context_variable_name] = variable
|
54
72
|
partial.render(context)
|
55
73
|
end
|
56
|
-
else
|
57
|
-
context[context_variable_name] = variable
|
58
|
-
partial.render(context)
|
59
74
|
end
|
75
|
+
ensure
|
76
|
+
context.template_name = old_template_name
|
77
|
+
context.partial = old_partial
|
60
78
|
end
|
61
79
|
end
|
62
80
|
|
63
81
|
private
|
64
|
-
def load_cached_partial(context)
|
65
|
-
cached_partials = context.registers[:cached_partials] || {}
|
66
|
-
template_name = context[@template_name]
|
67
82
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
source = read_template_from_file_system(context)
|
72
|
-
partial = Liquid::Template.parse(source, pass_options)
|
73
|
-
cached_partials[template_name] = partial
|
74
|
-
context.registers[:cached_partials] = cached_partials
|
75
|
-
partial
|
76
|
-
end
|
83
|
+
alias_method :parse_context, :options
|
84
|
+
private :parse_context
|
77
85
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# make read_template_file call backwards-compatible.
|
82
|
-
case file_system.method(:read_template_file).arity
|
83
|
-
when 1
|
84
|
-
file_system.read_template_file(context[@template_name])
|
85
|
-
when 2
|
86
|
-
file_system.read_template_file(context[@template_name], context)
|
87
|
-
else
|
88
|
-
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
89
|
-
end
|
90
|
-
end
|
86
|
+
def load_cached_partial(template_name, context)
|
87
|
+
cached_partials = context.registers[:cached_partials] || {}
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
89
|
+
if cached = cached_partials[template_name]
|
90
|
+
return cached
|
91
|
+
end
|
92
|
+
source = read_template_from_file_system(context)
|
93
|
+
begin
|
94
|
+
parse_context.partial = true
|
95
|
+
partial = Liquid::Template.parse(source, parse_context)
|
96
|
+
ensure
|
97
|
+
parse_context.partial = false
|
100
98
|
end
|
99
|
+
cached_partials[template_name] = partial
|
100
|
+
context.registers[:cached_partials] = cached_partials
|
101
|
+
partial
|
102
|
+
end
|
103
|
+
|
104
|
+
def read_template_from_file_system(context)
|
105
|
+
file_system = context.registers[:file_system] || Liquid::Template.file_system
|
106
|
+
|
107
|
+
file_system.read_template_file(context.evaluate(@template_name_expr))
|
108
|
+
end
|
101
109
|
end
|
102
110
|
|
103
111
|
Template.register_tag('include'.freeze, Include)
|
data/lib/liquid/tags/raw.rb
CHANGED
@@ -1,17 +1,39 @@
|
|
1
1
|
module Liquid
|
2
2
|
class Raw < Block
|
3
|
+
Syntax = /\A\s*\z/
|
3
4
|
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
4
5
|
|
6
|
+
def initialize(tag_name, markup, parse_context)
|
7
|
+
super
|
8
|
+
|
9
|
+
unless markup =~ Syntax
|
10
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
5
14
|
def parse(tokens)
|
6
|
-
@
|
7
|
-
@nodelist.clear
|
15
|
+
@body = ''
|
8
16
|
while token = tokens.shift
|
9
17
|
if token =~ FullTokenPossiblyInvalid
|
10
|
-
@
|
18
|
+
@body << $1 if $1 != "".freeze
|
11
19
|
return if block_delimiter == $2
|
12
20
|
end
|
13
|
-
@
|
21
|
+
@body << token unless token.empty?
|
14
22
|
end
|
23
|
+
|
24
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
25
|
+
end
|
26
|
+
|
27
|
+
def render(_context)
|
28
|
+
@body
|
29
|
+
end
|
30
|
+
|
31
|
+
def nodelist
|
32
|
+
[@body]
|
33
|
+
end
|
34
|
+
|
35
|
+
def blank?
|
36
|
+
@body.empty?
|
15
37
|
end
|
16
38
|
end
|
17
39
|
|
@@ -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,50 +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
|
-
'rindex'.freeze => length - index,
|
46
|
-
'rindex0'.freeze => length - index - 1,
|
47
|
-
'first'.freeze => (index == 0),
|
48
|
-
'last'.freeze => (index == length - 1),
|
49
|
-
'col_first'.freeze => (col == 0),
|
50
|
-
'col_last'.freeze => (col == cols - 1)
|
51
|
-
}
|
52
|
-
|
53
|
-
|
54
|
-
col += 1
|
55
38
|
|
56
|
-
result << "<td class=\"col#{col}\">" << super << '</td>'
|
39
|
+
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
57
40
|
|
58
|
-
if
|
59
|
-
|
60
|
-
row += 1
|
61
|
-
result << "</tr>\n<tr class=\"row#{row}\">"
|
41
|
+
if tablerowloop.col_last && !tablerowloop.last
|
42
|
+
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
62
43
|
end
|
63
44
|
|
45
|
+
tablerowloop.send(:increment!)
|
64
46
|
end
|
65
47
|
end
|
66
48
|
result << "</tr>\n"
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
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.
|
@@ -8,17 +8,16 @@ module Liquid
|
|
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,11 +13,9 @@ 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
|
@@ -28,7 +25,7 @@ module Liquid
|
|
28
25
|
end
|
29
26
|
|
30
27
|
def [](tag_name)
|
31
|
-
return nil unless @tags.
|
28
|
+
return nil unless @tags.key?(tag_name)
|
32
29
|
return @cache[tag_name] if Liquid.cache_classes
|
33
30
|
|
34
31
|
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
@@ -110,7 +107,7 @@ module Liquid
|
|
110
107
|
end
|
111
108
|
|
112
109
|
def initialize
|
113
|
-
@resource_limits = self.class.default_resource_limits
|
110
|
+
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
114
111
|
end
|
115
112
|
|
116
113
|
# Parse source code.
|
@@ -119,16 +116,12 @@ module Liquid
|
|
119
116
|
@options = options
|
120
117
|
@profiling = options[:profile]
|
121
118
|
@line_numbers = options[:line_numbers] || @profiling
|
122
|
-
|
123
|
-
@
|
119
|
+
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
120
|
+
@root = Document.parse(tokenize(source), parse_context)
|
121
|
+
@warnings = parse_context.warnings
|
124
122
|
self
|
125
123
|
end
|
126
124
|
|
127
|
-
def warnings
|
128
|
-
return [] unless @root
|
129
|
-
@warnings ||= @root.warnings
|
130
|
-
end
|
131
|
-
|
132
125
|
def registers
|
133
126
|
@registers ||= {}
|
134
127
|
end
|
@@ -167,7 +160,7 @@ module Liquid
|
|
167
160
|
c = args.shift
|
168
161
|
|
169
162
|
if @rethrow_errors
|
170
|
-
c.exception_handler = ->(e) {
|
163
|
+
c.exception_handler = ->(e) { raise }
|
171
164
|
end
|
172
165
|
|
173
166
|
c
|
@@ -186,27 +179,25 @@ module Liquid
|
|
186
179
|
when Hash
|
187
180
|
options = args.pop
|
188
181
|
|
189
|
-
if options[:registers].is_a?(Hash)
|
190
|
-
self.registers.merge!(options[:registers])
|
191
|
-
end
|
182
|
+
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
192
183
|
|
193
|
-
if options[:filters]
|
194
|
-
context.add_filters(options[:filters])
|
195
|
-
end
|
184
|
+
context.add_filters(options[:filters]) if options[:filters]
|
196
185
|
|
197
|
-
if options[:
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
when Array
|
186
|
+
context.global_filter = options[:global_filter] if options[:global_filter]
|
187
|
+
|
188
|
+
context.exception_handler = options[:exception_handler] if options[:exception_handler]
|
189
|
+
|
190
|
+
when Module, Array
|
203
191
|
context.add_filters(args.pop)
|
204
192
|
end
|
205
193
|
|
194
|
+
# Retrying a render resets resource usage
|
195
|
+
context.resource_limits.reset
|
196
|
+
|
206
197
|
begin
|
207
198
|
# render the nodelist.
|
208
199
|
# for performance reasons we get an array back here. join will make a string out of it.
|
209
|
-
result = with_profiling do
|
200
|
+
result = with_profiling(context) do
|
210
201
|
@root.render(context)
|
211
202
|
end
|
212
203
|
result.respond_to?(:join) ? result.join : result
|
@@ -224,32 +215,14 @@ module Liquid
|
|
224
215
|
|
225
216
|
private
|
226
217
|
|
227
|
-
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
228
218
|
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
|
219
|
+
Tokenizer.new(source, @line_numbers)
|
238
220
|
end
|
239
221
|
|
240
|
-
def
|
241
|
-
|
242
|
-
|
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
|
222
|
+
def with_profiling(context)
|
223
|
+
if @profiling && !context.partial
|
224
|
+
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
250
225
|
|
251
|
-
def with_profiling
|
252
|
-
if @profiling && !@options[:included]
|
253
226
|
@profiler = Profiler.new
|
254
227
|
@profiler.start
|
255
228
|
|
@@ -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 = 1 if line_numbers
|
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
|