liquid 5.8.7 → 5.9.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 +3 -0
- data/README.md +5 -5
- data/lib/liquid/condition.rb +2 -2
- data/lib/liquid/environment.rb +1 -1
- data/lib/liquid/expression.rb +4 -0
- data/lib/liquid/locales/en.yml +1 -0
- data/lib/liquid/parse_context.rb +16 -1
- data/lib/liquid/parser_switching.rb +26 -1
- data/lib/liquid/tag.rb +6 -2
- data/lib/liquid/tags/assign.rb +4 -0
- data/lib/liquid/tags/capture.rb +4 -0
- data/lib/liquid/tags/case.rb +42 -6
- data/lib/liquid/tags/cycle.rb +69 -16
- data/lib/liquid/tags/decrement.rb +4 -0
- data/lib/liquid/tags/for.rb +11 -7
- data/lib/liquid/tags/if.rb +8 -4
- data/lib/liquid/tags/include.rb +44 -18
- data/lib/liquid/tags/increment.rb +4 -0
- data/lib/liquid/tags/render.rb +50 -16
- data/lib/liquid/tags/table_row.rb +40 -3
- data/lib/liquid/template.rb +3 -2
- data/lib/liquid/utils.rb +5 -2
- data/lib/liquid/variable.rb +55 -4
- data/lib/liquid/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 112da43191fdf32af9afb1ad6322ff2fab8b22b6d658073980ba236aa1834213
|
|
4
|
+
data.tar.gz: b7ab5c91bc2f65b91a782027eedd8374f1f46629c791d6b2c07a8ba2c07d7314
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c941dc92d57cf97c30a6db6eb206e7f00b780070b8dc790def3a8be06a3714562813b0ca8da34d5745be4432c839cf2f5946557d103ae0e56ffe5822b4ebf1e6
|
|
7
|
+
data.tar.gz: 9491a34a69273c3cebe65208f935007e68dbd81a8b32913ebc7d0056863c9f3a30b6dff9d1da3b37af66e472aedf60c6979e22d5f500ba6d24bd4f8637449a3f
|
data/History.md
CHANGED
data/README.md
CHANGED
|
@@ -99,14 +99,14 @@ Setting the error mode of Liquid lets you specify how strictly you want your tem
|
|
|
99
99
|
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
|
100
100
|
it very hard to debug and can lead to unexpected behaviour.
|
|
101
101
|
|
|
102
|
-
Liquid also comes with
|
|
102
|
+
Liquid also comes with different parsers that can be used when editing templates to give better error messages
|
|
103
103
|
when templates are invalid. You can enable this new parser like this:
|
|
104
104
|
|
|
105
105
|
```ruby
|
|
106
|
-
Liquid::Environment.default.error_mode = :
|
|
107
|
-
Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
|
|
108
|
-
Liquid::Environment.default.error_mode = :warn
|
|
109
|
-
Liquid::Environment.default.error_mode = :lax
|
|
106
|
+
Liquid::Environment.default.error_mode = :rigid # Raises a SyntaxError when invalid syntax is used in all tags
|
|
107
|
+
Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used in some tags
|
|
108
|
+
Liquid::Environment.default.error_mode = :warn # Adds strict errors to template.errors but continues as normal
|
|
109
|
+
Liquid::Environment.default.error_mode = :lax # The default mode, accepts almost anything.
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
|
data/lib/liquid/condition.rb
CHANGED
|
@@ -48,8 +48,8 @@ module Liquid
|
|
|
48
48
|
@@operators
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def self.parse_expression(parse_context, markup)
|
|
52
|
-
@@method_literals[markup] || parse_context.parse_expression(markup)
|
|
51
|
+
def self.parse_expression(parse_context, markup, safe: false)
|
|
52
|
+
@@method_literals[markup] || parse_context.parse_expression(markup, safe: safe)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
attr_reader :attachment, :child_condition
|
data/lib/liquid/environment.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Liquid
|
|
|
34
34
|
# @param file_system The default file system that is used
|
|
35
35
|
# to load templates from.
|
|
36
36
|
# @param error_mode [Symbol] The default error mode for all templates
|
|
37
|
-
# (either :strict, :warn, or :lax).
|
|
37
|
+
# (either :rigid, :strict, :warn, or :lax).
|
|
38
38
|
# @param exception_renderer [Proc] The exception renderer that is used to
|
|
39
39
|
# render exceptions.
|
|
40
40
|
# @yieldparam environment [Environment] The environment instance that is being built.
|
data/lib/liquid/expression.rb
CHANGED
|
@@ -28,6 +28,10 @@ module Liquid
|
|
|
28
28
|
FLOAT_REGEX = /\A(-?\d+)\.\d+\z/
|
|
29
29
|
|
|
30
30
|
class << self
|
|
31
|
+
def safe_parse(parser, ss = StringScanner.new(""), cache = nil)
|
|
32
|
+
parse(parser.expression, ss, cache)
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
def parse(markup, ss = StringScanner.new(""), cache = nil)
|
|
32
36
|
return unless markup
|
|
33
37
|
|
data/lib/liquid/locales/en.yml
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
invalid_template_encoding: "Invalid template encoding"
|
|
21
21
|
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
|
22
22
|
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
|
23
|
+
table_row_invalid_attribute: "Invalid attribute '%{attribute}' in tablerow loop. Valid attributes are cols, limit, offset, and range"
|
|
23
24
|
tag_never_closed: "'%{block_name}' tag was never closed"
|
|
24
25
|
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
|
25
26
|
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
data/lib/liquid/parse_context.rb
CHANGED
|
@@ -50,7 +50,22 @@ module Liquid
|
|
|
50
50
|
)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def safe_parse_expression(parser)
|
|
54
|
+
Expression.safe_parse(parser, @string_scanner, @expression_cache)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_expression(markup, safe: false)
|
|
58
|
+
if !safe && @error_mode == :rigid
|
|
59
|
+
# parse_expression is a widely used API. To maintain backward
|
|
60
|
+
# compatibility while raising awareness about rigid parser standards,
|
|
61
|
+
# the safe flag supports API users make a deliberate decision.
|
|
62
|
+
#
|
|
63
|
+
# In rigid mode, markup MUST come from a string returned by the parser
|
|
64
|
+
# (e.g., parser.expression). We're not calling the parser here to
|
|
65
|
+
# prevent redundant parser overhead.
|
|
66
|
+
raise Liquid::InternalError, "unsafe parse_expression cannot be used in rigid mode"
|
|
67
|
+
end
|
|
68
|
+
|
|
54
69
|
Expression.parse(markup, @string_scanner, @expression_cache)
|
|
55
70
|
end
|
|
56
71
|
|
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module Liquid
|
|
4
4
|
module ParserSwitching
|
|
5
|
+
# Do not use this.
|
|
6
|
+
#
|
|
7
|
+
# It's basically doing the same thing the {#parse_with_selected_parser},
|
|
8
|
+
# except this will try the strict parser regardless of the error mode,
|
|
9
|
+
# and fall back to the lax parser if the error mode is lax or warn,
|
|
10
|
+
# except when in rigid mode where it uses the rigid parser.
|
|
11
|
+
#
|
|
12
|
+
# @deprecated Use {#parse_with_selected_parser} instead.
|
|
5
13
|
def strict_parse_with_error_mode_fallback(markup)
|
|
14
|
+
return rigid_parse_with_error_context(markup) if rigid_mode?
|
|
15
|
+
|
|
6
16
|
strict_parse_with_error_context(markup)
|
|
7
17
|
rescue SyntaxError => e
|
|
8
18
|
case parse_context.error_mode
|
|
19
|
+
when :rigid
|
|
20
|
+
raise
|
|
9
21
|
when :strict
|
|
10
22
|
raise
|
|
11
23
|
when :warn
|
|
@@ -16,11 +28,12 @@ module Liquid
|
|
|
16
28
|
|
|
17
29
|
def parse_with_selected_parser(markup)
|
|
18
30
|
case parse_context.error_mode
|
|
31
|
+
when :rigid then rigid_parse_with_error_context(markup)
|
|
19
32
|
when :strict then strict_parse_with_error_context(markup)
|
|
20
33
|
when :lax then lax_parse(markup)
|
|
21
34
|
when :warn
|
|
22
35
|
begin
|
|
23
|
-
|
|
36
|
+
rigid_parse_with_error_context(markup)
|
|
24
37
|
rescue SyntaxError => e
|
|
25
38
|
parse_context.warnings << e
|
|
26
39
|
lax_parse(markup)
|
|
@@ -28,8 +41,20 @@ module Liquid
|
|
|
28
41
|
end
|
|
29
42
|
end
|
|
30
43
|
|
|
44
|
+
def rigid_mode?
|
|
45
|
+
parse_context.error_mode == :rigid
|
|
46
|
+
end
|
|
47
|
+
|
|
31
48
|
private
|
|
32
49
|
|
|
50
|
+
def rigid_parse_with_error_context(markup)
|
|
51
|
+
rigid_parse(markup)
|
|
52
|
+
rescue SyntaxError => e
|
|
53
|
+
e.line_number = line_number
|
|
54
|
+
e.markup_context = markup_context(markup)
|
|
55
|
+
raise e
|
|
56
|
+
end
|
|
57
|
+
|
|
33
58
|
def strict_parse_with_error_context(markup)
|
|
34
59
|
strict_parse(markup)
|
|
35
60
|
rescue SyntaxError => e
|
data/lib/liquid/tag.rb
CHANGED
|
@@ -68,8 +68,12 @@ module Liquid
|
|
|
68
68
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
|
-
def
|
|
72
|
-
parse_context.
|
|
71
|
+
def safe_parse_expression(parser)
|
|
72
|
+
parse_context.safe_parse_expression(parser)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_expression(markup, safe: false)
|
|
76
|
+
parse_context.parse_expression(markup, safe: safe)
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
end
|
data/lib/liquid/tags/assign.rb
CHANGED
|
@@ -9,6 +9,10 @@ module Liquid
|
|
|
9
9
|
# Creates a new variable.
|
|
10
10
|
# @liquid_description
|
|
11
11
|
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
|
|
12
|
+
#
|
|
13
|
+
# > Caution:
|
|
14
|
+
# > Predefined Liquid objects can be overridden by variables with the same name.
|
|
15
|
+
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
|
|
12
16
|
# @liquid_syntax
|
|
13
17
|
# {% assign variable_name = value %}
|
|
14
18
|
# @liquid_syntax_keyword variable_name The name of the variable being created.
|
data/lib/liquid/tags/capture.rb
CHANGED
|
@@ -9,6 +9,10 @@ module Liquid
|
|
|
9
9
|
# Creates a new variable with a string value.
|
|
10
10
|
# @liquid_description
|
|
11
11
|
# You can create complex strings with Liquid logic and variables.
|
|
12
|
+
#
|
|
13
|
+
# > Caution:
|
|
14
|
+
# > Predefined Liquid objects can be overridden by variables with the same name.
|
|
15
|
+
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
|
|
12
16
|
# @liquid_syntax
|
|
13
17
|
# {% capture variable %}
|
|
14
18
|
# value
|
data/lib/liquid/tags/case.rb
CHANGED
|
@@ -31,12 +31,7 @@ module Liquid
|
|
|
31
31
|
def initialize(tag_name, markup, options)
|
|
32
32
|
super
|
|
33
33
|
@blocks = []
|
|
34
|
-
|
|
35
|
-
if markup =~ Syntax
|
|
36
|
-
@left = parse_expression(Regexp.last_match(1))
|
|
37
|
-
else
|
|
38
|
-
raise SyntaxError, options[:locale].t("errors.syntax.case")
|
|
39
|
-
end
|
|
34
|
+
parse_with_selected_parser(markup)
|
|
40
35
|
end
|
|
41
36
|
|
|
42
37
|
def parse(tokens)
|
|
@@ -91,9 +86,50 @@ module Liquid
|
|
|
91
86
|
|
|
92
87
|
private
|
|
93
88
|
|
|
89
|
+
def rigid_parse(markup)
|
|
90
|
+
parser = @parse_context.new_parser(markup)
|
|
91
|
+
@left = safe_parse_expression(parser)
|
|
92
|
+
parser.consume(:end_of_string)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def strict_parse(markup)
|
|
96
|
+
lax_parse(markup)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def lax_parse(markup)
|
|
100
|
+
if markup =~ Syntax
|
|
101
|
+
@left = parse_expression(Regexp.last_match(1))
|
|
102
|
+
else
|
|
103
|
+
raise SyntaxError, options[:locale].t("errors.syntax.case")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
94
107
|
def record_when_condition(markup)
|
|
95
108
|
body = new_body
|
|
96
109
|
|
|
110
|
+
if rigid_mode?
|
|
111
|
+
parse_rigid_when(markup, body)
|
|
112
|
+
else
|
|
113
|
+
parse_lax_when(markup, body)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def parse_rigid_when(markup, body)
|
|
118
|
+
parser = @parse_context.new_parser(markup)
|
|
119
|
+
|
|
120
|
+
loop do
|
|
121
|
+
expr = safe_parse_expression(parser)
|
|
122
|
+
block = Condition.new(@left, '==', expr)
|
|
123
|
+
block.attach(body)
|
|
124
|
+
@blocks << block
|
|
125
|
+
|
|
126
|
+
break unless parser.id?('or') || parser.consume?(:comma)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
parser.consume(:end_of_string)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_lax_when(markup, body)
|
|
97
133
|
while markup
|
|
98
134
|
unless markup =~ WhenSyntax
|
|
99
135
|
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when")
|
data/lib/liquid/tags/cycle.rb
CHANGED
|
@@ -17,23 +17,13 @@ module Liquid
|
|
|
17
17
|
class Cycle < Tag
|
|
18
18
|
SimpleSyntax = /\A#{QuotedFragment}+/o
|
|
19
19
|
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
|
20
|
+
UNNAMED_CYCLE_PATTERN = /\w+:0x\h{8}/
|
|
20
21
|
|
|
21
22
|
attr_reader :variables
|
|
22
23
|
|
|
23
24
|
def initialize(tag_name, markup, options)
|
|
24
25
|
super
|
|
25
|
-
|
|
26
|
-
when NamedSyntax
|
|
27
|
-
@variables = variables_from_string(Regexp.last_match(2))
|
|
28
|
-
@name = parse_expression(Regexp.last_match(1))
|
|
29
|
-
@is_named = true
|
|
30
|
-
when SimpleSyntax
|
|
31
|
-
@variables = variables_from_string(markup)
|
|
32
|
-
@name = @variables.to_s
|
|
33
|
-
@is_named = !@name.match?(/\w+:0x\h{8}/)
|
|
34
|
-
else
|
|
35
|
-
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
|
|
36
|
-
end
|
|
26
|
+
parse_with_selected_parser(markup)
|
|
37
27
|
end
|
|
38
28
|
|
|
39
29
|
def named?
|
|
@@ -65,19 +55,82 @@ module Liquid
|
|
|
65
55
|
|
|
66
56
|
private
|
|
67
57
|
|
|
58
|
+
# cycle [name:] expression(, expression)*
|
|
59
|
+
def rigid_parse(markup)
|
|
60
|
+
p = @parse_context.new_parser(markup)
|
|
61
|
+
|
|
62
|
+
@variables = []
|
|
63
|
+
|
|
64
|
+
raise SyntaxError, options[:locale].t("errors.syntax.cycle") if p.look(:end_of_string)
|
|
65
|
+
|
|
66
|
+
first_expression = safe_parse_expression(p)
|
|
67
|
+
if p.look(:colon)
|
|
68
|
+
# cycle name: expr1, expr2, ...
|
|
69
|
+
@name = first_expression
|
|
70
|
+
@is_named = true
|
|
71
|
+
p.consume(:colon)
|
|
72
|
+
# After the colon, parse the first variable (required for named cycles)
|
|
73
|
+
@variables << maybe_dup_lookup(safe_parse_expression(p))
|
|
74
|
+
else
|
|
75
|
+
# cycle expr1, expr2, ...
|
|
76
|
+
@variables << maybe_dup_lookup(first_expression)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Parse remaining comma-separated expressions
|
|
80
|
+
while p.consume?(:comma)
|
|
81
|
+
break if p.look(:end_of_string)
|
|
82
|
+
|
|
83
|
+
@variables << maybe_dup_lookup(safe_parse_expression(p))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
p.consume(:end_of_string)
|
|
87
|
+
|
|
88
|
+
unless @is_named
|
|
89
|
+
@name = @variables.to_s
|
|
90
|
+
@is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def strict_parse(markup)
|
|
95
|
+
lax_parse(markup)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def lax_parse(markup)
|
|
99
|
+
case markup
|
|
100
|
+
when NamedSyntax
|
|
101
|
+
@variables = variables_from_string(Regexp.last_match(2))
|
|
102
|
+
@name = parse_expression(Regexp.last_match(1))
|
|
103
|
+
@is_named = true
|
|
104
|
+
when SimpleSyntax
|
|
105
|
+
@variables = variables_from_string(markup)
|
|
106
|
+
@name = @variables.to_s
|
|
107
|
+
@is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)
|
|
108
|
+
else
|
|
109
|
+
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
68
113
|
def variables_from_string(markup)
|
|
69
114
|
markup.split(',').collect do |var|
|
|
70
115
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
|
71
116
|
next unless Regexp.last_match(1)
|
|
72
117
|
|
|
73
|
-
# Expression Parser returns cached objects, and we need to dup them to
|
|
74
|
-
# start the cycle over for each new cycle call.
|
|
75
|
-
# Liquid-C does not have a cache, so we don't need to dup the object.
|
|
76
118
|
var = parse_expression(Regexp.last_match(1))
|
|
77
|
-
var
|
|
119
|
+
maybe_dup_lookup(var)
|
|
78
120
|
end.compact
|
|
79
121
|
end
|
|
80
122
|
|
|
123
|
+
# For backwards compatibility, whenever a lookup is used in an unnamed cycle,
|
|
124
|
+
# we make it so that the @variables.to_s produces different strings for cycles
|
|
125
|
+
# called with the same arguments (since @variables.to_s is used as the cycle counter key)
|
|
126
|
+
# This makes it so {% cycle a, b %} and {% cycle a, b %} have independent counters even if a and b share value.
|
|
127
|
+
# This is not true for literal values, {% cycle "a", "b" %} and {% cycle "a", "b" %} share the same counter.
|
|
128
|
+
# I was really scratching my head about this one, but migrating away from this would be more headache
|
|
129
|
+
# than it's worth. So we're keeping this quirk for now.
|
|
130
|
+
def maybe_dup_lookup(var)
|
|
131
|
+
var.is_a?(VariableLookup) ? var.dup : var
|
|
132
|
+
end
|
|
133
|
+
|
|
81
134
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
82
135
|
def children
|
|
83
136
|
Array(@node.variables)
|
|
@@ -7,6 +7,10 @@ module Liquid
|
|
|
7
7
|
# @liquid_name decrement
|
|
8
8
|
# @liquid_summary
|
|
9
9
|
# Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
|
|
10
|
+
#
|
|
11
|
+
# > Caution:
|
|
12
|
+
# > Predefined Liquid objects can be overridden by variables with the same name.
|
|
13
|
+
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
|
|
10
14
|
# @liquid_description
|
|
11
15
|
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
|
12
16
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
data/lib/liquid/tags/for.rb
CHANGED
|
@@ -20,8 +20,8 @@ module Liquid
|
|
|
20
20
|
# @liquid_syntax_keyword variable The current item in the array.
|
|
21
21
|
# @liquid_syntax_keyword array The array to iterate over.
|
|
22
22
|
# @liquid_syntax_keyword expression The expression to render for each iteration.
|
|
23
|
-
# @liquid_optional_param limit [number] The number of iterations to perform.
|
|
24
|
-
# @liquid_optional_param offset [number] The 1-based index to start iterating at.
|
|
23
|
+
# @liquid_optional_param limit: [number] The number of iterations to perform.
|
|
24
|
+
# @liquid_optional_param offset: [number] The 1-based index to start iterating at.
|
|
25
25
|
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
|
|
26
26
|
# @liquid_optional_param reversed [untyped] Iterate in reverse order.
|
|
27
27
|
class For < Block
|
|
@@ -93,7 +93,7 @@ module Liquid
|
|
|
93
93
|
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
|
|
94
94
|
|
|
95
95
|
collection_name = p.expression
|
|
96
|
-
@collection_name = parse_expression(collection_name)
|
|
96
|
+
@collection_name = parse_expression(collection_name, safe: true)
|
|
97
97
|
|
|
98
98
|
@name = "#{@variable_name}-#{collection_name}"
|
|
99
99
|
@reversed = p.id?('reversed')
|
|
@@ -104,13 +104,17 @@ module Liquid
|
|
|
104
104
|
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
|
|
105
105
|
end
|
|
106
106
|
p.consume(:colon)
|
|
107
|
-
set_attribute(attribute, p.expression)
|
|
107
|
+
set_attribute(attribute, p.expression, safe: true)
|
|
108
108
|
end
|
|
109
109
|
p.consume(:end_of_string)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
private
|
|
113
113
|
|
|
114
|
+
def rigid_parse(markup)
|
|
115
|
+
strict_parse(markup)
|
|
116
|
+
end
|
|
117
|
+
|
|
114
118
|
def collection_segment(context)
|
|
115
119
|
offsets = context.registers[:for] ||= {}
|
|
116
120
|
|
|
@@ -174,16 +178,16 @@ module Liquid
|
|
|
174
178
|
output
|
|
175
179
|
end
|
|
176
180
|
|
|
177
|
-
def set_attribute(key, expr)
|
|
181
|
+
def set_attribute(key, expr, safe: false)
|
|
178
182
|
case key
|
|
179
183
|
when 'offset'
|
|
180
184
|
@from = if expr == 'continue'
|
|
181
185
|
:continue
|
|
182
186
|
else
|
|
183
|
-
parse_expression(expr)
|
|
187
|
+
parse_expression(expr, safe: safe)
|
|
184
188
|
end
|
|
185
189
|
when 'limit'
|
|
186
|
-
@limit = parse_expression(expr)
|
|
190
|
+
@limit = parse_expression(expr, safe: safe)
|
|
187
191
|
end
|
|
188
192
|
end
|
|
189
193
|
|
data/lib/liquid/tags/if.rb
CHANGED
|
@@ -66,6 +66,10 @@ module Liquid
|
|
|
66
66
|
|
|
67
67
|
private
|
|
68
68
|
|
|
69
|
+
def rigid_parse(markup)
|
|
70
|
+
strict_parse(markup)
|
|
71
|
+
end
|
|
72
|
+
|
|
69
73
|
def push_block(tag, markup)
|
|
70
74
|
block = if tag == 'else'
|
|
71
75
|
ElseCondition.new
|
|
@@ -77,8 +81,8 @@ module Liquid
|
|
|
77
81
|
block.attach(new_body)
|
|
78
82
|
end
|
|
79
83
|
|
|
80
|
-
def parse_expression(markup)
|
|
81
|
-
Condition.parse_expression(parse_context, markup)
|
|
84
|
+
def parse_expression(markup, safe: false)
|
|
85
|
+
Condition.parse_expression(parse_context, markup, safe: safe)
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
def lax_parse(markup)
|
|
@@ -120,9 +124,9 @@ module Liquid
|
|
|
120
124
|
end
|
|
121
125
|
|
|
122
126
|
def parse_comparison(p)
|
|
123
|
-
a = parse_expression(p.expression)
|
|
127
|
+
a = parse_expression(p.expression, safe: true)
|
|
124
128
|
if (op = p.consume?(:comparison))
|
|
125
|
-
b = parse_expression(p.expression)
|
|
129
|
+
b = parse_expression(p.expression, safe: true)
|
|
126
130
|
Condition.new(a, op, b)
|
|
127
131
|
else
|
|
128
132
|
Condition.new(a)
|
data/lib/liquid/tags/include.rb
CHANGED
|
@@ -27,24 +27,7 @@ module Liquid
|
|
|
27
27
|
|
|
28
28
|
def initialize(tag_name, markup, options)
|
|
29
29
|
super
|
|
30
|
-
|
|
31
|
-
if markup =~ SYNTAX
|
|
32
|
-
|
|
33
|
-
template_name = Regexp.last_match(1)
|
|
34
|
-
variable_name = Regexp.last_match(3)
|
|
35
|
-
|
|
36
|
-
@alias_name = Regexp.last_match(5)
|
|
37
|
-
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
|
38
|
-
@template_name_expr = parse_expression(template_name)
|
|
39
|
-
@attributes = {}
|
|
40
|
-
|
|
41
|
-
markup.scan(TagAttributes) do |key, value|
|
|
42
|
-
@attributes[key] = parse_expression(value)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
else
|
|
46
|
-
raise SyntaxError, options[:locale].t("errors.syntax.include")
|
|
47
|
-
end
|
|
30
|
+
parse_with_selected_parser(markup)
|
|
48
31
|
end
|
|
49
32
|
|
|
50
33
|
def parse(_tokens)
|
|
@@ -101,6 +84,49 @@ module Liquid
|
|
|
101
84
|
alias_method :parse_context, :options
|
|
102
85
|
private :parse_context
|
|
103
86
|
|
|
87
|
+
def rigid_parse(markup)
|
|
88
|
+
p = @parse_context.new_parser(markup)
|
|
89
|
+
|
|
90
|
+
@template_name_expr = safe_parse_expression(p)
|
|
91
|
+
@variable_name_expr = safe_parse_expression(p) if p.id?("for") || p.id?("with")
|
|
92
|
+
@alias_name = p.consume(:id) if p.id?("as")
|
|
93
|
+
|
|
94
|
+
p.consume?(:comma)
|
|
95
|
+
|
|
96
|
+
@attributes = {}
|
|
97
|
+
while p.look(:id)
|
|
98
|
+
key = p.consume
|
|
99
|
+
p.consume(:colon)
|
|
100
|
+
@attributes[key] = safe_parse_expression(p)
|
|
101
|
+
p.consume?(:comma)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
p.consume(:end_of_string)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def strict_parse(markup)
|
|
108
|
+
lax_parse(markup)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def lax_parse(markup)
|
|
112
|
+
if markup =~ SYNTAX
|
|
113
|
+
template_name = Regexp.last_match(1)
|
|
114
|
+
variable_name = Regexp.last_match(3)
|
|
115
|
+
|
|
116
|
+
@alias_name = Regexp.last_match(5)
|
|
117
|
+
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
|
118
|
+
@template_name_expr = parse_expression(template_name)
|
|
119
|
+
@attributes = {}
|
|
120
|
+
|
|
121
|
+
markup.scan(TagAttributes) do |key, value|
|
|
122
|
+
@attributes[key] = parse_expression(value)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
else
|
|
126
|
+
raise SyntaxError, options[:locale].t("errors.syntax.include")
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
104
130
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
105
131
|
def children
|
|
106
132
|
[
|
|
@@ -7,6 +7,10 @@ module Liquid
|
|
|
7
7
|
# @liquid_name increment
|
|
8
8
|
# @liquid_summary
|
|
9
9
|
# Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.
|
|
10
|
+
#
|
|
11
|
+
# > Caution:
|
|
12
|
+
# > Predefined Liquid objects can be overridden by variables with the same name.
|
|
13
|
+
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
|
|
10
14
|
# @liquid_description
|
|
11
15
|
# Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
|
12
16
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
data/lib/liquid/tags/render.rb
CHANGED
|
@@ -35,22 +35,7 @@ module Liquid
|
|
|
35
35
|
|
|
36
36
|
def initialize(tag_name, markup, options)
|
|
37
37
|
super
|
|
38
|
-
|
|
39
|
-
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
|
|
40
|
-
|
|
41
|
-
template_name = Regexp.last_match(1)
|
|
42
|
-
with_or_for = Regexp.last_match(3)
|
|
43
|
-
variable_name = Regexp.last_match(4)
|
|
44
|
-
|
|
45
|
-
@alias_name = Regexp.last_match(6)
|
|
46
|
-
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
|
47
|
-
@template_name_expr = parse_expression(template_name)
|
|
48
|
-
@is_for_loop = (with_or_for == FOR)
|
|
49
|
-
|
|
50
|
-
@attributes = {}
|
|
51
|
-
markup.scan(TagAttributes) do |key, value|
|
|
52
|
-
@attributes[key] = parse_expression(value)
|
|
53
|
-
end
|
|
38
|
+
parse_with_selected_parser(markup)
|
|
54
39
|
end
|
|
55
40
|
|
|
56
41
|
def for_loop?
|
|
@@ -99,6 +84,55 @@ module Liquid
|
|
|
99
84
|
output
|
|
100
85
|
end
|
|
101
86
|
|
|
87
|
+
# render (string) (with|for expression)? (as id)? (key: value)*
|
|
88
|
+
def rigid_parse(markup)
|
|
89
|
+
p = @parse_context.new_parser(markup)
|
|
90
|
+
|
|
91
|
+
@template_name_expr = parse_expression(rigid_template_name(p), safe: true)
|
|
92
|
+
with_or_for = p.id?("for") || p.id?("with")
|
|
93
|
+
@variable_name_expr = safe_parse_expression(p) if with_or_for
|
|
94
|
+
@alias_name = p.consume(:id) if p.id?("as")
|
|
95
|
+
@is_for_loop = (with_or_for == FOR)
|
|
96
|
+
|
|
97
|
+
p.consume?(:comma)
|
|
98
|
+
|
|
99
|
+
@attributes = {}
|
|
100
|
+
while p.look(:id)
|
|
101
|
+
key = p.consume
|
|
102
|
+
p.consume(:colon)
|
|
103
|
+
@attributes[key] = safe_parse_expression(p)
|
|
104
|
+
p.consume?(:comma)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
p.consume(:end_of_string)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def rigid_template_name(p)
|
|
111
|
+
p.consume(:string)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def strict_parse(markup)
|
|
115
|
+
lax_parse(markup)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def lax_parse(markup)
|
|
119
|
+
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
|
|
120
|
+
|
|
121
|
+
template_name = Regexp.last_match(1)
|
|
122
|
+
with_or_for = Regexp.last_match(3)
|
|
123
|
+
variable_name = Regexp.last_match(4)
|
|
124
|
+
|
|
125
|
+
@alias_name = Regexp.last_match(6)
|
|
126
|
+
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
|
127
|
+
@template_name_expr = parse_expression(template_name)
|
|
128
|
+
@is_for_loop = (with_or_for == FOR)
|
|
129
|
+
|
|
130
|
+
@attributes = {}
|
|
131
|
+
markup.scan(TagAttributes) do |key, value|
|
|
132
|
+
@attributes[key] = parse_expression(value)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
102
136
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
103
137
|
def children
|
|
104
138
|
[
|
|
@@ -19,17 +19,54 @@ module Liquid
|
|
|
19
19
|
# @liquid_syntax_keyword variable The current item in the array.
|
|
20
20
|
# @liquid_syntax_keyword array The array to iterate over.
|
|
21
21
|
# @liquid_syntax_keyword expression The expression to render.
|
|
22
|
-
# @liquid_optional_param cols [number] The number of columns that the table should have.
|
|
23
|
-
# @liquid_optional_param limit [number] The number of iterations to perform.
|
|
24
|
-
# @liquid_optional_param offset [number] The 1-based index to start iterating at.
|
|
22
|
+
# @liquid_optional_param cols: [number] The number of columns that the table should have.
|
|
23
|
+
# @liquid_optional_param limit: [number] The number of iterations to perform.
|
|
24
|
+
# @liquid_optional_param offset: [number] The 1-based index to start iterating at.
|
|
25
25
|
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
|
|
26
26
|
class TableRow < Block
|
|
27
27
|
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
|
28
|
+
ALLOWED_ATTRIBUTES = ['cols', 'limit', 'offset', 'range'].freeze
|
|
28
29
|
|
|
29
30
|
attr_reader :variable_name, :collection_name, :attributes
|
|
30
31
|
|
|
31
32
|
def initialize(tag_name, markup, options)
|
|
32
33
|
super
|
|
34
|
+
parse_with_selected_parser(markup)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def rigid_parse(markup)
|
|
38
|
+
p = @parse_context.new_parser(markup)
|
|
39
|
+
|
|
40
|
+
@variable_name = p.consume(:id)
|
|
41
|
+
|
|
42
|
+
unless p.id?("in")
|
|
43
|
+
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@collection_name = safe_parse_expression(p)
|
|
47
|
+
|
|
48
|
+
p.consume?(:comma)
|
|
49
|
+
|
|
50
|
+
@attributes = {}
|
|
51
|
+
while p.look(:id)
|
|
52
|
+
key = p.consume
|
|
53
|
+
unless ALLOWED_ATTRIBUTES.include?(key)
|
|
54
|
+
raise SyntaxError, options[:locale].t("errors.syntax.table_row_invalid_attribute", attribute: key)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
p.consume(:colon)
|
|
58
|
+
@attributes[key] = safe_parse_expression(p)
|
|
59
|
+
p.consume?(:comma)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
p.consume(:end_of_string)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def strict_parse(markup)
|
|
66
|
+
lax_parse(markup)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def lax_parse(markup)
|
|
33
70
|
if markup =~ Syntax
|
|
34
71
|
@variable_name = Regexp.last_match(1)
|
|
35
72
|
@collection_name = parse_expression(Regexp.last_match(2))
|
data/lib/liquid/template.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Liquid
|
|
4
4
|
# Templates are central to liquid.
|
|
5
|
-
#
|
|
5
|
+
# Interpreting templates is a two step process. First you compile the
|
|
6
6
|
# source code you got. During compile time some extensive error checking is performed.
|
|
7
7
|
# your code should expect to get some SyntaxErrors.
|
|
8
8
|
#
|
|
@@ -24,7 +24,8 @@ module Liquid
|
|
|
24
24
|
# Sets how strict the parser should be.
|
|
25
25
|
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
|
26
26
|
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
|
27
|
-
# :strict
|
|
27
|
+
# :strict enforces correct syntax for most tags
|
|
28
|
+
# :rigid enforces correct syntax for all tags
|
|
28
29
|
def error_mode=(mode)
|
|
29
30
|
Deprecations.warn("Template.error_mode=", "Environment#error_mode=")
|
|
30
31
|
Environment.default.error_mode = mode
|
data/lib/liquid/utils.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Liquid
|
|
4
4
|
module Utils
|
|
5
|
+
DECIMAL_REGEX = /\A-?\d+\.\d+\z/
|
|
6
|
+
UNIX_TIMESTAMP_REGEX = /\A\d+\z/
|
|
7
|
+
|
|
5
8
|
def self.slice_collection(collection, from, to)
|
|
6
9
|
if (from != 0 || !to.nil?) && collection.respond_to?(:load_slice)
|
|
7
10
|
collection.load_slice(from, to)
|
|
@@ -52,7 +55,7 @@ module Liquid
|
|
|
52
55
|
when Numeric
|
|
53
56
|
obj
|
|
54
57
|
when String
|
|
55
|
-
|
|
58
|
+
DECIMAL_REGEX.match?(obj.strip) ? BigDecimal(obj) : obj.to_i
|
|
56
59
|
else
|
|
57
60
|
if obj.respond_to?(:to_number)
|
|
58
61
|
obj.to_number
|
|
@@ -73,7 +76,7 @@ module Liquid
|
|
|
73
76
|
case obj
|
|
74
77
|
when 'now', 'today'
|
|
75
78
|
Time.now
|
|
76
|
-
when
|
|
79
|
+
when UNIX_TIMESTAMP_REGEX, Integer
|
|
77
80
|
Time.at(obj.to_i)
|
|
78
81
|
when String
|
|
79
82
|
Time.parse(obj)
|
data/lib/liquid/variable.rb
CHANGED
|
@@ -54,7 +54,7 @@ module Liquid
|
|
|
54
54
|
next unless f =~ /\w+/
|
|
55
55
|
filtername = Regexp.last_match(0)
|
|
56
56
|
filterargs = f.scan(FilterArgsRegex).flatten
|
|
57
|
-
@filters <<
|
|
57
|
+
@filters << lax_parse_filter_expressions(filtername, filterargs)
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
end
|
|
@@ -65,15 +65,26 @@ module Liquid
|
|
|
65
65
|
|
|
66
66
|
return if p.look(:end_of_string)
|
|
67
67
|
|
|
68
|
-
@name = parse_context.
|
|
68
|
+
@name = parse_context.safe_parse_expression(p)
|
|
69
69
|
while p.consume?(:pipe)
|
|
70
70
|
filtername = p.consume(:id)
|
|
71
71
|
filterargs = p.consume?(:colon) ? parse_filterargs(p) : Const::EMPTY_ARRAY
|
|
72
|
-
@filters <<
|
|
72
|
+
@filters << lax_parse_filter_expressions(filtername, filterargs)
|
|
73
73
|
end
|
|
74
74
|
p.consume(:end_of_string)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def rigid_parse(markup)
|
|
78
|
+
@filters = []
|
|
79
|
+
p = @parse_context.new_parser(markup)
|
|
80
|
+
|
|
81
|
+
return if p.look(:end_of_string)
|
|
82
|
+
|
|
83
|
+
@name = parse_context.safe_parse_expression(p)
|
|
84
|
+
@filters << rigid_parse_filter_expressions(p) while p.consume?(:pipe)
|
|
85
|
+
p.consume(:end_of_string)
|
|
86
|
+
end
|
|
87
|
+
|
|
77
88
|
def parse_filterargs(p)
|
|
78
89
|
# first argument
|
|
79
90
|
filterargs = [p.argument]
|
|
@@ -122,7 +133,7 @@ module Liquid
|
|
|
122
133
|
|
|
123
134
|
private
|
|
124
135
|
|
|
125
|
-
def
|
|
136
|
+
def lax_parse_filter_expressions(filter_name, unparsed_args)
|
|
126
137
|
filter_args = []
|
|
127
138
|
keyword_args = nil
|
|
128
139
|
unparsed_args.each do |a|
|
|
@@ -138,6 +149,46 @@ module Liquid
|
|
|
138
149
|
result
|
|
139
150
|
end
|
|
140
151
|
|
|
152
|
+
# Surprisingly, positional and keyword arguments can be mixed.
|
|
153
|
+
#
|
|
154
|
+
# filter = filtername [":" filterargs?]
|
|
155
|
+
# filterargs = argument ("," argument)*
|
|
156
|
+
# argument = (positional_argument | keyword_argument)
|
|
157
|
+
# positional_argument = expression
|
|
158
|
+
# keyword_argument = id ":" expression
|
|
159
|
+
def rigid_parse_filter_expressions(p)
|
|
160
|
+
filtername = p.consume(:id)
|
|
161
|
+
filter_args = []
|
|
162
|
+
keyword_args = {}
|
|
163
|
+
|
|
164
|
+
if p.consume?(:colon)
|
|
165
|
+
# Parse first argument (no leading comma)
|
|
166
|
+
argument(p, filter_args, keyword_args) unless end_of_arguments?(p)
|
|
167
|
+
|
|
168
|
+
# Parse remaining arguments (with leading commas) and optional trailing comma
|
|
169
|
+
argument(p, filter_args, keyword_args) while p.consume?(:comma) && !end_of_arguments?(p)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
result = [filtername, filter_args]
|
|
173
|
+
result << keyword_args unless keyword_args.empty?
|
|
174
|
+
result
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def argument(p, positional_arguments, keyword_arguments)
|
|
178
|
+
if p.look(:id) && p.look(:colon, 1)
|
|
179
|
+
key = p.consume(:id)
|
|
180
|
+
p.consume(:colon)
|
|
181
|
+
value = parse_context.safe_parse_expression(p)
|
|
182
|
+
keyword_arguments[key] = value
|
|
183
|
+
else
|
|
184
|
+
positional_arguments << parse_context.safe_parse_expression(p)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def end_of_arguments?(p)
|
|
189
|
+
p.look(:pipe) || p.look(:end_of_string)
|
|
190
|
+
end
|
|
191
|
+
|
|
141
192
|
def evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
|
142
193
|
parsed_args = filter_args.map { |expr| context.evaluate(expr) }
|
|
143
194
|
if filter_kwargs
|
data/lib/liquid/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: liquid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobias Lütke
|
|
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
159
159
|
- !ruby/object:Gem::Version
|
|
160
160
|
version: 1.3.7
|
|
161
161
|
requirements: []
|
|
162
|
-
rubygems_version: 3.
|
|
162
|
+
rubygems_version: 3.7.2
|
|
163
163
|
specification_version: 4
|
|
164
164
|
summary: A secure, non-evaling end user template engine with aesthetic markup.
|
|
165
165
|
test_files: []
|