liquid 4.0.0.rc3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/History.md +93 -2
- data/README.md +8 -0
- data/lib/liquid.rb +18 -5
- data/lib/liquid/block.rb +47 -20
- data/lib/liquid/block_body.rb +190 -76
- data/lib/liquid/condition.rb +69 -29
- data/lib/liquid/context.rb +122 -76
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -25
- data/lib/liquid/expression.rb +30 -31
- data/lib/liquid/extensions.rb +8 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +11 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +35 -26
- data/lib/liquid/locales/en.yml +4 -2
- data/lib/liquid/parse_context.rb +17 -4
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- 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.rb +67 -86
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/range_lookup.rb +5 -3
- data/lib/liquid/register.rb +6 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +171 -57
- data/lib/liquid/static_registers.rb +44 -0
- data/lib/liquid/strainer_factory.rb +36 -0
- data/lib/liquid/strainer_template.rb +53 -0
- data/lib/liquid/tablerowloop_drop.rb +6 -4
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tags/assign.rb +32 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +41 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +35 -16
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +26 -0
- data/lib/liquid/tags/for.rb +79 -47
- data/lib/liquid/tags/if.rb +53 -30
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +42 -44
- data/lib/liquid/tags/increment.rb +7 -3
- data/lib/liquid/tags/raw.rb +14 -11
- data/lib/liquid/tags/render.rb +84 -0
- data/lib/liquid/tags/table_row.rb +32 -20
- data/lib/liquid/tags/unless.rb +15 -15
- data/lib/liquid/template.rb +60 -71
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +17 -9
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +6 -4
- data/lib/liquid/variable.rb +55 -38
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +3 -1
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +58 -0
- data/test/integration/capture_test.rb +18 -10
- data/test/integration/context_test.rb +608 -5
- data/test/integration/document_test.rb +4 -2
- data/test/integration/drop_test.rb +67 -83
- data/test/integration/error_handling_test.rb +90 -60
- data/test/integration/expression_test.rb +46 -0
- data/test/integration/filter_test.rb +53 -42
- data/test/integration/hash_ordering_test.rb +5 -3
- data/test/integration/output_test.rb +26 -24
- data/test/integration/parsing_quirks_test.rb +24 -8
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +41 -18
- data/test/integration/standard_filter_test.rb +523 -205
- data/test/integration/tag/disableable_test.rb +59 -0
- data/test/integration/tag_test.rb +45 -0
- data/test/integration/tags/break_tag_test.rb +4 -2
- data/test/integration/tags/continue_tag_test.rb +4 -2
- data/test/integration/tags/echo_test.rb +13 -0
- data/test/integration/tags/for_tag_test.rb +109 -53
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +83 -52
- data/test/integration/tags/increment_tag_test.rb +4 -2
- data/test/integration/tags/liquid_tag_test.rb +116 -0
- data/test/integration/tags/raw_tag_test.rb +14 -11
- data/test/integration/tags/render_tag_test.rb +213 -0
- data/test/integration/tags/standard_tag_test.rb +38 -31
- data/test/integration/tags/statements_test.rb +23 -21
- data/test/integration/tags/table_row_test.rb +2 -0
- data/test/integration/tags/unless_else_tag_test.rb +4 -2
- data/test/integration/template_test.rb +128 -121
- data/test/integration/trim_mode_test.rb +82 -44
- data/test/integration/variable_test.rb +46 -31
- data/test/test_helper.rb +75 -23
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +82 -72
- data/test/unit/file_system_unit_test.rb +6 -4
- data/test/unit/i18n_unit_test.rb +7 -5
- data/test/unit/lexer_unit_test.rb +12 -10
- data/test/unit/parse_tree_visitor_test.rb +247 -0
- data/test/unit/parser_unit_test.rb +37 -35
- data/test/unit/partial_cache_unit_test.rb +128 -0
- data/test/unit/regexp_unit_test.rb +17 -15
- data/test/unit/static_registers_unit_test.rb +156 -0
- data/test/unit/strainer_factory_unit_test.rb +100 -0
- data/test/unit/strainer_template_unit_test.rb +82 -0
- data/test/unit/tag_unit_test.rb +5 -3
- data/test/unit/tags/case_tag_unit_test.rb +3 -1
- data/test/unit/tags/for_tag_unit_test.rb +4 -2
- data/test/unit/tags/if_tag_unit_test.rb +3 -1
- data/test/unit/template_factory_unit_test.rb +12 -0
- data/test/unit/template_unit_test.rb +19 -10
- data/test/unit/tokenizer_unit_test.rb +19 -17
- data/test/unit/variable_unit_test.rb +51 -49
- metadata +83 -50
- data/lib/liquid/strainer.rb +0 -65
- data/test/unit/context_unit_test.rb +0 -483
- data/test/unit/strainer_unit_test.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0b1c036e6dd6d55418e6364b008551f5d3ba91f6e7dbc1c0ac3be43b235bd957
|
4
|
+
data.tar.gz: a6279802ed388bcffc8980c49bcb0034a0f98de7cd6730bf97f603c07bf13dcd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c5094a47d46c8de3ac8ac632dbec8b813c1e47af834c0037ed9b868f5a2dde4da8dfdd143ff34c41505c1425524f1879ca909525676028cadd9900cbd028e63
|
7
|
+
data.tar.gz: 7195f154c81283e8b7d99a07b82a2d9976b41be761efc528945f79ac0d77c2ff9c7632b3455f871b6db0471979c35e633f87e1c33900e4f9898012f836fd1496
|
data/History.md
CHANGED
@@ -1,8 +1,96 @@
|
|
1
1
|
# Liquid Change Log
|
2
2
|
|
3
|
-
##
|
3
|
+
## 5.0.0 / 2021-01-06
|
4
|
+
|
5
|
+
### Features
|
6
|
+
* Add new `{% render %}` tag (#1122) [Samuel Doiron]
|
7
|
+
* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
|
8
|
+
* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
|
9
|
+
* Add [usage tracking](README.md#usage-tracking) [Mike Angell]
|
10
|
+
* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
|
11
|
+
* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
|
12
|
+
|
13
|
+
### Fixes
|
14
|
+
* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
|
15
|
+
* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
|
16
|
+
* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
|
17
|
+
* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
|
18
|
+
* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
|
19
|
+
* Fix duplication of text in raw tags (#1304) [Peter Zhu]
|
20
|
+
* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
|
21
|
+
* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
|
22
|
+
|
23
|
+
### Breaking Changes
|
24
|
+
* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
|
25
|
+
* Remove support for taint checking (#1268) [Dylan Thacker-Smith]
|
26
|
+
* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
|
27
|
+
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
|
28
|
+
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
|
29
|
+
* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
|
30
|
+
* And several internal changes
|
31
|
+
|
32
|
+
### Performance Improvements
|
33
|
+
* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
|
34
|
+
* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
|
35
|
+
|
36
|
+
## 4.0.3 / 2019-03-12
|
37
|
+
|
38
|
+
### Fixed
|
39
|
+
* Fix break and continue tags inside included templates in loops (#1072) [Justin Li]
|
40
|
+
|
41
|
+
## 4.0.2 / 2019-03-08
|
42
|
+
|
43
|
+
### Changed
|
44
|
+
* Add `where` filter (#1026) [Samuel Doiron]
|
45
|
+
* Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber]
|
46
|
+
* Improve `strip_html` performance (#1032) [printercu]
|
47
|
+
|
48
|
+
### Fixed
|
49
|
+
* Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang]
|
50
|
+
* Validate the character encoding in url_decode (#1070) [Clayton Smith]
|
51
|
+
|
52
|
+
## 4.0.1 / 2018-10-09
|
4
53
|
|
5
54
|
### Changed
|
55
|
+
* Add benchmark group in Gemfile (#855) [Jerry Liu]
|
56
|
+
* Allow benchmarks to benchmark render by itself (#851) [Jerry Liu]
|
57
|
+
* Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith]
|
58
|
+
* Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith]
|
59
|
+
* Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield]
|
60
|
+
* Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith]
|
61
|
+
* Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith]
|
62
|
+
* Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith]
|
63
|
+
* Remove Spy Gem (#896) [Dylan Thacker-Smith]
|
64
|
+
* Add `collection_name` and `variable_name` reader to `For` block (#909)
|
65
|
+
* Symbols render as strings (#920) [Justin Li]
|
66
|
+
* Remove default value from Hash objects (#932) [Maxime Bedard]
|
67
|
+
* Remove one level of nesting (#944) [Dylan Thacker-Smith]
|
68
|
+
* Update Rubocop version (#952) [Justin Li]
|
69
|
+
* Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal]
|
70
|
+
* Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith]
|
71
|
+
* Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith]
|
72
|
+
* Add tests against Ruby 2.4 (#963) and 2.5 (#981)
|
73
|
+
* Replace RegExp literals with constants (#988) [Ashwin Maroli]
|
74
|
+
* Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli]
|
75
|
+
* Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith]
|
76
|
+
* Refactor and optimize rendering (#1005) [Christopher Aue]
|
77
|
+
* Add installation instruction (#1006) [Ben Gift]
|
78
|
+
* Remove Circle CI (#1010)
|
79
|
+
* Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO]
|
80
|
+
* Rename deprecated Rubocop name (#1027) [Justin Li]
|
81
|
+
|
82
|
+
### Fixed
|
83
|
+
* Handle `join` filter on non String joiners (#857) [Richard Monette]
|
84
|
+
* Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861)
|
85
|
+
* Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal]
|
86
|
+
* Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz]
|
87
|
+
* Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan]
|
88
|
+
|
89
|
+
## 4.0.0 / 2016-12-14 / branch "4-0-stable"
|
90
|
+
|
91
|
+
### Changed
|
92
|
+
* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]
|
93
|
+
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
|
6
94
|
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
7
95
|
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
8
96
|
* Improve loop performance (#681) [Florian Weingarten]
|
@@ -18,10 +106,13 @@
|
|
18
106
|
* Add concat filter to concatenate arrays (#429) [Diogo Beato]
|
19
107
|
* Ruby 1.9 support dropped (#491) [Justin Li]
|
20
108
|
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
|
21
|
-
* Remove
|
109
|
+
* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement)
|
22
110
|
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
|
23
111
|
|
24
112
|
### Fixed
|
113
|
+
|
114
|
+
* Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell]
|
115
|
+
* Fix include tag used with strict_variables (#828) [QuickPay]
|
25
116
|
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
|
26
117
|
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
|
27
118
|
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
|
data/README.md
CHANGED
@@ -42,6 +42,8 @@ Liquid is a template engine which was written with very specific requirements:
|
|
42
42
|
|
43
43
|
## How to use Liquid
|
44
44
|
|
45
|
+
Install Liquid by adding `gem 'liquid'` to your gemfile.
|
46
|
+
|
45
47
|
Liquid supports a very simple API based around the Liquid::Template class.
|
46
48
|
For standard use you can just pass it the content of a file and call render with a parameters hash.
|
47
49
|
|
@@ -104,3 +106,9 @@ template = Liquid::Template.parse("{{x}} {{y}}")
|
|
104
106
|
template.render!({ 'x' => 1}, { strict_variables: true })
|
105
107
|
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
|
106
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.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright (c) 2005 Tobias Luetke
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining
|
@@ -21,10 +23,10 @@
|
|
21
23
|
|
22
24
|
module Liquid
|
23
25
|
FilterSeparator = /\|/
|
24
|
-
ArgumentSeparator = ','
|
25
|
-
FilterArgumentSeparator = ':'
|
26
|
-
VariableAttributeSeparator = '.'
|
27
|
-
WhitespaceControl = '-'
|
26
|
+
ArgumentSeparator = ','
|
27
|
+
FilterArgumentSeparator = ':'
|
28
|
+
VariableAttributeSeparator = '.'
|
29
|
+
WhitespaceControl = '-'
|
28
30
|
TagStart = /\{\%/
|
29
31
|
TagEnd = /\%\}/
|
30
32
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
@@ -40,11 +42,14 @@ module Liquid
|
|
40
42
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
41
43
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
42
44
|
|
45
|
+
RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
|
46
|
+
|
43
47
|
singleton_class.send(:attr_accessor, :cache_classes)
|
44
48
|
self.cache_classes = true
|
45
49
|
end
|
46
50
|
|
47
51
|
require "liquid/version"
|
52
|
+
require 'liquid/parse_tree_visitor'
|
48
53
|
require 'liquid/lexer'
|
49
54
|
require 'liquid/parser'
|
50
55
|
require 'liquid/i18n'
|
@@ -54,11 +59,14 @@ require 'liquid/forloop_drop'
|
|
54
59
|
require 'liquid/extensions'
|
55
60
|
require 'liquid/errors'
|
56
61
|
require 'liquid/interrupts'
|
57
|
-
require 'liquid/
|
62
|
+
require 'liquid/strainer_factory'
|
63
|
+
require 'liquid/strainer_template'
|
58
64
|
require 'liquid/expression'
|
59
65
|
require 'liquid/context'
|
60
66
|
require 'liquid/parser_switching'
|
61
67
|
require 'liquid/tag'
|
68
|
+
require 'liquid/tag/disabler'
|
69
|
+
require 'liquid/tag/disableable'
|
62
70
|
require 'liquid/block'
|
63
71
|
require 'liquid/block_body'
|
64
72
|
require 'liquid/document'
|
@@ -73,6 +81,11 @@ require 'liquid/condition'
|
|
73
81
|
require 'liquid/utils'
|
74
82
|
require 'liquid/tokenizer'
|
75
83
|
require 'liquid/parse_context'
|
84
|
+
require 'liquid/partial_cache'
|
85
|
+
require 'liquid/usage'
|
86
|
+
require 'liquid/register'
|
87
|
+
require 'liquid/static_registers'
|
88
|
+
require 'liquid/template_factory'
|
76
89
|
|
77
90
|
# Load all the tags of the standard library
|
78
91
|
#
|
data/lib/liquid/block.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class Block < Tag
|
5
|
+
MAX_DEPTH = 100
|
6
|
+
|
3
7
|
def initialize(tag_name, markup, options)
|
4
8
|
super
|
5
9
|
@blank = true
|
6
10
|
end
|
7
11
|
|
8
12
|
def parse(tokens)
|
9
|
-
@body =
|
13
|
+
@body = new_body
|
10
14
|
while parse_body(@body, tokens)
|
11
15
|
end
|
16
|
+
@body.freeze
|
12
17
|
end
|
13
18
|
|
19
|
+
# For backwards compatibility
|
14
20
|
def render(context)
|
15
21
|
@body.render(context)
|
16
22
|
end
|
@@ -23,20 +29,29 @@ module Liquid
|
|
23
29
|
@body.nodelist
|
24
30
|
end
|
25
31
|
|
26
|
-
def unknown_tag(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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",
|
43
|
+
tag: tag,
|
33
44
|
block_name: block_name,
|
34
|
-
block_delimiter: block_delimiter)
|
45
|
+
block_delimiter: block_delimiter)
|
35
46
|
else
|
36
|
-
raise SyntaxError
|
47
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
37
48
|
end
|
38
49
|
end
|
39
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
|
+
|
40
55
|
def block_name
|
41
56
|
@tag_name
|
42
57
|
end
|
@@ -45,20 +60,32 @@ module Liquid
|
|
45
60
|
@block_delimiter ||= "end#{block_name}"
|
46
61
|
end
|
47
62
|
|
48
|
-
|
63
|
+
private
|
49
64
|
|
65
|
+
# @api public
|
66
|
+
def new_body
|
67
|
+
parse_context.new_block_body
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api public
|
50
71
|
def parse_body(body, tokens)
|
51
|
-
|
52
|
-
|
72
|
+
if parse_context.depth >= MAX_DEPTH
|
73
|
+
raise StackLevelError, "Nesting too deep"
|
74
|
+
end
|
75
|
+
parse_context.depth += 1
|
76
|
+
begin
|
77
|
+
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
|
78
|
+
@blank &&= body.blank?
|
53
79
|
|
54
|
-
|
55
|
-
|
56
|
-
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
57
|
-
end
|
80
|
+
return false if end_tag_name == block_delimiter
|
81
|
+
raise_tag_never_closed(block_name) unless end_tag_name
|
58
82
|
|
59
|
-
|
60
|
-
|
61
|
-
|
83
|
+
# this tag is not registered with the system
|
84
|
+
# pass it to the current block for special handling or error reporting
|
85
|
+
unknown_tag(end_tag_name, end_tag_params, tokens)
|
86
|
+
end
|
87
|
+
ensure
|
88
|
+
parse_context.depth -= 1
|
62
89
|
end
|
63
90
|
|
64
91
|
true
|
data/lib/liquid/block_body.rb
CHANGED
@@ -1,52 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
1
5
|
module Liquid
|
2
6
|
class BlockBody
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
+
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
|
8
|
+
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
9
|
+
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
10
|
+
WhitespaceOrNothing = /\A\s*\z/
|
11
|
+
TAGSTART = "{%"
|
12
|
+
VARSTART = "{{"
|
7
13
|
|
8
14
|
attr_reader :nodelist
|
9
15
|
|
10
16
|
def initialize
|
11
17
|
@nodelist = []
|
12
|
-
@blank
|
18
|
+
@blank = true
|
13
19
|
end
|
14
20
|
|
15
|
-
def parse(tokenizer, parse_context)
|
21
|
+
def parse(tokenizer, parse_context, &block)
|
22
|
+
raise FrozenError, "can't modify frozen Liquid::BlockBody" if frozen?
|
23
|
+
|
16
24
|
parse_context.line_number = tokenizer.line_number
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
when token.start_with?(VARSTART)
|
39
|
-
whitespace_handler(token, parse_context)
|
40
|
-
@nodelist << create_variable(token, parse_context)
|
41
|
-
@blank = false
|
42
|
-
else
|
43
|
-
if parse_context.trim_whitespace
|
44
|
-
token.lstrip!
|
45
|
-
end
|
46
|
-
parse_context.trim_whitespace = false
|
47
|
-
@nodelist << token
|
48
|
-
@blank &&= !!(token =~ /\A\s*\z/)
|
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 =~ 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
|
49
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 = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
|
103
|
+
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
|
104
|
+
if end_tag_name
|
105
|
+
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private def parse_for_document(tokenizer, parse_context)
|
111
|
+
while (token = tokenizer.shift)
|
112
|
+
next if token.empty?
|
113
|
+
case
|
114
|
+
when token.start_with?(TAGSTART)
|
115
|
+
whitespace_handler(token, parse_context)
|
116
|
+
unless token =~ FullToken
|
117
|
+
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
118
|
+
end
|
119
|
+
tag_name = Regexp.last_match(2)
|
120
|
+
markup = Regexp.last_match(4)
|
121
|
+
|
122
|
+
if parse_context.line_number
|
123
|
+
# newlines inside the tag should increase the line number,
|
124
|
+
# particularly important for multiline {% liquid %} tags
|
125
|
+
parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
|
126
|
+
end
|
127
|
+
|
128
|
+
if tag_name == 'liquid'
|
129
|
+
parse_liquid_tag(markup, parse_context)
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
unless (tag = registered_tags[tag_name])
|
134
|
+
# end parsing if we reach an unknown tag and let the caller decide
|
135
|
+
# determine how to proceed
|
136
|
+
return yield tag_name, markup
|
137
|
+
end
|
138
|
+
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
139
|
+
@blank &&= new_tag.blank?
|
140
|
+
@nodelist << new_tag
|
141
|
+
when token.start_with?(VARSTART)
|
142
|
+
whitespace_handler(token, parse_context)
|
143
|
+
@nodelist << create_variable(token, parse_context)
|
144
|
+
@blank = false
|
145
|
+
else
|
146
|
+
if parse_context.trim_whitespace
|
147
|
+
token.lstrip!
|
148
|
+
end
|
149
|
+
parse_context.trim_whitespace = false
|
150
|
+
@nodelist << token
|
151
|
+
@blank &&= !!(token =~ WhitespaceOrNothing)
|
50
152
|
end
|
51
153
|
parse_context.line_number = tokenizer.line_number
|
52
154
|
end
|
@@ -57,8 +159,12 @@ module Liquid
|
|
57
159
|
def whitespace_handler(token, parse_context)
|
58
160
|
if token[2] == WhitespaceControl
|
59
161
|
previous_token = @nodelist.last
|
60
|
-
if previous_token.is_a?
|
162
|
+
if previous_token.is_a?(String)
|
163
|
+
first_byte = previous_token.getbyte(0)
|
61
164
|
previous_token.rstrip!
|
165
|
+
if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte
|
166
|
+
previous_token << first_byte
|
167
|
+
end
|
62
168
|
end
|
63
169
|
end
|
64
170
|
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
@@ -68,52 +174,58 @@ module Liquid
|
|
68
174
|
@blank
|
69
175
|
end
|
70
176
|
|
177
|
+
# Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)
|
178
|
+
# with a blank body.
|
179
|
+
#
|
180
|
+
# For example, in a conditional assignment like the following
|
181
|
+
#
|
182
|
+
# ```
|
183
|
+
# {% if size > max_size %}
|
184
|
+
# {% assign size = max_size %}
|
185
|
+
# {% endif %}
|
186
|
+
# ```
|
187
|
+
#
|
188
|
+
# we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method
|
189
|
+
# will remove them to reduce the render output size.
|
190
|
+
#
|
191
|
+
# Note that it is now preferred to use the `liquid` tag for this use case.
|
192
|
+
def remove_blank_strings
|
193
|
+
raise "remove_blank_strings only support being called on a blank block body" unless @blank
|
194
|
+
@nodelist.reject! { |node| node.instance_of?(String) }
|
195
|
+
end
|
196
|
+
|
71
197
|
def render(context)
|
72
|
-
|
73
|
-
|
198
|
+
render_to_output_buffer(context, +'')
|
199
|
+
end
|
200
|
+
|
201
|
+
def render_to_output_buffer(context, output)
|
202
|
+
freeze unless frozen?
|
74
203
|
|
75
|
-
@nodelist.
|
76
|
-
# Break out if we have any unhanded interrupts.
|
77
|
-
break if context.interrupt?
|
204
|
+
context.resource_limits.increment_render_score(@nodelist.length)
|
78
205
|
|
79
|
-
|
206
|
+
idx = 0
|
207
|
+
while (node = @nodelist[idx])
|
208
|
+
if node.instance_of?(String)
|
209
|
+
output << node
|
210
|
+
else
|
211
|
+
render_node(context, output, node)
|
80
212
|
# If we get an Interrupt that means the block must stop processing. An
|
81
213
|
# Interrupt is any command that stops block execution such as {% break %}
|
82
|
-
# or {% continue %}
|
83
|
-
if
|
84
|
-
context.push_interrupt(token.interrupt)
|
85
|
-
break
|
86
|
-
end
|
87
|
-
|
88
|
-
node_output = render_node(token, context)
|
89
|
-
|
90
|
-
unless token.is_a?(Block) && token.blank?
|
91
|
-
output << node_output
|
92
|
-
end
|
93
|
-
rescue MemoryError => e
|
94
|
-
raise e
|
95
|
-
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
96
|
-
context.handle_error(e, token.line_number)
|
97
|
-
output << nil
|
98
|
-
rescue ::StandardError => e
|
99
|
-
output << context.handle_error(e, token.line_number)
|
214
|
+
# or {% continue %}. These tags may also occur through Block or Include tags.
|
215
|
+
break if context.interrupt? # might have happened in a for-block
|
100
216
|
end
|
217
|
+
idx += 1
|
218
|
+
|
219
|
+
context.resource_limits.increment_write_score(output)
|
101
220
|
end
|
102
221
|
|
103
|
-
output
|
222
|
+
output
|
104
223
|
end
|
105
224
|
|
106
225
|
private
|
107
226
|
|
108
|
-
def render_node(
|
109
|
-
|
110
|
-
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
111
|
-
|
112
|
-
context.resource_limits.render_length += node_output.length
|
113
|
-
if context.resource_limits.reached?
|
114
|
-
raise MemoryError.new("Memory limits exceeded".freeze)
|
115
|
-
end
|
116
|
-
node_output
|
227
|
+
def render_node(context, output, node)
|
228
|
+
BlockBody.render_node(context, output, node)
|
117
229
|
end
|
118
230
|
|
119
231
|
def create_variable(token, parse_context)
|
@@ -121,15 +233,17 @@ module Liquid
|
|
121
233
|
markup = content.first
|
122
234
|
return Variable.new(markup, parse_context)
|
123
235
|
end
|
124
|
-
raise_missing_variable_terminator(token, parse_context)
|
236
|
+
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
125
237
|
end
|
126
238
|
|
239
|
+
# @deprecated Use {.raise_missing_tag_terminator} instead
|
127
240
|
def raise_missing_tag_terminator(token, parse_context)
|
128
|
-
|
241
|
+
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
129
242
|
end
|
130
243
|
|
244
|
+
# @deprecated Use {.raise_missing_variable_terminator} instead
|
131
245
|
def raise_missing_variable_terminator(token, parse_context)
|
132
|
-
|
246
|
+
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
133
247
|
end
|
134
248
|
|
135
249
|
def registered_tags
|