liquid 2.6.1 → 4.0.3
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 +194 -29
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +60 -2
- data/lib/liquid.rb +25 -14
- data/lib/liquid/block.rb +47 -96
- data/lib/liquid/block_body.rb +143 -0
- data/lib/liquid/condition.rb +70 -39
- data/lib/liquid/context.rb +116 -157
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +31 -14
- data/lib/liquid/errors.rb +54 -10
- data/lib/liquid/expression.rb +49 -0
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +25 -14
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +2 -3
- data/lib/liquid/lexer.rb +55 -0
- data/lib/liquid/locales/en.yml +26 -0
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +90 -0
- data/lib/liquid/parser_switching.rb +31 -0
- data/lib/liquid/profiler.rb +158 -0
- data/lib/liquid/profiler/hooks.rb +23 -0
- data/lib/liquid/range_lookup.rb +37 -0
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +311 -77
- data/lib/liquid/strainer.rb +39 -26
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +28 -11
- data/lib/liquid/tags/assign.rb +34 -10
- data/lib/liquid/tags/break.rb +1 -4
- data/lib/liquid/tags/capture.rb +11 -9
- data/lib/liquid/tags/case.rb +37 -22
- data/lib/liquid/tags/comment.rb +10 -3
- data/lib/liquid/tags/continue.rb +1 -4
- data/lib/liquid/tags/cycle.rb +20 -14
- data/lib/liquid/tags/decrement.rb +4 -8
- data/lib/liquid/tags/for.rb +121 -60
- data/lib/liquid/tags/if.rb +73 -30
- data/lib/liquid/tags/ifchanged.rb +3 -5
- data/lib/liquid/tags/include.rb +77 -46
- data/lib/liquid/tags/increment.rb +4 -8
- data/lib/liquid/tags/raw.rb +35 -10
- data/lib/liquid/tags/table_row.rb +62 -0
- data/lib/liquid/tags/unless.rb +6 -9
- data/lib/liquid/template.rb +130 -32
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +57 -4
- data/lib/liquid/variable.rb +121 -30
- data/lib/liquid/variable_lookup.rb +88 -0
- data/lib/liquid/version.rb +2 -1
- data/test/fixtures/en_locale.yml +9 -0
- data/test/integration/assign_test.rb +48 -0
- data/test/integration/blank_test.rb +106 -0
- data/test/integration/block_test.rb +12 -0
- data/test/{liquid → integration}/capture_test.rb +13 -3
- data/test/integration/context_test.rb +32 -0
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +273 -0
- data/test/integration/error_handling_test.rb +260 -0
- data/test/integration/filter_test.rb +178 -0
- data/test/integration/hash_ordering_test.rb +23 -0
- data/test/integration/output_test.rb +123 -0
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +122 -0
- data/test/integration/render_profiling_test.rb +154 -0
- data/test/integration/security_test.rb +80 -0
- data/test/integration/standard_filter_test.rb +776 -0
- data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
- data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
- data/test/integration/tags/for_tag_test.rb +410 -0
- data/test/integration/tags/if_else_tag_test.rb +188 -0
- data/test/integration/tags/include_tag_test.rb +253 -0
- data/test/integration/tags/increment_tag_test.rb +23 -0
- data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
- data/test/integration/tags/standard_tag_test.rb +296 -0
- data/test/integration/tags/statements_test.rb +111 -0
- data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
- data/test/integration/tags/unless_else_tag_test.rb +26 -0
- data/test/integration/template_test.rb +332 -0
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +96 -0
- data/test/test_helper.rb +106 -19
- data/test/truffle/truffle_test.rb +9 -0
- data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
- data/test/unit/condition_unit_test.rb +166 -0
- data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
- data/test/unit/file_system_unit_test.rb +35 -0
- data/test/unit/i18n_unit_test.rb +37 -0
- data/test/unit/lexer_unit_test.rb +51 -0
- data/test/unit/parser_unit_test.rb +82 -0
- data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
- data/test/unit/strainer_unit_test.rb +164 -0
- data/test/unit/tag_unit_test.rb +21 -0
- data/test/unit/tags/case_tag_unit_test.rb +10 -0
- data/test/unit/tags/for_tag_unit_test.rb +13 -0
- data/test/unit/tags/if_tag_unit_test.rb +8 -0
- data/test/unit/template_unit_test.rb +78 -0
- data/test/unit/tokenizer_unit_test.rb +55 -0
- data/test/unit/variable_unit_test.rb +162 -0
- metadata +157 -77
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/liquid/htmltags.rb +0 -74
- data/lib/liquid/module_ex.rb +0 -62
- data/test/liquid/assign_test.rb +0 -21
- data/test/liquid/condition_test.rb +0 -127
- 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/security_test.rb +0 -64
- data/test/liquid/standard_filter_test.rb +0 -251
- data/test/liquid/strainer_test.rb +0 -52
- data/test/liquid/tags/for_tag_test.rb +0 -297
- 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/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/lib/liquid/interrupts.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# An interrupt is any command that breaks processing of a block (ex: a for loop).
|
4
3
|
class Interrupt
|
5
4
|
attr_reader :message
|
6
5
|
|
7
|
-
def initialize(message=nil)
|
8
|
-
@message = message || "interrupt"
|
6
|
+
def initialize(message = nil)
|
7
|
+
@message = message || "interrupt".freeze
|
9
8
|
end
|
10
9
|
end
|
11
10
|
|
data/lib/liquid/lexer.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "strscan"
|
2
|
+
module Liquid
|
3
|
+
class Lexer
|
4
|
+
SPECIALS = {
|
5
|
+
'|'.freeze => :pipe,
|
6
|
+
'.'.freeze => :dot,
|
7
|
+
':'.freeze => :colon,
|
8
|
+
','.freeze => :comma,
|
9
|
+
'['.freeze => :open_square,
|
10
|
+
']'.freeze => :close_square,
|
11
|
+
'('.freeze => :open_round,
|
12
|
+
')'.freeze => :close_round,
|
13
|
+
'?'.freeze => :question,
|
14
|
+
'-'.freeze => :dash
|
15
|
+
}.freeze
|
16
|
+
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
17
|
+
SINGLE_STRING_LITERAL = /'[^\']*'/
|
18
|
+
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
19
|
+
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
20
|
+
DOTDOT = /\.\./
|
21
|
+
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
22
|
+
WHITESPACE_OR_NOTHING = /\s*/
|
23
|
+
|
24
|
+
def initialize(input)
|
25
|
+
@ss = StringScanner.new(input)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tokenize
|
29
|
+
@output = []
|
30
|
+
|
31
|
+
until @ss.eos?
|
32
|
+
@ss.skip(WHITESPACE_OR_NOTHING)
|
33
|
+
break if @ss.eos?
|
34
|
+
tok = case
|
35
|
+
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
36
|
+
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
|
37
|
+
when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t]
|
38
|
+
when t = @ss.scan(NUMBER_LITERAL) then [:number, t]
|
39
|
+
when t = @ss.scan(IDENTIFIER) then [:id, t]
|
40
|
+
when t = @ss.scan(DOTDOT) then [:dotdot, t]
|
41
|
+
else
|
42
|
+
c = @ss.getch
|
43
|
+
if s = SPECIALS[c]
|
44
|
+
[s, c]
|
45
|
+
else
|
46
|
+
raise SyntaxError, "Unexpected character #{c}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@output << tok
|
50
|
+
end
|
51
|
+
|
52
|
+
@output << [:end_of_string]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
errors:
|
3
|
+
syntax:
|
4
|
+
tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
|
5
|
+
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
|
6
|
+
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
|
7
|
+
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
|
8
|
+
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
|
9
|
+
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
|
10
|
+
cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
|
11
|
+
for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
|
12
|
+
for_invalid_in: "For loops require an 'in' clause"
|
13
|
+
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
|
14
|
+
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
15
|
+
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
16
|
+
unknown_tag: "Unknown tag '%{tag}'"
|
17
|
+
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
18
|
+
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
19
|
+
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
20
|
+
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
21
|
+
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
|
22
|
+
tag_never_closed: "'%{block_name}' tag was never closed"
|
23
|
+
meta_syntax_error: "Liquid syntax error: #{e.message}"
|
24
|
+
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
25
|
+
argument:
|
26
|
+
include: "Argument error in tag 'include' - Illegal template name"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Liquid
|
2
|
+
class ParseContext
|
3
|
+
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
4
|
+
attr_reader :partial, :warnings, :error_mode
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@template_options = options ? options.dup : {}
|
8
|
+
@locale = @template_options[:locale] ||= I18n.new
|
9
|
+
@warnings = []
|
10
|
+
self.depth = 0
|
11
|
+
self.partial = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](option_key)
|
15
|
+
@options[option_key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def partial=(value)
|
19
|
+
@partial = value
|
20
|
+
@options = value ? partial_options : @template_options
|
21
|
+
@error_mode = @options[:error_mode] || Template.error_mode
|
22
|
+
value
|
23
|
+
end
|
24
|
+
|
25
|
+
def partial_options
|
26
|
+
@partial_options ||= begin
|
27
|
+
dont_pass = @template_options[:include_options_blacklist]
|
28
|
+
if dont_pass == true
|
29
|
+
{ locale: locale }
|
30
|
+
elsif dont_pass.is_a?(Array)
|
31
|
+
@template_options.reject { |k, v| dont_pass.include?(k) }
|
32
|
+
else
|
33
|
+
@template_options
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
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) : []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Liquid
|
2
|
+
class Parser
|
3
|
+
def initialize(input)
|
4
|
+
l = Lexer.new(input)
|
5
|
+
@tokens = l.tokenize
|
6
|
+
@p = 0 # pointer to current location
|
7
|
+
end
|
8
|
+
|
9
|
+
def jump(point)
|
10
|
+
@p = point
|
11
|
+
end
|
12
|
+
|
13
|
+
def consume(type = nil)
|
14
|
+
token = @tokens[@p]
|
15
|
+
if type && token[0] != type
|
16
|
+
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
|
17
|
+
end
|
18
|
+
@p += 1
|
19
|
+
token[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Only consumes the token if it matches the type
|
23
|
+
# Returns the token's contents if it was consumed
|
24
|
+
# or false otherwise.
|
25
|
+
def consume?(type)
|
26
|
+
token = @tokens[@p]
|
27
|
+
return false unless token && token[0] == type
|
28
|
+
@p += 1
|
29
|
+
token[1]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Like consume? Except for an :id token of a certain name
|
33
|
+
def id?(str)
|
34
|
+
token = @tokens[@p]
|
35
|
+
return false unless token && token[0] == :id
|
36
|
+
return false unless token[1] == str
|
37
|
+
@p += 1
|
38
|
+
token[1]
|
39
|
+
end
|
40
|
+
|
41
|
+
def look(type, ahead = 0)
|
42
|
+
tok = @tokens[@p + ahead]
|
43
|
+
return false unless tok
|
44
|
+
tok[0] == type
|
45
|
+
end
|
46
|
+
|
47
|
+
def expression
|
48
|
+
token = @tokens[@p]
|
49
|
+
if token[0] == :id
|
50
|
+
variable_signature
|
51
|
+
elsif [:string, :number].include? token[0]
|
52
|
+
consume
|
53
|
+
elsif token.first == :open_round
|
54
|
+
consume
|
55
|
+
first = expression
|
56
|
+
consume(:dotdot)
|
57
|
+
last = expression
|
58
|
+
consume(:close_round)
|
59
|
+
"(#{first}..#{last})"
|
60
|
+
else
|
61
|
+
raise SyntaxError, "#{token} is not a valid expression"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def argument
|
66
|
+
str = ""
|
67
|
+
# might be a keyword argument (identifier: expression)
|
68
|
+
if look(:id) && look(:colon, 1)
|
69
|
+
str << consume << consume << ' '.freeze
|
70
|
+
end
|
71
|
+
|
72
|
+
str << expression
|
73
|
+
str
|
74
|
+
end
|
75
|
+
|
76
|
+
def variable_signature
|
77
|
+
str = consume(:id)
|
78
|
+
while look(:open_square)
|
79
|
+
str << consume
|
80
|
+
str << expression
|
81
|
+
str << consume(:close_square)
|
82
|
+
end
|
83
|
+
if look(:dot)
|
84
|
+
str << consume
|
85
|
+
str << variable_signature
|
86
|
+
end
|
87
|
+
str
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Liquid
|
2
|
+
module ParserSwitching
|
3
|
+
def parse_with_selected_parser(markup)
|
4
|
+
case parse_context.error_mode
|
5
|
+
when :strict then strict_parse_with_error_context(markup)
|
6
|
+
when :lax then lax_parse(markup)
|
7
|
+
when :warn
|
8
|
+
begin
|
9
|
+
return strict_parse_with_error_context(markup)
|
10
|
+
rescue SyntaxError => e
|
11
|
+
parse_context.warnings << e
|
12
|
+
return lax_parse(markup)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def strict_parse_with_error_context(markup)
|
20
|
+
strict_parse(markup)
|
21
|
+
rescue SyntaxError => e
|
22
|
+
e.line_number = line_number
|
23
|
+
e.markup_context = markup_context(markup)
|
24
|
+
raise e
|
25
|
+
end
|
26
|
+
|
27
|
+
def markup_context(markup)
|
28
|
+
"in \"#{markup.strip}\""
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'liquid/profiler/hooks'
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# Profiler enables support for profiling template rendering to help track down performance issues.
|
5
|
+
#
|
6
|
+
# To enable profiling, first require 'liquid/profiler'.
|
7
|
+
# Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
|
8
|
+
# After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
|
9
|
+
# class via the <tt>Liquid::Template#profiler</tt> method.
|
10
|
+
#
|
11
|
+
# template = Liquid::Template.parse(template_content, profile: true)
|
12
|
+
# output = template.render
|
13
|
+
# profile = template.profiler
|
14
|
+
#
|
15
|
+
# This object contains all profiling information, containing information on what tags were rendered,
|
16
|
+
# where in the templates these tags live, and how long each tag took to render.
|
17
|
+
#
|
18
|
+
# This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
|
19
|
+
# inside of <tt>{% include %}</tt> tags.
|
20
|
+
#
|
21
|
+
# profile.each do |node|
|
22
|
+
# # Access to the node itself
|
23
|
+
# node.code
|
24
|
+
#
|
25
|
+
# # Which template and line number of this node.
|
26
|
+
# # If top level, this will be "<root>".
|
27
|
+
# node.partial
|
28
|
+
# node.line_number
|
29
|
+
#
|
30
|
+
# # Render time in seconds of this node
|
31
|
+
# node.render_time
|
32
|
+
#
|
33
|
+
# # If the template used {% include %}, this node will also have children.
|
34
|
+
# node.children.each do |child2|
|
35
|
+
# # ...
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
|
40
|
+
#
|
41
|
+
# All render times are in seconds. There is a small performance hit when profiling is enabled.
|
42
|
+
#
|
43
|
+
class Profiler
|
44
|
+
include Enumerable
|
45
|
+
|
46
|
+
class Timing
|
47
|
+
attr_reader :code, :partial, :line_number, :children
|
48
|
+
|
49
|
+
def initialize(node, partial)
|
50
|
+
@code = node.respond_to?(:raw) ? node.raw : node
|
51
|
+
@partial = partial
|
52
|
+
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
|
53
|
+
@children = []
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.start(node, partial)
|
57
|
+
new(node, partial).tap(&:start)
|
58
|
+
end
|
59
|
+
|
60
|
+
def start
|
61
|
+
@start_time = Time.now
|
62
|
+
end
|
63
|
+
|
64
|
+
def finish
|
65
|
+
@end_time = Time.now
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_time
|
69
|
+
@end_time - @start_time
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.profile_node_render(node)
|
74
|
+
if Profiler.current_profile && node.respond_to?(:render)
|
75
|
+
Profiler.current_profile.start_node(node)
|
76
|
+
output = yield
|
77
|
+
Profiler.current_profile.end_node(node)
|
78
|
+
output
|
79
|
+
else
|
80
|
+
yield
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.profile_children(template_name)
|
85
|
+
if Profiler.current_profile
|
86
|
+
Profiler.current_profile.push_partial(template_name)
|
87
|
+
output = yield
|
88
|
+
Profiler.current_profile.pop_partial
|
89
|
+
output
|
90
|
+
else
|
91
|
+
yield
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
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
|
+
def each(&block)
|
124
|
+
@root_timing.children.each(&block)
|
125
|
+
end
|
126
|
+
|
127
|
+
def [](idx)
|
128
|
+
@root_timing.children[idx]
|
129
|
+
end
|
130
|
+
|
131
|
+
def length
|
132
|
+
@root_timing.children.length
|
133
|
+
end
|
134
|
+
|
135
|
+
def start_node(node)
|
136
|
+
@timing_stack.push(Timing.start(node, current_partial))
|
137
|
+
end
|
138
|
+
|
139
|
+
def end_node(_node)
|
140
|
+
timing = @timing_stack.pop
|
141
|
+
timing.finish
|
142
|
+
|
143
|
+
@timing_stack.last.children << timing
|
144
|
+
end
|
145
|
+
|
146
|
+
def current_partial
|
147
|
+
@partial_stack.last
|
148
|
+
end
|
149
|
+
|
150
|
+
def push_partial(partial_name)
|
151
|
+
@partial_stack.push(partial_name)
|
152
|
+
end
|
153
|
+
|
154
|
+
def pop_partial
|
155
|
+
@partial_stack.pop
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|