liquid 2.6.1 → 4.0.3
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 +5 -5
- data/History.md +194 -29
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +60 -2
- data/lib/liquid.rb +25 -14
- data/lib/liquid/block.rb +47 -96
- data/lib/liquid/block_body.rb +143 -0
- data/lib/liquid/condition.rb +70 -39
- data/lib/liquid/context.rb +116 -157
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +31 -14
- data/lib/liquid/errors.rb +54 -10
- data/lib/liquid/expression.rb +49 -0
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +25 -14
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +2 -3
- data/lib/liquid/lexer.rb +55 -0
- data/lib/liquid/locales/en.yml +26 -0
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +90 -0
- data/lib/liquid/parser_switching.rb +31 -0
- data/lib/liquid/profiler.rb +158 -0
- data/lib/liquid/profiler/hooks.rb +23 -0
- data/lib/liquid/range_lookup.rb +37 -0
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +311 -77
- data/lib/liquid/strainer.rb +39 -26
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +28 -11
- data/lib/liquid/tags/assign.rb +34 -10
- data/lib/liquid/tags/break.rb +1 -4
- data/lib/liquid/tags/capture.rb +11 -9
- data/lib/liquid/tags/case.rb +37 -22
- data/lib/liquid/tags/comment.rb +10 -3
- data/lib/liquid/tags/continue.rb +1 -4
- data/lib/liquid/tags/cycle.rb +20 -14
- data/lib/liquid/tags/decrement.rb +4 -8
- data/lib/liquid/tags/for.rb +121 -60
- data/lib/liquid/tags/if.rb +73 -30
- data/lib/liquid/tags/ifchanged.rb +3 -5
- data/lib/liquid/tags/include.rb +77 -46
- data/lib/liquid/tags/increment.rb +4 -8
- data/lib/liquid/tags/raw.rb +35 -10
- data/lib/liquid/tags/table_row.rb +62 -0
- data/lib/liquid/tags/unless.rb +6 -9
- data/lib/liquid/template.rb +130 -32
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +57 -4
- data/lib/liquid/variable.rb +121 -30
- data/lib/liquid/variable_lookup.rb +88 -0
- data/lib/liquid/version.rb +2 -1
- data/test/fixtures/en_locale.yml +9 -0
- data/test/integration/assign_test.rb +48 -0
- data/test/integration/blank_test.rb +106 -0
- data/test/integration/block_test.rb +12 -0
- data/test/{liquid → integration}/capture_test.rb +13 -3
- data/test/integration/context_test.rb +32 -0
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +273 -0
- data/test/integration/error_handling_test.rb +260 -0
- data/test/integration/filter_test.rb +178 -0
- data/test/integration/hash_ordering_test.rb +23 -0
- data/test/integration/output_test.rb +123 -0
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +122 -0
- data/test/integration/render_profiling_test.rb +154 -0
- data/test/integration/security_test.rb +80 -0
- data/test/integration/standard_filter_test.rb +776 -0
- data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
- data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
- data/test/integration/tags/for_tag_test.rb +410 -0
- data/test/integration/tags/if_else_tag_test.rb +188 -0
- data/test/integration/tags/include_tag_test.rb +253 -0
- data/test/integration/tags/increment_tag_test.rb +23 -0
- data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
- data/test/integration/tags/standard_tag_test.rb +296 -0
- data/test/integration/tags/statements_test.rb +111 -0
- data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
- data/test/integration/tags/unless_else_tag_test.rb +26 -0
- data/test/integration/template_test.rb +332 -0
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +96 -0
- data/test/test_helper.rb +106 -19
- data/test/truffle/truffle_test.rb +9 -0
- data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
- data/test/unit/condition_unit_test.rb +166 -0
- data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
- data/test/unit/file_system_unit_test.rb +35 -0
- data/test/unit/i18n_unit_test.rb +37 -0
- data/test/unit/lexer_unit_test.rb +51 -0
- data/test/unit/parser_unit_test.rb +82 -0
- data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
- data/test/unit/strainer_unit_test.rb +164 -0
- data/test/unit/tag_unit_test.rb +21 -0
- data/test/unit/tags/case_tag_unit_test.rb +10 -0
- data/test/unit/tags/for_tag_unit_test.rb +13 -0
- data/test/unit/tags/if_tag_unit_test.rb +8 -0
- data/test/unit/template_unit_test.rb +78 -0
- data/test/unit/tokenizer_unit_test.rb +55 -0
- data/test/unit/variable_unit_test.rb +162 -0
- metadata +157 -77
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/liquid/htmltags.rb +0 -74
- data/lib/liquid/module_ex.rb +0 -62
- data/test/liquid/assign_test.rb +0 -21
- data/test/liquid/condition_test.rb +0 -127
- data/test/liquid/drop_test.rb +0 -180
- data/test/liquid/error_handling_test.rb +0 -81
- data/test/liquid/file_system_test.rb +0 -29
- data/test/liquid/filter_test.rb +0 -125
- data/test/liquid/hash_ordering_test.rb +0 -25
- data/test/liquid/module_ex_test.rb +0 -87
- data/test/liquid/output_test.rb +0 -116
- data/test/liquid/parsing_quirks_test.rb +0 -52
- data/test/liquid/security_test.rb +0 -64
- data/test/liquid/standard_filter_test.rb +0 -251
- data/test/liquid/strainer_test.rb +0 -52
- data/test/liquid/tags/for_tag_test.rb +0 -297
- data/test/liquid/tags/if_else_tag_test.rb +0 -166
- data/test/liquid/tags/include_tag_test.rb +0 -166
- data/test/liquid/tags/increment_tag_test.rb +0 -24
- data/test/liquid/tags/standard_tag_test.rb +0 -295
- data/test/liquid/tags/statements_test.rb +0 -134
- data/test/liquid/tags/unless_else_tag_test.rb +0 -26
- data/test/liquid/template_test.rb +0 -146
- data/test/liquid/variable_test.rb +0 -186
@@ -1,20 +1,18 @@
|
|
1
1
|
module Liquid
|
2
2
|
class Ifchanged < Block
|
3
|
-
|
4
3
|
def render(context)
|
5
4
|
context.stack do
|
6
|
-
|
7
|
-
output = render_all(@nodelist, context)
|
5
|
+
output = super
|
8
6
|
|
9
7
|
if output != context.registers[:ifchanged]
|
10
8
|
context.registers[:ifchanged] = output
|
11
9
|
output
|
12
10
|
else
|
13
|
-
''
|
11
|
+
''.freeze
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
Template.register_tag('ifchanged', Ifchanged)
|
17
|
+
Template.register_tag('ifchanged'.freeze, Ifchanged)
|
20
18
|
end
|
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:
|
@@ -17,77 +16,109 @@ module Liquid
|
|
17
16
|
class Include < Tag
|
18
17
|
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
19
18
|
|
20
|
-
|
19
|
+
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
20
|
+
|
21
|
+
def initialize(tag_name, markup, options)
|
22
|
+
super
|
23
|
+
|
21
24
|
if markup =~ Syntax
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
template_name = $1
|
27
|
+
variable_name = $3
|
28
|
+
|
29
|
+
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
30
|
+
@template_name_expr = Expression.parse(template_name)
|
31
|
+
@attributes = {}
|
26
32
|
|
27
33
|
markup.scan(TagAttributes) do |key, value|
|
28
|
-
@attributes[key] = value
|
34
|
+
@attributes[key] = Expression.parse(value)
|
29
35
|
end
|
30
36
|
|
31
37
|
else
|
32
|
-
raise SyntaxError.new(
|
38
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
|
33
39
|
end
|
34
|
-
|
35
|
-
super
|
36
40
|
end
|
37
41
|
|
38
|
-
def parse(
|
42
|
+
def parse(_tokens)
|
39
43
|
end
|
40
44
|
|
41
45
|
def render(context)
|
42
|
-
|
43
|
-
|
46
|
+
template_name = context.evaluate(@template_name_expr)
|
47
|
+
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
|
44
48
|
|
45
|
-
context
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
partial = load_cached_partial(template_name, context)
|
50
|
+
context_variable_name = template_name.split('/'.freeze).last
|
51
|
+
|
52
|
+
variable = if @variable_name_expr
|
53
|
+
context.evaluate(@variable_name_expr)
|
54
|
+
else
|
55
|
+
context.find_variable(template_name, raise_on_not_found: false)
|
56
|
+
end
|
49
57
|
|
50
|
-
|
51
|
-
|
52
|
-
|
58
|
+
old_template_name = context.template_name
|
59
|
+
old_partial = context.partial
|
60
|
+
begin
|
61
|
+
context.template_name = template_name
|
62
|
+
context.partial = true
|
63
|
+
context.stack do
|
64
|
+
@attributes.each do |key, value|
|
65
|
+
context[key] = context.evaluate(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
if variable.is_a?(Array)
|
69
|
+
variable.collect do |var|
|
70
|
+
context[context_variable_name] = var
|
71
|
+
partial.render(context)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
context[context_variable_name] = variable
|
53
75
|
partial.render(context)
|
54
76
|
end
|
55
|
-
else
|
56
|
-
context[@template_name[1..-2]] = variable
|
57
|
-
partial.render(context)
|
58
77
|
end
|
78
|
+
ensure
|
79
|
+
context.template_name = old_template_name
|
80
|
+
context.partial = old_partial
|
59
81
|
end
|
60
82
|
end
|
61
83
|
|
62
84
|
private
|
63
|
-
def load_cached_partial(context)
|
64
|
-
cached_partials = context.registers[:cached_partials] || {}
|
65
|
-
template_name = context[@template_name]
|
66
85
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
86
|
+
alias_method :parse_context, :options
|
87
|
+
private :parse_context
|
88
|
+
|
89
|
+
def load_cached_partial(template_name, context)
|
90
|
+
cached_partials = context.registers[:cached_partials] || {}
|
91
|
+
|
92
|
+
if cached = cached_partials[template_name]
|
93
|
+
return cached
|
94
|
+
end
|
95
|
+
source = read_template_from_file_system(context)
|
96
|
+
begin
|
97
|
+
parse_context.partial = true
|
98
|
+
partial = Liquid::Template.parse(source, parse_context)
|
99
|
+
ensure
|
100
|
+
parse_context.partial = false
|
75
101
|
end
|
102
|
+
cached_partials[template_name] = partial
|
103
|
+
context.registers[:cached_partials] = cached_partials
|
104
|
+
partial
|
105
|
+
end
|
76
106
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
107
|
+
def read_template_from_file_system(context)
|
108
|
+
file_system = context.registers[:file_system] || Liquid::Template.file_system
|
109
|
+
|
110
|
+
file_system.read_template_file(context.evaluate(@template_name_expr))
|
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
|
89
119
|
end
|
120
|
+
end
|
90
121
|
end
|
91
122
|
|
92
|
-
Template.register_tag('include', Include)
|
123
|
+
Template.register_tag('include'.freeze, Include)
|
93
124
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# increment is used in a place where one needs to insert a counter
|
4
3
|
# into a template, and needs the counter to survive across
|
5
4
|
# multiple instantiations of the template.
|
6
5
|
# (To achieve the survival, the application must keep the context)
|
7
6
|
#
|
8
7
|
# if the variable does not exist, it is created with value 0.
|
9
|
-
|
8
|
+
#
|
10
9
|
# Hello: {% increment variable %}
|
11
10
|
#
|
12
11
|
# gives you:
|
@@ -16,10 +15,9 @@ module Liquid
|
|
16
15
|
# Hello: 2
|
17
16
|
#
|
18
17
|
class Increment < Tag
|
19
|
-
def initialize(tag_name, markup,
|
20
|
-
@variable = markup.strip
|
21
|
-
|
18
|
+
def initialize(tag_name, markup, options)
|
22
19
|
super
|
20
|
+
@variable = markup.strip
|
23
21
|
end
|
24
22
|
|
25
23
|
def render(context)
|
@@ -27,9 +25,7 @@ module Liquid
|
|
27
25
|
context.environments.first[@variable] = value + 1
|
28
26
|
value.to_s
|
29
27
|
end
|
30
|
-
|
31
|
-
private
|
32
28
|
end
|
33
29
|
|
34
|
-
Template.register_tag('increment', Increment)
|
30
|
+
Template.register_tag('increment'.freeze, Increment)
|
35
31
|
end
|
data/lib/liquid/tags/raw.rb
CHANGED
@@ -1,22 +1,47 @@
|
|
1
1
|
module Liquid
|
2
2
|
class Raw < Block
|
3
|
-
|
3
|
+
Syntax = /\A\s*\z/
|
4
|
+
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
5
|
+
|
6
|
+
def initialize(tag_name, markup, parse_context)
|
7
|
+
super
|
8
|
+
|
9
|
+
ensure_valid_markup(tag_name, markup, parse_context)
|
10
|
+
end
|
4
11
|
|
5
12
|
def parse(tokens)
|
6
|
-
@
|
7
|
-
@nodelist.clear
|
13
|
+
@body = ''
|
8
14
|
while token = tokens.shift
|
9
15
|
if token =~ FullTokenPossiblyInvalid
|
10
|
-
@
|
11
|
-
if block_delimiter == $2
|
12
|
-
end_tag
|
13
|
-
return
|
14
|
-
end
|
16
|
+
@body << $1 if $1 != "".freeze
|
17
|
+
return if block_delimiter == $2
|
15
18
|
end
|
16
|
-
@
|
19
|
+
@body << token unless token.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(_context)
|
26
|
+
@body
|
27
|
+
end
|
28
|
+
|
29
|
+
def nodelist
|
30
|
+
[@body]
|
31
|
+
end
|
32
|
+
|
33
|
+
def blank?
|
34
|
+
@body.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def ensure_valid_markup(tag_name, markup, parse_context)
|
40
|
+
unless markup =~ Syntax
|
41
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
|
17
42
|
end
|
18
43
|
end
|
19
44
|
end
|
20
45
|
|
21
|
-
Template.register_tag('raw', Raw)
|
46
|
+
Template.register_tag('raw'.freeze, Raw)
|
22
47
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Liquid
|
2
|
+
class TableRow < Block
|
3
|
+
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
4
|
+
|
5
|
+
attr_reader :variable_name, :collection_name, :attributes
|
6
|
+
|
7
|
+
def initialize(tag_name, markup, options)
|
8
|
+
super
|
9
|
+
if markup =~ Syntax
|
10
|
+
@variable_name = $1
|
11
|
+
@collection_name = Expression.parse($2)
|
12
|
+
@attributes = {}
|
13
|
+
markup.scan(TagAttributes) do |key, value|
|
14
|
+
@attributes[key] = Expression.parse(value)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def render(context)
|
22
|
+
collection = context.evaluate(@collection_name) or return ''.freeze
|
23
|
+
|
24
|
+
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
|
25
|
+
to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
|
26
|
+
|
27
|
+
collection = Utils.slice_collection(collection, from, to)
|
28
|
+
|
29
|
+
length = collection.length
|
30
|
+
|
31
|
+
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
32
|
+
|
33
|
+
result = "<tr class=\"row1\">\n"
|
34
|
+
context.stack do
|
35
|
+
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
36
|
+
context['tablerowloop'.freeze] = tablerowloop
|
37
|
+
|
38
|
+
collection.each do |item|
|
39
|
+
context[@variable_name] = item
|
40
|
+
|
41
|
+
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
42
|
+
|
43
|
+
if tablerowloop.col_last && !tablerowloop.last
|
44
|
+
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
45
|
+
end
|
46
|
+
|
47
|
+
tablerowloop.send(:increment!)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
result << "</tr>\n"
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
55
|
+
def children
|
56
|
+
super + @node.attributes.values + [@node.collection_name]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Template.register_tag('tablerow'.freeze, TableRow)
|
62
|
+
end
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -1,33 +1,30 @@
|
|
1
|
-
|
1
|
+
require_relative 'if'
|
2
2
|
|
3
3
|
module Liquid
|
4
|
-
|
5
4
|
# Unless is a conditional just like 'if' but works on the inverse logic.
|
6
5
|
#
|
7
|
-
# {% unless x < 0 %} x is greater than zero {%
|
6
|
+
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
8
7
|
#
|
9
8
|
class Unless < If
|
10
9
|
def render(context)
|
11
10
|
context.stack do
|
12
|
-
|
13
11
|
# First condition is interpreted backwards ( if not )
|
14
12
|
first_block = @blocks.first
|
15
13
|
unless first_block.evaluate(context)
|
16
|
-
return
|
14
|
+
return first_block.attachment.render(context)
|
17
15
|
end
|
18
16
|
|
19
17
|
# After the first condition unless works just like if
|
20
18
|
@blocks[1..-1].each do |block|
|
21
19
|
if block.evaluate(context)
|
22
|
-
return
|
20
|
+
return block.attachment.render(context)
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
|
-
''
|
24
|
+
''.freeze
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
|
-
|
32
|
-
Template.register_tag('unless', Unless)
|
29
|
+
Template.register_tag('unless'.freeze, Unless)
|
33
30
|
end
|
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,10 +13,67 @@ module Liquid
|
|
14
13
|
# template.render('user_name' => 'bob')
|
15
14
|
#
|
16
15
|
class Template
|
17
|
-
attr_accessor :root
|
16
|
+
attr_accessor :root
|
17
|
+
attr_reader :resource_limits, :warnings
|
18
|
+
|
18
19
|
@@file_system = BlankFileSystem.new
|
19
20
|
|
21
|
+
class TagRegistry
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@tags = {}
|
26
|
+
@cache = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](tag_name)
|
30
|
+
return nil unless @tags.key?(tag_name)
|
31
|
+
return @cache[tag_name] if Liquid.cache_classes
|
32
|
+
|
33
|
+
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(tag_name, klass)
|
37
|
+
@tags[tag_name] = klass.name
|
38
|
+
@cache[tag_name] = klass
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(tag_name)
|
42
|
+
@tags.delete(tag_name)
|
43
|
+
@cache.delete(tag_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def each(&block)
|
47
|
+
@tags.each(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def lookup_class(name)
|
53
|
+
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :profiler
|
58
|
+
|
20
59
|
class << self
|
60
|
+
# Sets how strict the parser should be.
|
61
|
+
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
62
|
+
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
63
|
+
# :strict will enforce correct syntax.
|
64
|
+
attr_writer :error_mode
|
65
|
+
|
66
|
+
# Sets how strict the taint checker should be.
|
67
|
+
# :lax is the default, and ignores the taint flag completely
|
68
|
+
# :warn adds a warning, but does not interrupt the rendering
|
69
|
+
# :error raises an error when tainted output is used
|
70
|
+
attr_writer :taint_mode
|
71
|
+
|
72
|
+
attr_accessor :default_exception_renderer
|
73
|
+
Template.default_exception_renderer = lambda do |exception|
|
74
|
+
exception
|
75
|
+
end
|
76
|
+
|
21
77
|
def file_system
|
22
78
|
@@file_system
|
23
79
|
end
|
@@ -31,7 +87,15 @@ module Liquid
|
|
31
87
|
end
|
32
88
|
|
33
89
|
def tags
|
34
|
-
@tags ||=
|
90
|
+
@tags ||= TagRegistry.new
|
91
|
+
end
|
92
|
+
|
93
|
+
def error_mode
|
94
|
+
@error_mode ||= :lax
|
95
|
+
end
|
96
|
+
|
97
|
+
def taint_mode
|
98
|
+
@taint_mode ||= :lax
|
35
99
|
end
|
36
100
|
|
37
101
|
# Pass a module with filter methods which should be available
|
@@ -40,23 +104,33 @@ module Liquid
|
|
40
104
|
Strainer.global_filter(mod)
|
41
105
|
end
|
42
106
|
|
107
|
+
def default_resource_limits
|
108
|
+
@default_resource_limits ||= {}
|
109
|
+
end
|
110
|
+
|
43
111
|
# creates a new <tt>Template</tt> object from liquid source code
|
44
|
-
|
112
|
+
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
113
|
+
# See Liquid::Profiler for more information
|
114
|
+
def parse(source, options = {})
|
45
115
|
template = Template.new
|
46
|
-
template.parse(source)
|
47
|
-
template
|
116
|
+
template.parse(source, options)
|
48
117
|
end
|
49
118
|
end
|
50
119
|
|
51
|
-
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
52
120
|
def initialize
|
53
|
-
@
|
121
|
+
@rethrow_errors = false
|
122
|
+
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
54
123
|
end
|
55
124
|
|
56
125
|
# Parse source code.
|
57
126
|
# Returns self for easy chaining
|
58
|
-
def parse(source)
|
59
|
-
@
|
127
|
+
def parse(source, options = {})
|
128
|
+
@options = options
|
129
|
+
@profiling = options[:profile]
|
130
|
+
@line_numbers = options[:line_numbers] || @profiling
|
131
|
+
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
132
|
+
@root = Document.parse(tokenize(source), parse_context)
|
133
|
+
@warnings = parse_context.warnings
|
60
134
|
self
|
61
135
|
end
|
62
136
|
|
@@ -81,6 +155,9 @@ module Liquid
|
|
81
155
|
# if you use the same filters over and over again consider registering them globally
|
82
156
|
# with <tt>Template.register_filter</tt>
|
83
157
|
#
|
158
|
+
# if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
|
159
|
+
# will be available via <tt>Template#profiler</tt>
|
160
|
+
#
|
84
161
|
# Following options can be passed:
|
85
162
|
#
|
86
163
|
# * <tt>filters</tt> : array with local filters
|
@@ -88,11 +165,17 @@ module Liquid
|
|
88
165
|
# filters and tags and might be useful to integrate liquid more with its host application
|
89
166
|
#
|
90
167
|
def render(*args)
|
91
|
-
return '' if @root.nil?
|
168
|
+
return ''.freeze if @root.nil?
|
92
169
|
|
93
170
|
context = case args.first
|
94
171
|
when Liquid::Context
|
95
|
-
args.shift
|
172
|
+
c = args.shift
|
173
|
+
|
174
|
+
if @rethrow_errors
|
175
|
+
c.exception_renderer = ->(e) { raise }
|
176
|
+
end
|
177
|
+
|
178
|
+
c
|
96
179
|
when Liquid::Drop
|
97
180
|
drop = args.shift
|
98
181
|
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
@@ -101,31 +184,29 @@ module Liquid
|
|
101
184
|
when nil
|
102
185
|
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
103
186
|
else
|
104
|
-
raise ArgumentError, "
|
187
|
+
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
105
188
|
end
|
106
189
|
|
107
190
|
case args.last
|
108
191
|
when Hash
|
109
192
|
options = args.pop
|
110
193
|
|
111
|
-
if options[:registers].is_a?(Hash)
|
112
|
-
self.registers.merge!(options[:registers])
|
113
|
-
end
|
194
|
+
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
114
195
|
|
115
|
-
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
when Module
|
120
|
-
context.add_filters(args.pop)
|
121
|
-
when Array
|
196
|
+
apply_options_to_context(context, options)
|
197
|
+
when Module, Array
|
122
198
|
context.add_filters(args.pop)
|
123
199
|
end
|
124
200
|
|
201
|
+
# Retrying a render resets resource usage
|
202
|
+
context.resource_limits.reset
|
203
|
+
|
125
204
|
begin
|
126
205
|
# render the nodelist.
|
127
206
|
# for performance reasons we get an array back here. join will make a string out of it.
|
128
|
-
result =
|
207
|
+
result = with_profiling(context) do
|
208
|
+
@root.render(context)
|
209
|
+
end
|
129
210
|
result.respond_to?(:join) ? result.join : result
|
130
211
|
rescue Liquid::MemoryError => e
|
131
212
|
context.handle_error(e)
|
@@ -135,22 +216,39 @@ module Liquid
|
|
135
216
|
end
|
136
217
|
|
137
218
|
def render!(*args)
|
138
|
-
@rethrow_errors = true
|
219
|
+
@rethrow_errors = true
|
220
|
+
render(*args)
|
139
221
|
end
|
140
222
|
|
141
223
|
private
|
142
224
|
|
143
|
-
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
144
225
|
def tokenize(source)
|
145
|
-
|
146
|
-
|
147
|
-
tokens = source.split(TemplateParser)
|
226
|
+
Tokenizer.new(source, @line_numbers)
|
227
|
+
end
|
148
228
|
|
149
|
-
|
150
|
-
|
229
|
+
def with_profiling(context)
|
230
|
+
if @profiling && !context.partial
|
231
|
+
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
151
232
|
|
152
|
-
|
233
|
+
@profiler = Profiler.new
|
234
|
+
@profiler.start
|
235
|
+
|
236
|
+
begin
|
237
|
+
yield
|
238
|
+
ensure
|
239
|
+
@profiler.stop
|
240
|
+
end
|
241
|
+
else
|
242
|
+
yield
|
243
|
+
end
|
153
244
|
end
|
154
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
|
155
253
|
end
|
156
254
|
end
|