liquid 2.6.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 +5 -5
- data/History.md +272 -26
- data/README.md +67 -3
- data/lib/liquid/block.rb +62 -94
- data/lib/liquid/block_body.rb +255 -0
- data/lib/liquid/condition.rb +96 -38
- data/lib/liquid/context.rb +172 -154
- data/lib/liquid/document.rb +57 -9
- data/lib/liquid/drop.rb +33 -14
- data/lib/liquid/errors.rb +56 -10
- data/lib/liquid/expression.rb +45 -0
- data/lib/liquid/extensions.rb +21 -7
- data/lib/liquid/file_system.rb +27 -14
- data/lib/liquid/forloop_drop.rb +92 -0
- data/lib/liquid/i18n.rb +41 -0
- data/lib/liquid/interrupts.rb +3 -2
- data/lib/liquid/lexer.rb +62 -0
- data/lib/liquid/locales/en.yml +29 -0
- data/lib/liquid/parse_context.rb +54 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +102 -0
- data/lib/liquid/parser_switching.rb +45 -0
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +35 -0
- data/lib/liquid/profiler.rb +139 -0
- data/lib/liquid/range_lookup.rb +47 -0
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +62 -0
- data/lib/liquid/standardfilters.rb +789 -118
- data/lib/liquid/strainer_factory.rb +41 -0
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +121 -0
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tag.rb +49 -10
- data/lib/liquid/tags/assign.rb +61 -19
- data/lib/liquid/tags/break.rb +14 -4
- data/lib/liquid/tags/capture.rb +29 -21
- data/lib/liquid/tags/case.rb +80 -31
- data/lib/liquid/tags/comment.rb +24 -2
- data/lib/liquid/tags/continue.rb +14 -13
- data/lib/liquid/tags/cycle.rb +50 -32
- data/lib/liquid/tags/decrement.rb +24 -26
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +164 -100
- data/lib/liquid/tags/if.rb +105 -44
- data/lib/liquid/tags/ifchanged.rb +10 -11
- data/lib/liquid/tags/include.rb +85 -65
- data/lib/liquid/tags/increment.rb +24 -22
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +50 -11
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +88 -0
- data/lib/liquid/tags/unless.rb +37 -21
- data/lib/liquid/template.rb +124 -46
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +39 -0
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +68 -5
- data/lib/liquid/variable.rb +128 -32
- data/lib/liquid/variable_lookup.rb +96 -0
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +36 -13
- metadata +69 -77
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/liquid/htmltags.rb +0 -73
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/strainer.rb +0 -53
- data/test/liquid/assign_test.rb +0 -21
- data/test/liquid/block_test.rb +0 -58
- data/test/liquid/capture_test.rb +0 -40
- data/test/liquid/condition_test.rb +0 -127
- data/test/liquid/context_test.rb +0 -478
- data/test/liquid/drop_test.rb +0 -180
- data/test/liquid/error_handling_test.rb +0 -81
- data/test/liquid/file_system_test.rb +0 -29
- data/test/liquid/filter_test.rb +0 -125
- data/test/liquid/hash_ordering_test.rb +0 -25
- data/test/liquid/module_ex_test.rb +0 -87
- data/test/liquid/output_test.rb +0 -116
- data/test/liquid/parsing_quirks_test.rb +0 -52
- data/test/liquid/regexp_test.rb +0 -44
- data/test/liquid/security_test.rb +0 -64
- data/test/liquid/standard_filter_test.rb +0 -263
- data/test/liquid/strainer_test.rb +0 -52
- data/test/liquid/tags/break_tag_test.rb +0 -16
- data/test/liquid/tags/continue_tag_test.rb +0 -16
- data/test/liquid/tags/for_tag_test.rb +0 -297
- data/test/liquid/tags/html_tag_test.rb +0 -63
- data/test/liquid/tags/if_else_tag_test.rb +0 -166
- data/test/liquid/tags/include_tag_test.rb +0 -166
- data/test/liquid/tags/increment_tag_test.rb +0 -24
- data/test/liquid/tags/raw_tag_test.rb +0 -24
- data/test/liquid/tags/standard_tag_test.rb +0 -295
- data/test/liquid/tags/statements_test.rb +0 -134
- data/test/liquid/tags/unless_else_tag_test.rb +0 -26
- data/test/liquid/template_test.rb +0 -146
- data/test/liquid/variable_test.rb +0 -186
- data/test/test_helper.rb +0 -29
- /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'liquid/profiler/hooks'
|
4
|
+
|
5
|
+
module Liquid
|
6
|
+
# Profiler enables support for profiling template rendering to help track down performance issues.
|
7
|
+
#
|
8
|
+
# To enable profiling, first require 'liquid/profiler'.
|
9
|
+
# Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
|
10
|
+
# After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
|
11
|
+
# class via the <tt>Liquid::Template#profiler</tt> method.
|
12
|
+
#
|
13
|
+
# template = Liquid::Template.parse(template_content, profile: true)
|
14
|
+
# output = template.render
|
15
|
+
# profile = template.profiler
|
16
|
+
#
|
17
|
+
# This object contains all profiling information, containing information on what tags were rendered,
|
18
|
+
# where in the templates these tags live, and how long each tag took to render.
|
19
|
+
#
|
20
|
+
# This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
|
21
|
+
# inside of <tt>{% include %}</tt> tags.
|
22
|
+
#
|
23
|
+
# profile.each do |node|
|
24
|
+
# # Access to the node itself
|
25
|
+
# node.code
|
26
|
+
#
|
27
|
+
# # Which template and line number of this node.
|
28
|
+
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
|
29
|
+
# node.partial
|
30
|
+
# node.line_number
|
31
|
+
#
|
32
|
+
# # Render time in seconds of this node
|
33
|
+
# node.render_time
|
34
|
+
#
|
35
|
+
# # If the template used {% include %}, this node will also have children.
|
36
|
+
# node.children.each do |child2|
|
37
|
+
# # ...
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
|
42
|
+
#
|
43
|
+
# All render times are in seconds. There is a small performance hit when profiling is enabled.
|
44
|
+
#
|
45
|
+
class Profiler
|
46
|
+
include Enumerable
|
47
|
+
|
48
|
+
class Timing
|
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 = []
|
59
|
+
end
|
60
|
+
|
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
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
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
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def children
|
99
|
+
children = @root_children
|
100
|
+
if children.length == 1
|
101
|
+
children.first.children
|
102
|
+
else
|
103
|
+
children
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def each(&block)
|
108
|
+
children.each(&block)
|
109
|
+
end
|
110
|
+
|
111
|
+
def [](idx)
|
112
|
+
children[idx]
|
113
|
+
end
|
114
|
+
|
115
|
+
def length
|
116
|
+
children.length
|
117
|
+
end
|
118
|
+
|
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
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def monotonic_time
|
136
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class RangeLookup
|
5
|
+
def self.parse(start_markup, end_markup)
|
6
|
+
start_obj = Expression.parse(start_markup)
|
7
|
+
end_obj = Expression.parse(end_markup)
|
8
|
+
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
9
|
+
new(start_obj, end_obj)
|
10
|
+
else
|
11
|
+
start_obj.to_i..end_obj.to_i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :start_obj, :end_obj
|
16
|
+
|
17
|
+
def initialize(start_obj, end_obj)
|
18
|
+
@start_obj = start_obj
|
19
|
+
@end_obj = end_obj
|
20
|
+
end
|
21
|
+
|
22
|
+
def evaluate(context)
|
23
|
+
start_int = to_integer(context.evaluate(@start_obj))
|
24
|
+
end_int = to_integer(context.evaluate(@end_obj))
|
25
|
+
start_int..end_int
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def to_integer(input)
|
31
|
+
case input
|
32
|
+
when Integer
|
33
|
+
input
|
34
|
+
when NilClass, String
|
35
|
+
input.to_i
|
36
|
+
else
|
37
|
+
Utils.to_integer(input)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
42
|
+
def children
|
43
|
+
[@node.start_obj, @node.end_obj]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class ResourceLimits
|
5
|
+
attr_accessor :render_length_limit, :render_score_limit, :assign_score_limit
|
6
|
+
attr_reader :render_score, :assign_score
|
7
|
+
|
8
|
+
def initialize(limits)
|
9
|
+
@render_length_limit = limits[:render_length_limit]
|
10
|
+
@render_score_limit = limits[:render_score_limit]
|
11
|
+
@assign_score_limit = limits[:assign_score_limit]
|
12
|
+
reset
|
13
|
+
end
|
14
|
+
|
15
|
+
def increment_render_score(amount)
|
16
|
+
@render_score += amount
|
17
|
+
raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
|
18
|
+
end
|
19
|
+
|
20
|
+
def increment_assign_score(amount)
|
21
|
+
@assign_score += amount
|
22
|
+
raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
|
23
|
+
end
|
24
|
+
|
25
|
+
# update either render_length or assign_score based on whether or not the writes are captured
|
26
|
+
def increment_write_score(output)
|
27
|
+
if (last_captured = @last_capture_length)
|
28
|
+
captured = output.bytesize
|
29
|
+
increment = captured - last_captured
|
30
|
+
@last_capture_length = captured
|
31
|
+
increment_assign_score(increment)
|
32
|
+
elsif @render_length_limit && output.bytesize > @render_length_limit
|
33
|
+
raise_limits_reached
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def raise_limits_reached
|
38
|
+
@reached_limit = true
|
39
|
+
raise MemoryError, "Memory limits exceeded"
|
40
|
+
end
|
41
|
+
|
42
|
+
def reached?
|
43
|
+
@reached_limit
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset
|
47
|
+
@reached_limit = false
|
48
|
+
@last_capture_length = nil
|
49
|
+
@render_score = @assign_score = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_capture
|
53
|
+
old_capture_length = @last_capture_length
|
54
|
+
begin
|
55
|
+
@last_capture_length = 0
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
@last_capture_length = old_capture_length
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|