liquid 5.4.0 → 5.6.4
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 +11 -0
- data/README.md +48 -6
- data/lib/liquid/block.rb +8 -4
- data/lib/liquid/block_body.rb +28 -10
- data/lib/liquid/condition.rb +9 -4
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +24 -14
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/drop.rb +4 -0
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/errors.rb +16 -15
- data/lib/liquid/expression.rb +101 -22
- data/lib/liquid/forloop_drop.rb +2 -5
- data/lib/liquid/lexer.rb +155 -44
- data/lib/liquid/locales/en.yml +1 -0
- data/lib/liquid/parse_context.rb +29 -6
- data/lib/liquid/parse_tree_visitor.rb +1 -1
- data/lib/liquid/parser.rb +3 -3
- data/lib/liquid/partial_cache.rb +12 -3
- data/lib/liquid/range_lookup.rb +14 -4
- data/lib/liquid/standardfilters.rb +82 -21
- data/lib/liquid/tablerowloop_drop.rb +1 -1
- data/lib/liquid/tag/disabler.rb +0 -8
- data/lib/liquid/tag.rb +13 -3
- data/lib/liquid/tags/assign.rb +1 -3
- data/lib/liquid/tags/break.rb +1 -3
- data/lib/liquid/tags/capture.rb +0 -2
- data/lib/liquid/tags/case.rb +1 -3
- data/lib/liquid/tags/comment.rb +60 -3
- data/lib/liquid/tags/continue.rb +1 -3
- data/lib/liquid/tags/cycle.rb +14 -4
- data/lib/liquid/tags/decrement.rb +8 -7
- data/lib/liquid/tags/echo.rb +2 -4
- data/lib/liquid/tags/for.rb +6 -8
- data/lib/liquid/tags/if.rb +3 -5
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +8 -8
- data/lib/liquid/tags/increment.rb +8 -7
- data/lib/liquid/tags/inline_comment.rb +0 -15
- data/lib/liquid/tags/raw.rb +2 -4
- data/lib/liquid/tags/render.rb +14 -12
- data/lib/liquid/tags/table_row.rb +18 -6
- data/lib/liquid/tags/unless.rb +3 -5
- data/lib/liquid/tags.rb +47 -0
- data/lib/liquid/template.rb +60 -57
- data/lib/liquid/tokenizer.rb +127 -11
- data/lib/liquid/variable.rb +14 -8
- data/lib/liquid/variable_lookup.rb +13 -5
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +15 -16
- metadata +37 -10
- data/lib/liquid/strainer_factory.rb +0 -41
@@ -12,26 +12,27 @@ module Liquid
|
|
12
12
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
13
13
|
# [snippets](/themes/architecture#snippets) included in the file.
|
14
14
|
#
|
15
|
-
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags
|
16
|
-
# and [`capture`](/api/liquid/tags
|
15
|
+
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
|
16
|
+
# and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share
|
17
17
|
# variables.
|
18
18
|
# @liquid_syntax
|
19
19
|
# {% increment variable_name %}
|
20
20
|
# @liquid_syntax_keyword variable_name The name of the variable being incremented.
|
21
21
|
class Increment < Tag
|
22
|
+
attr_reader :variable_name
|
23
|
+
|
22
24
|
def initialize(tag_name, markup, options)
|
23
25
|
super
|
24
|
-
@
|
26
|
+
@variable_name = markup.strip
|
25
27
|
end
|
26
28
|
|
27
29
|
def render_to_output_buffer(context, output)
|
28
|
-
|
29
|
-
|
30
|
+
counter_environment = context.environments.first
|
31
|
+
value = counter_environment[@variable_name] || 0
|
32
|
+
counter_environment[@variable_name] = value + 1
|
30
33
|
|
31
34
|
output << value.to_s
|
32
35
|
output
|
33
36
|
end
|
34
37
|
end
|
35
|
-
|
36
|
-
Template.register_tag('increment', Increment)
|
37
38
|
end
|
@@ -1,19 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Liquid
|
4
|
-
# @liquid_public_docs
|
5
|
-
# @liquid_type tag
|
6
|
-
# @liquid_category syntax
|
7
|
-
# @liquid_name inline_comment
|
8
|
-
# @liquid_summary
|
9
|
-
# Prevents an expression from being rendered or output.
|
10
|
-
# @liquid_description
|
11
|
-
# Any text inside an `inline_comment` tag won't be rendered or output.
|
12
|
-
#
|
13
|
-
# You can create multi-line inline comments. However, each line must begin with a `#`.
|
14
|
-
# @liquid_syntax
|
15
|
-
# {% # content %}
|
16
|
-
# @liquid_syntax_keyword content The content of the comment.
|
17
4
|
class InlineComment < Tag
|
18
5
|
def initialize(tag_name, markup, options)
|
19
6
|
super
|
@@ -38,6 +25,4 @@ module Liquid
|
|
38
25
|
true
|
39
26
|
end
|
40
27
|
end
|
41
|
-
|
42
|
-
Template.register_tag('#', InlineComment)
|
43
28
|
end
|
data/lib/liquid/tags/raw.rb
CHANGED
@@ -14,7 +14,6 @@ module Liquid
|
|
14
14
|
# @liquid_syntax_keyword expression The expression to be output without being rendered.
|
15
15
|
class Raw < Block
|
16
16
|
Syntax = /\A\s*\z/
|
17
|
-
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
18
17
|
|
19
18
|
def initialize(tag_name, markup, parse_context)
|
20
19
|
super
|
@@ -25,7 +24,8 @@ module Liquid
|
|
25
24
|
def parse(tokens)
|
26
25
|
@body = +''
|
27
26
|
while (token = tokens.shift)
|
28
|
-
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
27
|
+
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
28
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
29
29
|
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
30
30
|
return
|
31
31
|
end
|
@@ -56,6 +56,4 @@ module Liquid
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
|
-
|
60
|
-
Template.register_tag('raw', Raw)
|
61
59
|
end
|
data/lib/liquid/tags/render.rb
CHANGED
@@ -8,19 +8,19 @@ module Liquid
|
|
8
8
|
# @liquid_summary
|
9
9
|
# Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
|
10
10
|
# @liquid_description
|
11
|
-
# Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags
|
12
|
-
# of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags#render-passing-variables-to-
|
11
|
+
# Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside
|
12
|
+
# of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
|
13
13
|
# to pass outside variables to snippets.
|
14
14
|
#
|
15
15
|
# While you can't directly access created variables, you can access global objects, as well as any objects that are
|
16
16
|
# directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
|
17
|
-
# can access the [`product` object](/api/liquid/objects
|
18
|
-
# can access the [`section` object](/api/liquid/objects
|
17
|
+
# can access the [`product` object](/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
|
18
|
+
# can access the [`section` object](/docs/api/liquid/objects/section).
|
19
19
|
#
|
20
20
|
# Outside a snippet or app block, you can't access variables created inside the snippet or app block.
|
21
21
|
#
|
22
22
|
# > Note:
|
23
|
-
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags
|
23
|
+
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/docs/api/liquid/tags/include)
|
24
24
|
# > inside the snippet.
|
25
25
|
# @liquid_syntax
|
26
26
|
# {% render 'filename' %}
|
@@ -31,7 +31,7 @@ module Liquid
|
|
31
31
|
|
32
32
|
disable_tags "include"
|
33
33
|
|
34
|
-
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
34
|
+
attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name
|
35
35
|
|
36
36
|
def initialize(tag_name, markup, options)
|
37
37
|
super
|
@@ -45,7 +45,7 @@ module Liquid
|
|
45
45
|
@alias_name = Regexp.last_match(6)
|
46
46
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
47
47
|
@template_name_expr = parse_expression(template_name)
|
48
|
-
@
|
48
|
+
@is_for_loop = (with_or_for == FOR)
|
49
49
|
|
50
50
|
@attributes = {}
|
51
51
|
markup.scan(TagAttributes) do |key, value|
|
@@ -53,6 +53,10 @@ module Liquid
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
def for_loop?
|
57
|
+
@is_for_loop
|
58
|
+
end
|
59
|
+
|
56
60
|
def render_to_output_buffer(context, output)
|
57
61
|
render_tag(context, output)
|
58
62
|
end
|
@@ -65,14 +69,14 @@ module Liquid
|
|
65
69
|
partial = PartialCache.load(
|
66
70
|
template_name,
|
67
71
|
context: context,
|
68
|
-
parse_context: parse_context
|
72
|
+
parse_context: parse_context,
|
69
73
|
)
|
70
74
|
|
71
75
|
context_variable_name = @alias_name || template_name.split('/').last
|
72
76
|
|
73
77
|
render_partial_func = ->(var, forloop) {
|
74
78
|
inner_context = context.new_isolated_subcontext
|
75
|
-
inner_context.template_name =
|
79
|
+
inner_context.template_name = partial.name
|
76
80
|
inner_context.partial = true
|
77
81
|
inner_context['forloop'] = forloop if forloop
|
78
82
|
|
@@ -85,7 +89,7 @@ module Liquid
|
|
85
89
|
}
|
86
90
|
|
87
91
|
variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
|
88
|
-
if @
|
92
|
+
if @is_for_loop && variable.respond_to?(:each) && variable.respond_to?(:count)
|
89
93
|
forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
|
90
94
|
variable.each { |var| render_partial_func.call(var, forloop) }
|
91
95
|
else
|
@@ -104,6 +108,4 @@ module Liquid
|
|
104
108
|
end
|
105
109
|
end
|
106
110
|
end
|
107
|
-
|
108
|
-
Template.register_tag('render', Render)
|
109
111
|
end
|
@@ -11,7 +11,7 @@ module Liquid
|
|
11
11
|
# The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
|
12
12
|
#
|
13
13
|
# > Tip:
|
14
|
-
# > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects
|
14
|
+
# > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.
|
15
15
|
# @liquid_syntax
|
16
16
|
# {% tablerow variable in array %}
|
17
17
|
# expression
|
@@ -45,13 +45,13 @@ module Liquid
|
|
45
45
|
def render_to_output_buffer(context, output)
|
46
46
|
(collection = context.evaluate(@collection_name)) || (return '')
|
47
47
|
|
48
|
-
from = @attributes.key?('offset') ? context.evaluate(@attributes['offset'])
|
49
|
-
to
|
48
|
+
from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
|
49
|
+
to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
|
50
50
|
|
51
51
|
collection = Utils.slice_collection(collection, from, to)
|
52
52
|
length = collection.length
|
53
53
|
|
54
|
-
cols = context.evaluate(@attributes['cols'])
|
54
|
+
cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
|
55
55
|
|
56
56
|
output << "<tr class=\"row1\">\n"
|
57
57
|
context.stack do
|
@@ -65,6 +65,12 @@ module Liquid
|
|
65
65
|
super
|
66
66
|
output << '</td>'
|
67
67
|
|
68
|
+
# Handle any interrupts if they exist.
|
69
|
+
if context.interrupt?
|
70
|
+
interrupt = context.pop_interrupt
|
71
|
+
break if interrupt.is_a?(BreakInterrupt)
|
72
|
+
end
|
73
|
+
|
68
74
|
if tablerowloop.col_last && !tablerowloop.last
|
69
75
|
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
70
76
|
end
|
@@ -82,7 +88,13 @@ module Liquid
|
|
82
88
|
super + @node.attributes.values + [@node.collection_name]
|
83
89
|
end
|
84
90
|
end
|
85
|
-
end
|
86
91
|
|
87
|
-
|
92
|
+
private
|
93
|
+
|
94
|
+
def to_integer(value)
|
95
|
+
value.to_i
|
96
|
+
rescue NoMethodError
|
97
|
+
raise Liquid::ArgumentError, "invalid integer"
|
98
|
+
end
|
99
|
+
end
|
88
100
|
end
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -11,7 +11,7 @@ module Liquid
|
|
11
11
|
# Renders an expression unless a specific condition is `true`.
|
12
12
|
# @liquid_description
|
13
13
|
# > Tip:
|
14
|
-
# > Similar to the [`if` tag](/api/liquid/tags
|
14
|
+
# > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
|
15
15
|
# @liquid_syntax
|
16
16
|
# {% unless condition %}
|
17
17
|
# expression
|
@@ -23,7 +23,7 @@ module Liquid
|
|
23
23
|
# First condition is interpreted backwards ( if not )
|
24
24
|
first_block = @blocks.first
|
25
25
|
result = Liquid::Utils.to_liquid_value(
|
26
|
-
first_block.evaluate(context)
|
26
|
+
first_block.evaluate(context),
|
27
27
|
)
|
28
28
|
|
29
29
|
unless result
|
@@ -33,7 +33,7 @@ module Liquid
|
|
33
33
|
# After the first condition unless works just like if
|
34
34
|
@blocks[1..-1].each do |block|
|
35
35
|
result = Liquid::Utils.to_liquid_value(
|
36
|
-
block.evaluate(context)
|
36
|
+
block.evaluate(context),
|
37
37
|
)
|
38
38
|
|
39
39
|
if result
|
@@ -44,6 +44,4 @@ module Liquid
|
|
44
44
|
output
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
48
|
-
Template.register_tag('unless', Unless)
|
49
47
|
end
|
data/lib/liquid/tags.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "tags/table_row"
|
4
|
+
require_relative "tags/echo"
|
5
|
+
require_relative "tags/if"
|
6
|
+
require_relative "tags/break"
|
7
|
+
require_relative "tags/inline_comment"
|
8
|
+
require_relative "tags/for"
|
9
|
+
require_relative "tags/assign"
|
10
|
+
require_relative "tags/ifchanged"
|
11
|
+
require_relative "tags/case"
|
12
|
+
require_relative "tags/include"
|
13
|
+
require_relative "tags/continue"
|
14
|
+
require_relative "tags/capture"
|
15
|
+
require_relative "tags/decrement"
|
16
|
+
require_relative "tags/unless"
|
17
|
+
require_relative "tags/increment"
|
18
|
+
require_relative "tags/comment"
|
19
|
+
require_relative "tags/raw"
|
20
|
+
require_relative "tags/render"
|
21
|
+
require_relative "tags/cycle"
|
22
|
+
|
23
|
+
module Liquid
|
24
|
+
module Tags
|
25
|
+
STANDARD_TAGS = {
|
26
|
+
'cycle' => Cycle,
|
27
|
+
'render' => Render,
|
28
|
+
'raw' => Raw,
|
29
|
+
'comment' => Comment,
|
30
|
+
'increment' => Increment,
|
31
|
+
'unless' => Unless,
|
32
|
+
'decrement' => Decrement,
|
33
|
+
'capture' => Capture,
|
34
|
+
'continue' => Continue,
|
35
|
+
'include' => Include,
|
36
|
+
'case' => Case,
|
37
|
+
'ifchanged' => Ifchanged,
|
38
|
+
'assign' => Assign,
|
39
|
+
'for' => For,
|
40
|
+
'#' => InlineComment,
|
41
|
+
'break' => Break,
|
42
|
+
'if' => If,
|
43
|
+
'echo' => Echo,
|
44
|
+
'tablerow' => TableRow,
|
45
|
+
}.freeze
|
46
|
+
end
|
47
|
+
end
|
data/lib/liquid/template.rb
CHANGED
@@ -15,98 +15,93 @@ module Liquid
|
|
15
15
|
# template.render('user_name' => 'bob')
|
16
16
|
#
|
17
17
|
class Template
|
18
|
-
attr_accessor :root
|
18
|
+
attr_accessor :root, :name
|
19
19
|
attr_reader :resource_limits, :warnings
|
20
20
|
|
21
|
-
|
22
|
-
include Enumerable
|
21
|
+
attr_reader :profiler
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
class << self
|
24
|
+
# Sets how strict the parser should be.
|
25
|
+
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
26
|
+
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
27
|
+
# :strict will enforce correct syntax.
|
28
|
+
def error_mode=(mode)
|
29
|
+
Deprecations.warn("Template.error_mode=", "Environment#error_mode=")
|
30
|
+
Environment.default.error_mode = mode
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
30
|
-
|
31
|
-
return @cache[tag_name] if Liquid.cache_classes
|
32
|
-
|
33
|
-
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
33
|
+
def error_mode
|
34
|
+
Environment.default.error_mode
|
34
35
|
end
|
35
36
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
37
|
+
def default_exception_renderer=(renderer)
|
38
|
+
Deprecations.warn("Template.default_exception_renderer=", "Environment#exception_renderer=")
|
39
|
+
Environment.default.exception_renderer = renderer
|
39
40
|
end
|
40
41
|
|
41
|
-
def
|
42
|
-
|
43
|
-
@cache.delete(tag_name)
|
42
|
+
def default_exception_renderer
|
43
|
+
Environment.default.exception_renderer
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
47
|
-
|
46
|
+
def file_system=(file_system)
|
47
|
+
Deprecations.warn("Template.file_system=", "Environment#file_system=")
|
48
|
+
Environment.default.file_system = file_system
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
def lookup_class(name)
|
53
|
-
Object.const_get(name)
|
51
|
+
def file_system
|
52
|
+
Environment.default.file_system
|
54
53
|
end
|
55
|
-
end
|
56
|
-
|
57
|
-
attr_reader :profiler
|
58
54
|
|
59
|
-
|
60
|
-
|
61
|
-
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
62
|
-
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
63
|
-
# :strict will enforce correct syntax.
|
64
|
-
attr_accessor :error_mode
|
65
|
-
Template.error_mode = :lax
|
66
|
-
|
67
|
-
attr_accessor :default_exception_renderer
|
68
|
-
Template.default_exception_renderer = lambda do |exception|
|
69
|
-
exception
|
55
|
+
def tags
|
56
|
+
Environment.default.tags
|
70
57
|
end
|
71
58
|
|
72
|
-
attr_accessor :file_system
|
73
|
-
Template.file_system = BlankFileSystem.new
|
74
|
-
|
75
|
-
attr_accessor :tags
|
76
|
-
Template.tags = TagRegistry.new
|
77
|
-
private :tags=
|
78
|
-
|
79
59
|
def register_tag(name, klass)
|
80
|
-
|
60
|
+
Deprecations.warn("Template.register_tag", "Environment#register_tag")
|
61
|
+
Environment.default.register_tag(name, klass)
|
81
62
|
end
|
82
63
|
|
83
64
|
# Pass a module with filter methods which should be available
|
84
65
|
# to all liquid views. Good for registering the standard library
|
85
66
|
def register_filter(mod)
|
86
|
-
|
67
|
+
Deprecations.warn("Template.register_filter", "Environment#register_filter")
|
68
|
+
Environment.default.register_filter(mod)
|
87
69
|
end
|
88
70
|
|
89
|
-
|
90
|
-
|
91
|
-
|
71
|
+
private def default_resource_limits=(limits)
|
72
|
+
Deprecations.warn("Template.default_resource_limits=", "Environment#default_resource_limits=")
|
73
|
+
Environment.default.default_resource_limits = limits
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_resource_limits
|
77
|
+
Environment.default.default_resource_limits
|
78
|
+
end
|
92
79
|
|
93
80
|
# creates a new <tt>Template</tt> object from liquid source code
|
94
81
|
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
95
82
|
# See Liquid::Profiler for more information
|
96
83
|
def parse(source, options = {})
|
97
|
-
|
84
|
+
environment = options[:environment] || Environment.default
|
85
|
+
new(environment: environment).parse(source, options)
|
98
86
|
end
|
99
87
|
end
|
100
88
|
|
101
|
-
def initialize
|
89
|
+
def initialize(environment: Environment.default)
|
90
|
+
@environment = environment
|
102
91
|
@rethrow_errors = false
|
103
|
-
@resource_limits = ResourceLimits.new(
|
92
|
+
@resource_limits = ResourceLimits.new(environment.default_resource_limits)
|
104
93
|
end
|
105
94
|
|
106
95
|
# Parse source code.
|
107
96
|
# Returns self for easy chaining
|
108
97
|
def parse(source, options = {})
|
109
98
|
parse_context = configure_options(options)
|
99
|
+
source = source.to_s.to_str
|
100
|
+
|
101
|
+
unless source.valid_encoding?
|
102
|
+
raise TemplateEncodingError, parse_context.locale.t("errors.syntax.invalid_template_encoding")
|
103
|
+
end
|
104
|
+
|
110
105
|
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
|
111
106
|
@root = Document.parse(tokenizer, parse_context)
|
112
107
|
self
|
@@ -156,11 +151,11 @@ module Liquid
|
|
156
151
|
c
|
157
152
|
when Liquid::Drop
|
158
153
|
drop = args.shift
|
159
|
-
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
154
|
+
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
160
155
|
when Hash
|
161
|
-
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
156
|
+
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
162
157
|
when nil
|
163
|
-
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
158
|
+
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
164
159
|
else
|
165
160
|
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
166
161
|
end
|
@@ -189,6 +184,8 @@ module Liquid
|
|
189
184
|
@profiler = context.profiler = Liquid::Profiler.new
|
190
185
|
end
|
191
186
|
|
187
|
+
context.template_name ||= name
|
188
|
+
|
192
189
|
begin
|
193
190
|
# render the nodelist.
|
194
191
|
@root.render_to_output_buffer(context, output || +'')
|
@@ -218,8 +215,14 @@ module Liquid
|
|
218
215
|
@options = options
|
219
216
|
@profiling = profiling
|
220
217
|
@line_numbers = options[:line_numbers] || @profiling
|
221
|
-
parse_context = options.is_a?(ParseContext)
|
222
|
-
|
218
|
+
parse_context = if options.is_a?(ParseContext)
|
219
|
+
options
|
220
|
+
else
|
221
|
+
opts = options.key?(:environment) ? options : options.merge(environment: @environment)
|
222
|
+
ParseContext.new(opts)
|
223
|
+
end
|
224
|
+
|
225
|
+
@warnings = parse_context.warnings
|
223
226
|
parse_context
|
224
227
|
end
|
225
228
|
|
data/lib/liquid/tokenizer.rb
CHANGED
@@ -1,18 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "strscan"
|
4
|
+
|
3
5
|
module Liquid
|
4
6
|
class Tokenizer
|
5
7
|
attr_reader :line_number, :for_liquid_tag
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
TAG_END = /%\}/
|
10
|
+
TAG_OR_VARIABLE_START = /\{[\{\%]/
|
11
|
+
NEWLINE = /\n/
|
12
|
+
|
13
|
+
OPEN_CURLEY = "{".ord
|
14
|
+
CLOSE_CURLEY = "}".ord
|
15
|
+
PERCENTAGE = "%".ord
|
16
|
+
|
17
|
+
def initialize(
|
18
|
+
source:,
|
19
|
+
string_scanner:,
|
20
|
+
line_numbers: false,
|
21
|
+
line_number: nil,
|
22
|
+
for_liquid_tag: false
|
23
|
+
)
|
24
|
+
@line_number = line_number || (line_numbers ? 1 : nil)
|
10
25
|
@for_liquid_tag = for_liquid_tag
|
11
|
-
@
|
26
|
+
@source = source.to_s.to_str
|
27
|
+
@offset = 0
|
28
|
+
@tokens = []
|
29
|
+
|
30
|
+
if @source
|
31
|
+
@ss = string_scanner
|
32
|
+
@ss.string = @source
|
33
|
+
tokenize
|
34
|
+
end
|
12
35
|
end
|
13
36
|
|
14
37
|
def shift
|
15
|
-
|
38
|
+
token = @tokens[@offset]
|
39
|
+
|
40
|
+
return unless token
|
41
|
+
|
42
|
+
@offset += 1
|
16
43
|
|
17
44
|
if @line_number
|
18
45
|
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
@@ -24,16 +51,105 @@ module Liquid
|
|
24
51
|
private
|
25
52
|
|
26
53
|
def tokenize
|
27
|
-
|
54
|
+
if @for_liquid_tag
|
55
|
+
@tokens = @source.split("\n")
|
56
|
+
else
|
57
|
+
@tokens << shift_normal until @ss.eos?
|
58
|
+
end
|
59
|
+
|
60
|
+
@source = nil
|
61
|
+
@ss = nil
|
62
|
+
end
|
28
63
|
|
29
|
-
|
64
|
+
def shift_normal
|
65
|
+
token = next_token
|
30
66
|
|
31
|
-
|
67
|
+
return unless token
|
32
68
|
|
33
|
-
|
34
|
-
|
69
|
+
token
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_token
|
73
|
+
# possible states: :text, :tag, :variable
|
74
|
+
byte_a = @ss.peek_byte
|
75
|
+
|
76
|
+
if byte_a == OPEN_CURLEY
|
77
|
+
@ss.scan_byte
|
78
|
+
|
79
|
+
byte_b = @ss.peek_byte
|
80
|
+
|
81
|
+
if byte_b == PERCENTAGE
|
82
|
+
@ss.scan_byte
|
83
|
+
return next_tag_token
|
84
|
+
elsif byte_b == OPEN_CURLEY
|
85
|
+
@ss.scan_byte
|
86
|
+
return next_variable_token
|
87
|
+
end
|
88
|
+
|
89
|
+
@ss.pos -= 1
|
90
|
+
end
|
91
|
+
|
92
|
+
next_text_token
|
93
|
+
end
|
94
|
+
|
95
|
+
def next_text_token
|
96
|
+
start = @ss.pos
|
97
|
+
|
98
|
+
unless @ss.skip_until(TAG_OR_VARIABLE_START)
|
99
|
+
token = @ss.rest
|
100
|
+
@ss.terminate
|
101
|
+
return token
|
102
|
+
end
|
103
|
+
|
104
|
+
pos = @ss.pos -= 2
|
105
|
+
@source.byteslice(start, pos - start)
|
106
|
+
end
|
107
|
+
|
108
|
+
def next_variable_token
|
109
|
+
start = @ss.pos - 2
|
110
|
+
|
111
|
+
byte_a = byte_b = @ss.scan_byte
|
112
|
+
|
113
|
+
while byte_b
|
114
|
+
byte_a = @ss.scan_byte while byte_a && (byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY)
|
115
|
+
|
116
|
+
break unless byte_a
|
117
|
+
|
118
|
+
if @ss.eos?
|
119
|
+
return byte_a == CLOSE_CURLEY ? @source.byteslice(start, @ss.pos - start) : "{{"
|
120
|
+
end
|
121
|
+
|
122
|
+
byte_b = @ss.scan_byte
|
123
|
+
|
124
|
+
if byte_a == CLOSE_CURLEY
|
125
|
+
if byte_b == CLOSE_CURLEY
|
126
|
+
return @source.byteslice(start, @ss.pos - start)
|
127
|
+
elsif byte_b != CLOSE_CURLEY
|
128
|
+
@ss.pos -= 1
|
129
|
+
return @source.byteslice(start, @ss.pos - start)
|
130
|
+
end
|
131
|
+
elsif byte_a == OPEN_CURLEY && byte_b == PERCENTAGE
|
132
|
+
return next_tag_token_with_start(start)
|
133
|
+
end
|
134
|
+
|
135
|
+
byte_a = byte_b
|
136
|
+
end
|
137
|
+
|
138
|
+
"{{"
|
139
|
+
end
|
140
|
+
|
141
|
+
def next_tag_token
|
142
|
+
start = @ss.pos - 2
|
143
|
+
if (len = @ss.skip_until(TAG_END))
|
144
|
+
@source.byteslice(start, len + 2)
|
145
|
+
else
|
146
|
+
"{%"
|
147
|
+
end
|
148
|
+
end
|
35
149
|
|
36
|
-
|
150
|
+
def next_tag_token_with_start(start)
|
151
|
+
@ss.skip_until(TAG_END)
|
152
|
+
@source.byteslice(start, @ss.pos - start)
|
37
153
|
end
|
38
154
|
end
|
39
155
|
end
|