liquid 4.0.3 → 5.4.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 +89 -0
- data/README.md +10 -4
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +169 -57
- data/lib/liquid/condition.rb +48 -21
- data/lib/liquid/context.rb +111 -52
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -18
- data/lib/liquid/expression.rb +28 -32
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +54 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +30 -23
- data/lib/liquid/locales/en.yml +8 -5
- data/lib/liquid/parse_context.rb +20 -4
- data/lib/liquid/parse_tree_visitor.rb +2 -2
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +13 -3
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +551 -114
- data/lib/liquid/strainer_factory.rb +41 -0
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +64 -5
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tags/assign.rb +36 -18
- data/lib/liquid/tags/break.rb +16 -3
- data/lib/liquid/tags/capture.rb +24 -18
- data/lib/liquid/tags/case.rb +61 -27
- data/lib/liquid/tags/comment.rb +18 -3
- data/lib/liquid/tags/continue.rb +16 -12
- data/lib/liquid/tags/cycle.rb +37 -25
- data/lib/liquid/tags/decrement.rb +22 -20
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +90 -87
- data/lib/liquid/tags/if.rb +50 -32
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +49 -60
- data/lib/liquid/tags/increment.rb +23 -17
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +25 -11
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +45 -19
- data/lib/liquid/tags/unless.rb +38 -19
- data/lib/liquid/template.rb +52 -72
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +18 -10
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +13 -3
- data/lib/liquid/variable.rb +49 -44
- data/lib/liquid/variable_lookup.rb +18 -10
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +18 -6
- metadata +20 -108
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- data/test/fixtures/en_locale.yml +0 -9
- data/test/integration/assign_test.rb +0 -48
- data/test/integration/blank_test.rb +0 -106
- data/test/integration/block_test.rb +0 -12
- data/test/integration/capture_test.rb +0 -50
- data/test/integration/context_test.rb +0 -32
- data/test/integration/document_test.rb +0 -19
- data/test/integration/drop_test.rb +0 -273
- data/test/integration/error_handling_test.rb +0 -260
- data/test/integration/filter_test.rb +0 -178
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -123
- data/test/integration/parse_tree_visitor_test.rb +0 -247
- data/test/integration/parsing_quirks_test.rb +0 -122
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -80
- data/test/integration/standard_filter_test.rb +0 -776
- data/test/integration/tags/break_tag_test.rb +0 -15
- data/test/integration/tags/continue_tag_test.rb +0 -15
- data/test/integration/tags/for_tag_test.rb +0 -410
- data/test/integration/tags/if_else_tag_test.rb +0 -188
- data/test/integration/tags/include_tag_test.rb +0 -253
- data/test/integration/tags/increment_tag_test.rb +0 -23
- data/test/integration/tags/raw_tag_test.rb +0 -31
- data/test/integration/tags/standard_tag_test.rb +0 -296
- data/test/integration/tags/statements_test.rb +0 -111
- data/test/integration/tags/table_row_test.rb +0 -64
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -332
- data/test/integration/trim_mode_test.rb +0 -529
- data/test/integration/variable_test.rb +0 -96
- data/test/test_helper.rb +0 -116
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -166
- data/test/unit/context_unit_test.rb +0 -489
- data/test/unit/file_system_unit_test.rb +0 -35
- data/test/unit/i18n_unit_test.rb +0 -37
- data/test/unit/lexer_unit_test.rb +0 -51
- data/test/unit/parser_unit_test.rb +0 -82
- data/test/unit/regexp_unit_test.rb +0 -44
- data/test/unit/strainer_unit_test.rb +0 -164
- data/test/unit/tag_unit_test.rb +0 -21
- data/test/unit/tags/case_tag_unit_test.rb +0 -10
- data/test/unit/tags/for_tag_unit_test.rb +0 -13
- data/test/unit/tags/if_tag_unit_test.rb +0 -8
- data/test/unit/template_unit_test.rb +0 -78
- data/test/unit/tokenizer_unit_test.rb +0 -55
- data/test/unit/variable_unit_test.rb +0 -162
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8408df245a1cc22ee1154fe5387e3e41eb3c740f7277e7d3736c1863d387e47
|
4
|
+
data.tar.gz: 64549a58828fd7e9e0eb7310cb4371e75e64a7c3249fe9ebd021039e3334bba1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29aaff16e3bc464712cdcac3aa205df943132198ef9568d46f4a123d327849f7ead7bd669a095eae0425581eb59ea08874ac38664bf9d8df00b0aa99917c7768
|
7
|
+
data.tar.gz: 9f6070c39733b7f4064f70676b1f886fc61070f3ba8921360b9c5fcdb09c217b3e58c3d59d8293b24b6c18e55899ed17552a2f867ea8eb95e7dee9a7deb29ae5
|
data/History.md
CHANGED
@@ -1,5 +1,94 @@
|
|
1
1
|
# Liquid Change Log
|
2
2
|
|
3
|
+
## 5.4.0 2022-07-29
|
4
|
+
|
5
|
+
### Breaking Changes
|
6
|
+
* Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]
|
7
|
+
|
8
|
+
### Features
|
9
|
+
* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]
|
10
|
+
|
11
|
+
### Fixes
|
12
|
+
* `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]
|
13
|
+
* Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]
|
14
|
+
* Fix `ParseTreeVisitor` for `with` variable expressions in `Render` tag (#1596) [CP Clermont]
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
* Liquid::Context#registers now always returns a Liquid::Registers object, though supports the most used Hash functions for compatibility (#1553)
|
18
|
+
|
19
|
+
## 5.3.0 2022-03-22
|
20
|
+
|
21
|
+
### Fixes
|
22
|
+
* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
|
23
|
+
* Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]
|
24
|
+
|
25
|
+
### Deprecation
|
26
|
+
* Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]
|
27
|
+
|
28
|
+
## 5.2.0 2022-03-01
|
29
|
+
|
30
|
+
### Features
|
31
|
+
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
|
32
|
+
* Eagerly cache global filters (#1524) [Jean Boussier]
|
33
|
+
|
34
|
+
### Fixes
|
35
|
+
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
|
36
|
+
* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
|
37
|
+
|
38
|
+
## 5.1.0 / 2021-09-09
|
39
|
+
|
40
|
+
### Features
|
41
|
+
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
|
42
|
+
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
|
43
|
+
|
44
|
+
### Fixes
|
45
|
+
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
|
46
|
+
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
|
47
|
+
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
|
48
|
+
|
49
|
+
## 5.0.1 / 2021-03-24
|
50
|
+
|
51
|
+
### Fixes
|
52
|
+
* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
|
53
|
+
* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
|
54
|
+
* Handle carriage return in newlines_to_br (#1391) [Unending]
|
55
|
+
|
56
|
+
### Performance Improvements
|
57
|
+
* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
|
58
|
+
|
59
|
+
## 5.0.0 / 2021-01-06
|
60
|
+
|
61
|
+
### Features
|
62
|
+
* Add new `{% render %}` tag (#1122) [Samuel Doiron]
|
63
|
+
* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
|
64
|
+
* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
|
65
|
+
* Add [usage tracking](README.md#usage-tracking) [Mike Angell]
|
66
|
+
* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
|
67
|
+
* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
|
68
|
+
|
69
|
+
### Fixes
|
70
|
+
* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
|
71
|
+
* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
|
72
|
+
* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
|
73
|
+
* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
|
74
|
+
* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
|
75
|
+
* Fix duplication of text in raw tags (#1304) [Peter Zhu]
|
76
|
+
* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
|
77
|
+
* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
|
78
|
+
|
79
|
+
### Breaking Changes
|
80
|
+
* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
|
81
|
+
* Remove support for taint checking (#1268) [Dylan Thacker-Smith]
|
82
|
+
* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
|
83
|
+
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
|
84
|
+
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
|
85
|
+
* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
|
86
|
+
* And several internal changes
|
87
|
+
|
88
|
+
### Performance Improvements
|
89
|
+
* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
|
90
|
+
* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
|
91
|
+
|
3
92
|
## 4.0.3 / 2019-03-12
|
4
93
|
|
5
94
|
### Fixed
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
* [Contributing guidelines](CONTRIBUTING.md)
|
7
7
|
* [Version history](History.md)
|
8
|
-
* [Liquid documentation from Shopify](
|
8
|
+
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
|
9
9
|
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
10
10
|
* [Website](http://liquidmarkup.org/)
|
11
11
|
|
@@ -56,20 +56,20 @@ For standard use you can just pass it the content of a file and call render with
|
|
56
56
|
|
57
57
|
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
58
58
|
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
59
|
-
it very hard to debug and can lead to unexpected behaviour.
|
59
|
+
it very hard to debug and can lead to unexpected behaviour.
|
60
60
|
|
61
61
|
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
|
62
62
|
when templates are invalid. You can enable this new parser like this:
|
63
63
|
|
64
64
|
```ruby
|
65
65
|
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
|
66
|
-
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
|
66
|
+
Liquid::Template.error_mode = :warn # Adds strict errors to template.errors but continues as normal
|
67
67
|
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
|
68
68
|
```
|
69
69
|
|
70
70
|
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
|
71
71
|
```ruby
|
72
|
-
Liquid::Template.parse(source, :
|
72
|
+
Liquid::Template.parse(source, error_mode: :strict)
|
73
73
|
```
|
74
74
|
This is useful for doing things like enabling strict mode only in the theme editor.
|
75
75
|
|
@@ -106,3 +106,9 @@ template = Liquid::Template.parse("{{x}} {{y}}")
|
|
106
106
|
template.render!({ 'x' => 1}, { strict_variables: true })
|
107
107
|
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
|
108
108
|
```
|
109
|
+
|
110
|
+
### Usage tracking
|
111
|
+
|
112
|
+
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
|
113
|
+
|
114
|
+
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
|
data/lib/liquid/block.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class Block < Tag
|
3
5
|
MAX_DEPTH = 100
|
@@ -8,11 +10,13 @@ module Liquid
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def parse(tokens)
|
11
|
-
@body =
|
13
|
+
@body = new_body
|
12
14
|
while parse_body(@body, tokens)
|
13
15
|
end
|
16
|
+
@body.freeze
|
14
17
|
end
|
15
18
|
|
19
|
+
# For backwards compatibility
|
16
20
|
def render(context)
|
17
21
|
@body.render(context)
|
18
22
|
end
|
@@ -25,20 +29,29 @@ module Liquid
|
|
25
29
|
@body.nodelist
|
26
30
|
end
|
27
31
|
|
28
|
-
def unknown_tag(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
def unknown_tag(tag_name, _markup, _tokenizer)
|
33
|
+
Block.raise_unknown_tag(tag_name, block_name, block_delimiter, parse_context)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
|
38
|
+
if tag == 'else'
|
39
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
|
40
|
+
block_name: block_name)
|
41
|
+
elsif tag.start_with?('end')
|
42
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
|
34
43
|
tag: tag,
|
35
44
|
block_name: block_name,
|
36
|
-
block_delimiter: block_delimiter)
|
45
|
+
block_delimiter: block_delimiter)
|
37
46
|
else
|
38
|
-
raise SyntaxError
|
47
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
39
48
|
end
|
40
49
|
end
|
41
50
|
|
51
|
+
def raise_tag_never_closed(block_name)
|
52
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
|
53
|
+
end
|
54
|
+
|
42
55
|
def block_name
|
43
56
|
@tag_name
|
44
57
|
end
|
@@ -47,11 +60,17 @@ module Liquid
|
|
47
60
|
@block_delimiter ||= "end#{block_name}"
|
48
61
|
end
|
49
62
|
|
50
|
-
|
63
|
+
private
|
64
|
+
|
65
|
+
# @api public
|
66
|
+
def new_body
|
67
|
+
parse_context.new_block_body
|
68
|
+
end
|
51
69
|
|
70
|
+
# @api public
|
52
71
|
def parse_body(body, tokens)
|
53
72
|
if parse_context.depth >= MAX_DEPTH
|
54
|
-
raise StackLevelError, "Nesting too deep"
|
73
|
+
raise StackLevelError, "Nesting too deep"
|
55
74
|
end
|
56
75
|
parse_context.depth += 1
|
57
76
|
begin
|
@@ -59,9 +78,7 @@ module Liquid
|
|
59
78
|
@blank &&= body.blank?
|
60
79
|
|
61
80
|
return false if end_tag_name == block_delimiter
|
62
|
-
unless end_tag_name
|
63
|
-
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
64
|
-
end
|
81
|
+
raise_tag_never_closed(block_name) unless end_tag_name
|
65
82
|
|
66
83
|
# this tag is not registered with the system
|
67
84
|
# pass it to the current block for special handling or error reporting
|
data/lib/liquid/block_body.rb
CHANGED
@@ -1,32 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
1
5
|
module Liquid
|
2
6
|
class BlockBody
|
3
|
-
|
4
|
-
|
7
|
+
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
|
8
|
+
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
9
|
+
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
5
10
|
WhitespaceOrNothing = /\A\s*\z/
|
6
|
-
TAGSTART
|
7
|
-
VARSTART
|
11
|
+
TAGSTART = "{%"
|
12
|
+
VARSTART = "{{"
|
8
13
|
|
9
14
|
attr_reader :nodelist
|
10
15
|
|
11
16
|
def initialize
|
12
17
|
@nodelist = []
|
13
|
-
@blank
|
18
|
+
@blank = true
|
14
19
|
end
|
15
20
|
|
16
|
-
def parse(tokenizer, parse_context)
|
21
|
+
def parse(tokenizer, parse_context, &block)
|
22
|
+
raise FrozenError, "can't modify frozen Liquid::BlockBody" if frozen?
|
23
|
+
|
17
24
|
parse_context.line_number = tokenizer.line_number
|
18
|
-
|
25
|
+
|
26
|
+
if tokenizer.for_liquid_tag
|
27
|
+
parse_for_liquid_tag(tokenizer, parse_context, &block)
|
28
|
+
else
|
29
|
+
parse_for_document(tokenizer, parse_context, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def freeze
|
34
|
+
@nodelist.freeze
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
private def parse_for_liquid_tag(tokenizer, parse_context)
|
39
|
+
while (token = tokenizer.shift)
|
40
|
+
unless token.empty? || token.match?(WhitespaceOrNothing)
|
41
|
+
unless token =~ LiquidTagToken
|
42
|
+
# line isn't empty but didn't match tag syntax, yield and let the
|
43
|
+
# caller raise a syntax error
|
44
|
+
return yield token, token
|
45
|
+
end
|
46
|
+
tag_name = Regexp.last_match(1)
|
47
|
+
markup = Regexp.last_match(2)
|
48
|
+
unless (tag = registered_tags[tag_name])
|
49
|
+
# end parsing if we reach an unknown tag and let the caller decide
|
50
|
+
# determine how to proceed
|
51
|
+
return yield tag_name, markup
|
52
|
+
end
|
53
|
+
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
54
|
+
@blank &&= new_tag.blank?
|
55
|
+
@nodelist << new_tag
|
56
|
+
end
|
57
|
+
parse_context.line_number = tokenizer.line_number
|
58
|
+
end
|
59
|
+
|
60
|
+
yield nil, nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
def self.unknown_tag_in_liquid_tag(tag, parse_context)
|
65
|
+
Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @api private
|
69
|
+
def self.raise_missing_tag_terminator(token, parse_context)
|
70
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
def self.raise_missing_variable_terminator(token, parse_context)
|
75
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def self.render_node(context, output, node)
|
80
|
+
node.render_to_output_buffer(context, output)
|
81
|
+
rescue => exc
|
82
|
+
blank_tag = !node.instance_of?(Variable) && node.blank?
|
83
|
+
rescue_render_node(context, output, node.line_number, exc, blank_tag)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
def self.rescue_render_node(context, output, line_number, exc, blank_tag)
|
88
|
+
case exc
|
89
|
+
when MemoryError
|
90
|
+
raise
|
91
|
+
when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
|
92
|
+
context.handle_error(exc, line_number)
|
93
|
+
else
|
94
|
+
error_message = context.handle_error(exc, line_number)
|
95
|
+
unless blank_tag # conditional for backwards compatibility
|
96
|
+
output << error_message
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private def parse_liquid_tag(markup, parse_context)
|
102
|
+
liquid_tag_tokenizer = parse_context.new_tokenizer(
|
103
|
+
markup, start_line_number: parse_context.line_number, for_liquid_tag: true
|
104
|
+
)
|
105
|
+
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
|
106
|
+
if end_tag_name
|
107
|
+
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private def parse_for_document(tokenizer, parse_context)
|
113
|
+
while (token = tokenizer.shift)
|
19
114
|
next if token.empty?
|
20
115
|
case
|
21
116
|
when token.start_with?(TAGSTART)
|
22
117
|
whitespace_handler(token, parse_context)
|
23
118
|
unless token =~ FullToken
|
24
|
-
raise_missing_tag_terminator(token, parse_context)
|
119
|
+
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
120
|
+
end
|
121
|
+
tag_name = Regexp.last_match(2)
|
122
|
+
markup = Regexp.last_match(4)
|
123
|
+
|
124
|
+
if parse_context.line_number
|
125
|
+
# newlines inside the tag should increase the line number,
|
126
|
+
# particularly important for multiline {% liquid %} tags
|
127
|
+
parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
|
128
|
+
end
|
129
|
+
|
130
|
+
if tag_name == 'liquid'
|
131
|
+
parse_liquid_tag(markup, parse_context)
|
132
|
+
next
|
25
133
|
end
|
26
|
-
|
27
|
-
|
28
|
-
# fetch the tag from registered blocks
|
29
|
-
unless tag = registered_tags[tag_name]
|
134
|
+
|
135
|
+
unless (tag = registered_tags[tag_name])
|
30
136
|
# end parsing if we reach an unknown tag and let the caller decide
|
31
137
|
# determine how to proceed
|
32
138
|
return yield tag_name, markup
|
@@ -44,7 +150,7 @@ module Liquid
|
|
44
150
|
end
|
45
151
|
parse_context.trim_whitespace = false
|
46
152
|
@nodelist << token
|
47
|
-
@blank &&=
|
153
|
+
@blank &&= token.match?(WhitespaceOrNothing)
|
48
154
|
end
|
49
155
|
parse_context.line_number = tokenizer.line_number
|
50
156
|
end
|
@@ -55,8 +161,12 @@ module Liquid
|
|
55
161
|
def whitespace_handler(token, parse_context)
|
56
162
|
if token[2] == WhitespaceControl
|
57
163
|
previous_token = @nodelist.last
|
58
|
-
if previous_token.is_a?
|
164
|
+
if previous_token.is_a?(String)
|
165
|
+
first_byte = previous_token.getbyte(0)
|
59
166
|
previous_token.rstrip!
|
167
|
+
if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte
|
168
|
+
previous_token << first_byte
|
169
|
+
end
|
60
170
|
end
|
61
171
|
end
|
62
172
|
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
@@ -66,74 +176,76 @@ module Liquid
|
|
66
176
|
@blank
|
67
177
|
end
|
68
178
|
|
179
|
+
# Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)
|
180
|
+
# with a blank body.
|
181
|
+
#
|
182
|
+
# For example, in a conditional assignment like the following
|
183
|
+
#
|
184
|
+
# ```
|
185
|
+
# {% if size > max_size %}
|
186
|
+
# {% assign size = max_size %}
|
187
|
+
# {% endif %}
|
188
|
+
# ```
|
189
|
+
#
|
190
|
+
# we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method
|
191
|
+
# will remove them to reduce the render output size.
|
192
|
+
#
|
193
|
+
# Note that it is now preferred to use the `liquid` tag for this use case.
|
194
|
+
def remove_blank_strings
|
195
|
+
raise "remove_blank_strings only support being called on a blank block body" unless @blank
|
196
|
+
@nodelist.reject! { |node| node.instance_of?(String) }
|
197
|
+
end
|
198
|
+
|
69
199
|
def render(context)
|
70
|
-
|
71
|
-
|
200
|
+
render_to_output_buffer(context, +'')
|
201
|
+
end
|
202
|
+
|
203
|
+
def render_to_output_buffer(context, output)
|
204
|
+
freeze unless frozen?
|
205
|
+
|
206
|
+
context.resource_limits.increment_render_score(@nodelist.length)
|
72
207
|
|
73
208
|
idx = 0
|
74
|
-
while node = @nodelist[idx]
|
75
|
-
|
76
|
-
when String
|
77
|
-
check_resources(context, node)
|
209
|
+
while (node = @nodelist[idx])
|
210
|
+
if node.instance_of?(String)
|
78
211
|
output << node
|
79
|
-
|
80
|
-
|
81
|
-
when Block
|
82
|
-
render_node_to_output(node, output, context, node.blank?)
|
83
|
-
break if context.interrupt? # might have happened in a for-block
|
84
|
-
when Continue, Break
|
212
|
+
else
|
213
|
+
render_node(context, output, node)
|
85
214
|
# If we get an Interrupt that means the block must stop processing. An
|
86
215
|
# Interrupt is any command that stops block execution such as {% break %}
|
87
|
-
# or {% continue %}
|
88
|
-
context.
|
89
|
-
break
|
90
|
-
else # Other non-Block tags
|
91
|
-
render_node_to_output(node, output, context)
|
92
|
-
break if context.interrupt? # might have happened through an include
|
216
|
+
# or {% continue %}. These tags may also occur through Block or Include tags.
|
217
|
+
break if context.interrupt? # might have happened in a for-block
|
93
218
|
end
|
94
219
|
idx += 1
|
220
|
+
|
221
|
+
context.resource_limits.increment_write_score(output)
|
95
222
|
end
|
96
223
|
|
97
|
-
output
|
224
|
+
output
|
98
225
|
end
|
99
226
|
|
100
227
|
private
|
101
228
|
|
102
|
-
def
|
103
|
-
|
104
|
-
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
105
|
-
check_resources(context, node_output)
|
106
|
-
output << node_output unless skip_output
|
107
|
-
rescue MemoryError => e
|
108
|
-
raise e
|
109
|
-
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
110
|
-
context.handle_error(e, node.line_number)
|
111
|
-
output << nil
|
112
|
-
rescue ::StandardError => e
|
113
|
-
line_number = node.is_a?(String) ? nil : node.line_number
|
114
|
-
output << context.handle_error(e, line_number)
|
115
|
-
end
|
116
|
-
|
117
|
-
def check_resources(context, node_output)
|
118
|
-
context.resource_limits.render_length += node_output.length
|
119
|
-
return unless context.resource_limits.reached?
|
120
|
-
raise MemoryError.new("Memory limits exceeded".freeze)
|
229
|
+
def render_node(context, output, node)
|
230
|
+
BlockBody.render_node(context, output, node)
|
121
231
|
end
|
122
232
|
|
123
233
|
def create_variable(token, parse_context)
|
124
|
-
token
|
125
|
-
markup =
|
234
|
+
if token =~ ContentOfVariable
|
235
|
+
markup = Regexp.last_match(1)
|
126
236
|
return Variable.new(markup, parse_context)
|
127
237
|
end
|
128
|
-
raise_missing_variable_terminator(token, parse_context)
|
238
|
+
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
129
239
|
end
|
130
240
|
|
241
|
+
# @deprecated Use {.raise_missing_tag_terminator} instead
|
131
242
|
def raise_missing_tag_terminator(token, parse_context)
|
132
|
-
|
243
|
+
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
133
244
|
end
|
134
245
|
|
246
|
+
# @deprecated Use {.raise_missing_variable_terminator} instead
|
135
247
|
def raise_missing_variable_terminator(token, parse_context)
|
136
|
-
|
248
|
+
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
137
249
|
end
|
138
250
|
|
139
251
|
def registered_tags
|
data/lib/liquid/condition.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Container for liquid nodes which conveniently wraps decision making logic
|
3
5
|
#
|
@@ -6,41 +8,60 @@ module Liquid
|
|
6
8
|
# c = Condition.new(1, '==', 1)
|
7
9
|
# c.evaluate #=> true
|
8
10
|
#
|
9
|
-
class Condition
|
11
|
+
class Condition # :nodoc:
|
10
12
|
@@operators = {
|
11
|
-
'=='
|
12
|
-
'!='
|
13
|
-
'<>'
|
14
|
-
'<'
|
15
|
-
'>'
|
16
|
-
'>='
|
17
|
-
'<='
|
18
|
-
'contains'
|
13
|
+
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
14
|
+
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
15
|
+
'<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
16
|
+
'<' => :<,
|
17
|
+
'>' => :>,
|
18
|
+
'>=' => :>=,
|
19
|
+
'<=' => :<=,
|
20
|
+
'contains' => lambda do |_cond, left, right|
|
19
21
|
if left && right && left.respond_to?(:include?)
|
20
22
|
right = right.to_s if left.is_a?(String)
|
21
23
|
left.include?(right)
|
22
24
|
else
|
23
25
|
false
|
24
26
|
end
|
27
|
+
end,
|
28
|
+
}
|
29
|
+
|
30
|
+
class MethodLiteral
|
31
|
+
attr_reader :method_name, :to_s
|
32
|
+
|
33
|
+
def initialize(method_name, to_s)
|
34
|
+
@method_name = method_name
|
35
|
+
@to_s = to_s
|
25
36
|
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@@method_literals = {
|
40
|
+
'blank' => MethodLiteral.new(:blank?, '').freeze,
|
41
|
+
'empty' => MethodLiteral.new(:empty?, '').freeze,
|
26
42
|
}
|
27
43
|
|
28
44
|
def self.operators
|
29
45
|
@@operators
|
30
46
|
end
|
31
47
|
|
48
|
+
def self.parse_expression(parse_context, markup)
|
49
|
+
@@method_literals[markup] || parse_context.parse_expression(markup)
|
50
|
+
end
|
51
|
+
|
32
52
|
attr_reader :attachment, :child_condition
|
33
53
|
attr_accessor :left, :operator, :right
|
34
54
|
|
35
55
|
def initialize(left = nil, operator = nil, right = nil)
|
36
|
-
@left
|
56
|
+
@left = left
|
37
57
|
@operator = operator
|
38
|
-
@right
|
58
|
+
@right = right
|
59
|
+
|
39
60
|
@child_relation = nil
|
40
61
|
@child_condition = nil
|
41
62
|
end
|
42
63
|
|
43
|
-
def evaluate(context =
|
64
|
+
def evaluate(context = deprecated_default_context)
|
44
65
|
condition = self
|
45
66
|
result = nil
|
46
67
|
loop do
|
@@ -60,12 +81,12 @@ module Liquid
|
|
60
81
|
end
|
61
82
|
|
62
83
|
def or(condition)
|
63
|
-
@child_relation
|
84
|
+
@child_relation = :or
|
64
85
|
@child_condition = condition
|
65
86
|
end
|
66
87
|
|
67
88
|
def and(condition)
|
68
|
-
@child_relation
|
89
|
+
@child_relation = :and
|
69
90
|
@child_condition = condition
|
70
91
|
end
|
71
92
|
|
@@ -78,7 +99,7 @@ module Liquid
|
|
78
99
|
end
|
79
100
|
|
80
101
|
def inspect
|
81
|
-
"#<Condition #{[@left, @operator, @right].compact.join(' '
|
102
|
+
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
82
103
|
end
|
83
104
|
|
84
105
|
protected
|
@@ -88,7 +109,7 @@ module Liquid
|
|
88
109
|
private
|
89
110
|
|
90
111
|
def equal_variables(left, right)
|
91
|
-
if left.is_a?(
|
112
|
+
if left.is_a?(MethodLiteral)
|
92
113
|
if right.respond_to?(left.method_name)
|
93
114
|
return right.send(left.method_name)
|
94
115
|
else
|
@@ -96,7 +117,7 @@ module Liquid
|
|
96
117
|
end
|
97
118
|
end
|
98
119
|
|
99
|
-
if right.is_a?(
|
120
|
+
if right.is_a?(MethodLiteral)
|
100
121
|
if left.respond_to?(right.method_name)
|
101
122
|
return left.send(right.method_name)
|
102
123
|
else
|
@@ -113,10 +134,10 @@ module Liquid
|
|
113
134
|
# return this as the result.
|
114
135
|
return context.evaluate(left) if op.nil?
|
115
136
|
|
116
|
-
left
|
117
|
-
right = context.evaluate(right)
|
137
|
+
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
|
138
|
+
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
|
118
139
|
|
119
|
-
operation = self.class.operators[op] || raise(Liquid::ArgumentError
|
140
|
+
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
|
120
141
|
|
121
142
|
if operation.respond_to?(:call)
|
122
143
|
operation.call(self, left, right)
|
@@ -124,11 +145,17 @@ module Liquid
|
|
124
145
|
begin
|
125
146
|
left.send(operation, right)
|
126
147
|
rescue ::ArgumentError => e
|
127
|
-
raise Liquid::ArgumentError
|
148
|
+
raise Liquid::ArgumentError, e.message
|
128
149
|
end
|
129
150
|
end
|
130
151
|
end
|
131
152
|
|
153
|
+
def deprecated_default_context
|
154
|
+
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
|
155
|
+
" and will be removed from Liquid 6.0.0.")
|
156
|
+
Context.new
|
157
|
+
end
|
158
|
+
|
132
159
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
133
160
|
def children
|
134
161
|
[
|