liquid 2.6.3 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|