liquid 4.0.0 → 5.10.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 +235 -2
- data/README.md +58 -8
- data/lib/liquid/block.rb +51 -20
- data/lib/liquid/block_body.rb +216 -82
- data/lib/liquid/condition.rb +83 -32
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +130 -59
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +8 -2
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/errors.rb +23 -20
- data/lib/liquid/expression.rb +114 -31
- data/lib/liquid/extensions.rb +8 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +51 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +165 -39
- data/lib/liquid/locales/en.yml +16 -6
- data/lib/liquid/parse_context.rb +62 -7
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +31 -19
- data/lib/liquid/parser_switching.rb +42 -3
- data/lib/liquid/partial_cache.rb +33 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +26 -6
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/snippet_drop.rb +22 -0
- data/lib/liquid/standardfilters.rb +813 -137
- 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 +13 -0
- data/lib/liquid/tag.rb +42 -6
- data/lib/liquid/tags/assign.rb +46 -18
- data/lib/liquid/tags/break.rb +15 -4
- data/lib/liquid/tags/capture.rb +26 -18
- data/lib/liquid/tags/case.rb +108 -32
- data/lib/liquid/tags/comment.rb +76 -4
- data/lib/liquid/tags/continue.rb +15 -13
- data/lib/liquid/tags/cycle.rb +117 -34
- data/lib/liquid/tags/decrement.rb +30 -23
- data/lib/liquid/tags/doc.rb +81 -0
- data/lib/liquid/tags/echo.rb +39 -0
- data/lib/liquid/tags/for.rb +109 -96
- data/lib/liquid/tags/if.rb +72 -41
- data/lib/liquid/tags/ifchanged.rb +10 -11
- data/lib/liquid/tags/include.rb +89 -63
- data/lib/liquid/tags/increment.rb +31 -20
- data/lib/liquid/tags/inline_comment.rb +28 -0
- data/lib/liquid/tags/raw.rb +25 -13
- data/lib/liquid/tags/render.rb +151 -0
- data/lib/liquid/tags/snippet.rb +45 -0
- data/lib/liquid/tags/table_row.rb +104 -21
- data/lib/liquid/tags/unless.rb +37 -20
- data/lib/liquid/tags.rb +51 -0
- data/lib/liquid/template.rb +90 -106
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +143 -13
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +114 -5
- data/lib/liquid/variable.rb +119 -45
- data/lib/liquid/variable_lookup.rb +35 -13
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +31 -18
- metadata +56 -107
- data/lib/liquid/strainer.rb +0 -66
- 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/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/parsing_quirks_test.rb +0 -118
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -66
- data/test/integration/standard_filter_test.rb +0 -535
- 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 -238
- 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 -323
- data/test/integration/trim_mode_test.rb +0 -525
- data/test/integration/variable_test.rb +0 -92
- data/test/test_helper.rb +0 -117
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -158
- data/test/unit/context_unit_test.rb +0 -483
- 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 -148
- 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
data/lib/liquid/parse_context.rb
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
4
|
class ParseContext
|
|
3
|
-
attr_accessor :locale, :line_number, :trim_whitespace
|
|
4
|
-
attr_reader :partial, :warnings, :error_mode
|
|
5
|
+
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
|
6
|
+
attr_reader :partial, :warnings, :error_mode, :environment
|
|
5
7
|
|
|
6
|
-
def initialize(options =
|
|
8
|
+
def initialize(options = Const::EMPTY_HASH)
|
|
9
|
+
@environment = options.fetch(:environment, Environment.default)
|
|
7
10
|
@template_options = options ? options.dup : {}
|
|
8
|
-
|
|
11
|
+
|
|
12
|
+
@locale = @template_options[:locale] ||= I18n.new
|
|
9
13
|
@warnings = []
|
|
14
|
+
|
|
15
|
+
# constructing new StringScanner in Lexer, Tokenizer, etc is expensive
|
|
16
|
+
# This StringScanner will be shared by all of them
|
|
17
|
+
@string_scanner = StringScanner.new("")
|
|
18
|
+
|
|
19
|
+
@expression_cache = if options[:expression_cache].nil?
|
|
20
|
+
{}
|
|
21
|
+
elsif options[:expression_cache].respond_to?(:[]) && options[:expression_cache].respond_to?(:[]=)
|
|
22
|
+
options[:expression_cache]
|
|
23
|
+
elsif options[:expression_cache]
|
|
24
|
+
{}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
self.depth = 0
|
|
10
28
|
self.partial = false
|
|
11
29
|
end
|
|
12
30
|
|
|
@@ -14,11 +32,48 @@ module Liquid
|
|
|
14
32
|
@options[option_key]
|
|
15
33
|
end
|
|
16
34
|
|
|
35
|
+
def new_block_body
|
|
36
|
+
Liquid::BlockBody.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def new_parser(input)
|
|
40
|
+
@string_scanner.string = input
|
|
41
|
+
Parser.new(@string_scanner)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)
|
|
45
|
+
Tokenizer.new(
|
|
46
|
+
source: source,
|
|
47
|
+
string_scanner: @string_scanner,
|
|
48
|
+
line_number: start_line_number,
|
|
49
|
+
for_liquid_tag: for_liquid_tag,
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
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
|
+
|
|
69
|
+
Expression.parse(markup, @string_scanner, @expression_cache)
|
|
70
|
+
end
|
|
71
|
+
|
|
17
72
|
def partial=(value)
|
|
18
73
|
@partial = value
|
|
19
74
|
@options = value ? partial_options : @template_options
|
|
20
|
-
|
|
21
|
-
|
|
75
|
+
|
|
76
|
+
@error_mode = @options[:error_mode] || @environment.error_mode
|
|
22
77
|
end
|
|
23
78
|
|
|
24
79
|
def partial_options
|
|
@@ -27,7 +82,7 @@ module Liquid
|
|
|
27
82
|
if dont_pass == true
|
|
28
83
|
{ locale: locale }
|
|
29
84
|
elsif dont_pass.is_a?(Array)
|
|
30
|
-
@template_options.reject { |k,
|
|
85
|
+
@template_options.reject { |k, _v| dont_pass.include?(k) }
|
|
31
86
|
else
|
|
32
87
|
@template_options
|
|
33
88
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
class ParseTreeVisitor
|
|
5
|
+
def self.for(node, callbacks = Hash.new(proc {}))
|
|
6
|
+
if defined?(node.class::ParseTreeVisitor)
|
|
7
|
+
node.class::ParseTreeVisitor
|
|
8
|
+
else
|
|
9
|
+
self
|
|
10
|
+
end.new(node, callbacks)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(node, callbacks)
|
|
14
|
+
@node = node
|
|
15
|
+
@callbacks = callbacks
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_callback_for(*classes, &block)
|
|
19
|
+
callback = block
|
|
20
|
+
callback = ->(node, _) { yield node } if block.arity.abs == 1
|
|
21
|
+
callback = ->(_, _) { yield } if block.arity.zero?
|
|
22
|
+
classes.each { |klass| @callbacks[klass] = callback }
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def visit(context = nil)
|
|
27
|
+
children.map do |node|
|
|
28
|
+
item, new_context = @callbacks[node.class].call(node, context)
|
|
29
|
+
[
|
|
30
|
+
item,
|
|
31
|
+
ParseTreeVisitor.for(node, @callbacks).visit(new_context || context),
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
def children
|
|
39
|
+
@node.respond_to?(:nodelist) ? Array(@node.nodelist) : Const::EMPTY_ARRAY
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/liquid/parser.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
4
|
class Parser
|
|
3
5
|
def initialize(input)
|
|
4
|
-
|
|
5
|
-
@tokens =
|
|
6
|
-
@p
|
|
6
|
+
ss = input.is_a?(StringScanner) ? input : StringScanner.new(input)
|
|
7
|
+
@tokens = Lexer.tokenize(ss)
|
|
8
|
+
@p = 0 # pointer to current location
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
def jump(point)
|
|
@@ -46,11 +48,18 @@ module Liquid
|
|
|
46
48
|
|
|
47
49
|
def expression
|
|
48
50
|
token = @tokens[@p]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
case token[0]
|
|
52
|
+
when :id
|
|
53
|
+
str = consume
|
|
54
|
+
str << variable_lookups
|
|
55
|
+
when :open_square
|
|
56
|
+
str = consume.dup
|
|
57
|
+
str << expression
|
|
58
|
+
str << consume(:close_square)
|
|
59
|
+
str << variable_lookups
|
|
60
|
+
when :string, :number
|
|
52
61
|
consume
|
|
53
|
-
|
|
62
|
+
when :open_round
|
|
54
63
|
consume
|
|
55
64
|
first = expression
|
|
56
65
|
consume(:dotdot)
|
|
@@ -63,26 +72,29 @@ module Liquid
|
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
def argument
|
|
66
|
-
str = ""
|
|
75
|
+
str = +""
|
|
67
76
|
# might be a keyword argument (identifier: expression)
|
|
68
77
|
if look(:id) && look(:colon, 1)
|
|
69
|
-
str << consume << consume << ' '
|
|
78
|
+
str << consume << consume << ' '
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
str << expression
|
|
73
82
|
str
|
|
74
83
|
end
|
|
75
84
|
|
|
76
|
-
def
|
|
77
|
-
str =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
def variable_lookups
|
|
86
|
+
str = +""
|
|
87
|
+
loop do
|
|
88
|
+
if look(:open_square)
|
|
89
|
+
str << consume
|
|
90
|
+
str << expression
|
|
91
|
+
str << consume(:close_square)
|
|
92
|
+
elsif look(:dot)
|
|
93
|
+
str << consume
|
|
94
|
+
str << consume(:id)
|
|
95
|
+
else
|
|
96
|
+
break
|
|
97
|
+
end
|
|
86
98
|
end
|
|
87
99
|
str
|
|
88
100
|
end
|
|
@@ -1,25 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
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.
|
|
13
|
+
def strict_parse_with_error_mode_fallback(markup)
|
|
14
|
+
return rigid_parse_with_error_context(markup) if rigid_mode?
|
|
15
|
+
|
|
16
|
+
strict_parse_with_error_context(markup)
|
|
17
|
+
rescue SyntaxError => e
|
|
18
|
+
case parse_context.error_mode
|
|
19
|
+
when :rigid
|
|
20
|
+
raise
|
|
21
|
+
when :strict
|
|
22
|
+
raise
|
|
23
|
+
when :warn
|
|
24
|
+
parse_context.warnings << e
|
|
25
|
+
end
|
|
26
|
+
lax_parse(markup)
|
|
27
|
+
end
|
|
28
|
+
|
|
3
29
|
def parse_with_selected_parser(markup)
|
|
4
30
|
case parse_context.error_mode
|
|
31
|
+
when :rigid then rigid_parse_with_error_context(markup)
|
|
5
32
|
when :strict then strict_parse_with_error_context(markup)
|
|
6
33
|
when :lax then lax_parse(markup)
|
|
7
34
|
when :warn
|
|
8
35
|
begin
|
|
9
|
-
|
|
36
|
+
rigid_parse_with_error_context(markup)
|
|
10
37
|
rescue SyntaxError => e
|
|
11
38
|
parse_context.warnings << e
|
|
12
|
-
|
|
39
|
+
lax_parse(markup)
|
|
13
40
|
end
|
|
14
41
|
end
|
|
15
42
|
end
|
|
16
43
|
|
|
44
|
+
def rigid_mode?
|
|
45
|
+
parse_context.error_mode == :rigid
|
|
46
|
+
end
|
|
47
|
+
|
|
17
48
|
private
|
|
18
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
|
+
|
|
19
58
|
def strict_parse_with_error_context(markup)
|
|
20
59
|
strict_parse(markup)
|
|
21
60
|
rescue SyntaxError => e
|
|
22
|
-
e.line_number
|
|
61
|
+
e.line_number = line_number
|
|
23
62
|
e.markup_context = markup_context(markup)
|
|
24
63
|
raise e
|
|
25
64
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
class PartialCache
|
|
5
|
+
def self.load(template_name, context:, parse_context:)
|
|
6
|
+
cached_partials = context.registers[:cached_partials]
|
|
7
|
+
cache_key = "#{template_name}:#{parse_context.error_mode}"
|
|
8
|
+
cached = cached_partials[cache_key]
|
|
9
|
+
return cached if cached
|
|
10
|
+
|
|
11
|
+
file_system = context.registers[:file_system]
|
|
12
|
+
source = file_system.read_template_file(template_name)
|
|
13
|
+
|
|
14
|
+
parse_context.partial = true
|
|
15
|
+
|
|
16
|
+
template_factory = context.registers[:template_factory]
|
|
17
|
+
template = template_factory.for(template_name)
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
partial = template.parse(source, parse_context)
|
|
21
|
+
rescue Liquid::Error => e
|
|
22
|
+
e.template_name = template&.name || template_name
|
|
23
|
+
raise e
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
partial.name ||= template_name
|
|
27
|
+
|
|
28
|
+
cached_partials[cache_key] = partial
|
|
29
|
+
ensure
|
|
30
|
+
parse_context.partial = false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,23 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
|
-
|
|
3
|
-
def
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
module BlockBodyProfilingHook
|
|
5
|
+
def render_node(context, output, node)
|
|
6
|
+
if (profiler = context.profiler)
|
|
7
|
+
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
else
|
|
11
|
+
super
|
|
6
12
|
end
|
|
7
13
|
end
|
|
8
|
-
|
|
9
|
-
alias_method :render_node_without_profiling, :render_node
|
|
10
|
-
alias_method :render_node, :render_node_with_profiling
|
|
11
14
|
end
|
|
15
|
+
BlockBody.prepend(BlockBodyProfilingHook)
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
end
|
|
17
|
+
module DocumentProfilingHook
|
|
18
|
+
def render_to_output_buffer(context, output)
|
|
19
|
+
return super unless context.profiler
|
|
20
|
+
context.profiler.profile(context.template_name) { super }
|
|
18
21
|
end
|
|
22
|
+
end
|
|
23
|
+
Document.prepend(DocumentProfilingHook)
|
|
24
|
+
|
|
25
|
+
module ContextProfilingHook
|
|
26
|
+
attr_accessor :profiler
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
def new_isolated_subcontext
|
|
29
|
+
new_context = super
|
|
30
|
+
new_context.profiler = profiler
|
|
31
|
+
new_context
|
|
32
|
+
end
|
|
22
33
|
end
|
|
34
|
+
Context.prepend(ContextProfilingHook)
|
|
23
35
|
end
|
data/lib/liquid/profiler.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'liquid/profiler/hooks'
|
|
2
4
|
|
|
3
5
|
module Liquid
|
|
@@ -23,7 +25,7 @@ module Liquid
|
|
|
23
25
|
# node.code
|
|
24
26
|
#
|
|
25
27
|
# # Which template and line number of this node.
|
|
26
|
-
# #
|
|
28
|
+
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
|
|
27
29
|
# node.partial
|
|
28
30
|
# node.line_number
|
|
29
31
|
#
|
|
@@ -44,115 +46,94 @@ module Liquid
|
|
|
44
46
|
include Enumerable
|
|
45
47
|
|
|
46
48
|
class Timing
|
|
47
|
-
attr_reader :code, :
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@
|
|
49
|
+
attr_reader :code, :template_name, :line_number, :children
|
|
50
|
+
attr_accessor :total_time
|
|
51
|
+
alias_method :render_time, :total_time
|
|
52
|
+
alias_method :partial, :template_name
|
|
53
|
+
|
|
54
|
+
def initialize(code: nil, template_name: nil, line_number: nil)
|
|
55
|
+
@code = code
|
|
56
|
+
@template_name = template_name
|
|
57
|
+
@line_number = line_number
|
|
58
|
+
@children = []
|
|
54
59
|
end
|
|
55
60
|
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def finish
|
|
65
|
-
@end_time = Time.now
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def render_time
|
|
69
|
-
@end_time - @start_time
|
|
61
|
+
def self_time
|
|
62
|
+
@self_time ||= begin
|
|
63
|
+
total_children_time = 0.0
|
|
64
|
+
@children.each do |child|
|
|
65
|
+
total_children_time += child.total_time
|
|
66
|
+
end
|
|
67
|
+
@total_time - total_children_time
|
|
68
|
+
end
|
|
70
69
|
end
|
|
71
70
|
end
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
attr_reader :total_time
|
|
73
|
+
alias_method :total_render_time, :total_time
|
|
74
|
+
|
|
75
|
+
def initialize
|
|
76
|
+
@root_children = []
|
|
77
|
+
@current_children = nil
|
|
78
|
+
@total_time = 0.0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def profile(template_name, &block)
|
|
82
|
+
# nested renders are done from a tag that already has a timing node
|
|
83
|
+
return yield if @current_children
|
|
84
|
+
|
|
85
|
+
root_children = @root_children
|
|
86
|
+
render_idx = root_children.length
|
|
87
|
+
begin
|
|
88
|
+
@current_children = root_children
|
|
89
|
+
profile_node(template_name, &block)
|
|
90
|
+
ensure
|
|
91
|
+
@current_children = nil
|
|
92
|
+
if (timing = root_children[render_idx])
|
|
93
|
+
@total_time += timing.total_time
|
|
94
|
+
end
|
|
81
95
|
end
|
|
82
96
|
end
|
|
83
97
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Profiler.current_profile.pop_partial
|
|
89
|
-
output
|
|
98
|
+
def children
|
|
99
|
+
children = @root_children
|
|
100
|
+
if children.length == 1
|
|
101
|
+
children.first.children
|
|
90
102
|
else
|
|
91
|
-
|
|
103
|
+
children
|
|
92
104
|
end
|
|
93
105
|
end
|
|
94
106
|
|
|
95
|
-
def self.current_profile
|
|
96
|
-
Thread.current[:liquid_profiler]
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def initialize
|
|
100
|
-
@partial_stack = ["<root>"]
|
|
101
|
-
|
|
102
|
-
@root_timing = Timing.new("", current_partial)
|
|
103
|
-
@timing_stack = [@root_timing]
|
|
104
|
-
|
|
105
|
-
@render_start_at = Time.now
|
|
106
|
-
@render_end_at = @render_start_at
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def start
|
|
110
|
-
Thread.current[:liquid_profiler] = self
|
|
111
|
-
@render_start_at = Time.now
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def stop
|
|
115
|
-
Thread.current[:liquid_profiler] = nil
|
|
116
|
-
@render_end_at = Time.now
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def total_render_time
|
|
120
|
-
@render_end_at - @render_start_at
|
|
121
|
-
end
|
|
122
|
-
|
|
123
107
|
def each(&block)
|
|
124
|
-
|
|
108
|
+
children.each(&block)
|
|
125
109
|
end
|
|
126
110
|
|
|
127
111
|
def [](idx)
|
|
128
|
-
|
|
112
|
+
children[idx]
|
|
129
113
|
end
|
|
130
114
|
|
|
131
115
|
def length
|
|
132
|
-
|
|
116
|
+
children.length
|
|
133
117
|
end
|
|
134
118
|
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@partial_stack.last
|
|
119
|
+
def profile_node(template_name, code: nil, line_number: nil)
|
|
120
|
+
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
|
|
121
|
+
parent_children = @current_children
|
|
122
|
+
start_time = monotonic_time
|
|
123
|
+
begin
|
|
124
|
+
@current_children = timing.children
|
|
125
|
+
yield
|
|
126
|
+
ensure
|
|
127
|
+
@current_children = parent_children
|
|
128
|
+
timing.total_time = monotonic_time - start_time
|
|
129
|
+
parent_children << timing
|
|
130
|
+
end
|
|
148
131
|
end
|
|
149
132
|
|
|
150
|
-
|
|
151
|
-
@partial_stack.push(partial_name)
|
|
152
|
-
end
|
|
133
|
+
private
|
|
153
134
|
|
|
154
|
-
def
|
|
155
|
-
|
|
135
|
+
def monotonic_time
|
|
136
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
156
137
|
end
|
|
157
138
|
end
|
|
158
139
|
end
|
data/lib/liquid/range_lookup.rb
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
4
|
class RangeLookup
|
|
3
|
-
def self.parse(start_markup, end_markup)
|
|
4
|
-
start_obj = Expression.parse(start_markup)
|
|
5
|
-
end_obj
|
|
5
|
+
def self.parse(start_markup, end_markup, string_scanner, cache = nil)
|
|
6
|
+
start_obj = Expression.parse(start_markup, string_scanner, cache)
|
|
7
|
+
end_obj = Expression.parse(end_markup, string_scanner, cache)
|
|
6
8
|
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
|
7
9
|
new(start_obj, end_obj)
|
|
8
10
|
else
|
|
9
|
-
|
|
11
|
+
begin
|
|
12
|
+
start_obj.to_i..end_obj.to_i
|
|
13
|
+
rescue NoMethodError
|
|
14
|
+
invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
|
|
15
|
+
invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
|
|
16
|
+
if invalid_expr
|
|
17
|
+
raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
raise
|
|
21
|
+
end
|
|
10
22
|
end
|
|
11
23
|
end
|
|
12
24
|
|
|
25
|
+
attr_reader :start_obj, :end_obj
|
|
26
|
+
|
|
13
27
|
def initialize(start_obj, end_obj)
|
|
14
28
|
@start_obj = start_obj
|
|
15
|
-
@end_obj
|
|
29
|
+
@end_obj = end_obj
|
|
16
30
|
end
|
|
17
31
|
|
|
18
32
|
def evaluate(context)
|
|
19
33
|
start_int = to_integer(context.evaluate(@start_obj))
|
|
20
|
-
end_int
|
|
34
|
+
end_int = to_integer(context.evaluate(@end_obj))
|
|
21
35
|
start_int..end_int
|
|
22
36
|
end
|
|
23
37
|
|
|
@@ -33,5 +47,11 @@ module Liquid
|
|
|
33
47
|
Utils.to_integer(input)
|
|
34
48
|
end
|
|
35
49
|
end
|
|
50
|
+
|
|
51
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
52
|
+
def children
|
|
53
|
+
[@node.start_obj, @node.end_obj]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
36
56
|
end
|
|
37
57
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
class Registers
|
|
5
|
+
attr_reader :static
|
|
6
|
+
|
|
7
|
+
def initialize(registers = {})
|
|
8
|
+
@static = registers.is_a?(Registers) ? registers.static : registers
|
|
9
|
+
@changes = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def []=(key, value)
|
|
13
|
+
@changes[key] = value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def [](key)
|
|
17
|
+
if @changes.key?(key)
|
|
18
|
+
@changes[key]
|
|
19
|
+
else
|
|
20
|
+
@static[key]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete(key)
|
|
25
|
+
@changes.delete(key)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
UNDEFINED = Object.new
|
|
29
|
+
|
|
30
|
+
def fetch(key, default = UNDEFINED, &block)
|
|
31
|
+
if @changes.key?(key)
|
|
32
|
+
@changes.fetch(key)
|
|
33
|
+
elsif default != UNDEFINED
|
|
34
|
+
if block_given?
|
|
35
|
+
@static.fetch(key, &block)
|
|
36
|
+
else
|
|
37
|
+
@static.fetch(key, default)
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
@static.fetch(key, &block)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def key?(key)
|
|
45
|
+
@changes.key?(key) || @static.key?(key)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Alias for backwards compatibility
|
|
50
|
+
StaticRegisters = Registers
|
|
51
|
+
end
|