liquid-4-0-2 4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.md +235 -0
- data/LICENSE +20 -0
- data/README.md +108 -0
- data/lib/liquid.rb +80 -0
- data/lib/liquid/block.rb +77 -0
- data/lib/liquid/block_body.rb +142 -0
- data/lib/liquid/condition.rb +151 -0
- data/lib/liquid/context.rb +226 -0
- data/lib/liquid/document.rb +27 -0
- data/lib/liquid/drop.rb +78 -0
- data/lib/liquid/errors.rb +56 -0
- data/lib/liquid/expression.rb +49 -0
- data/lib/liquid/extensions.rb +74 -0
- data/lib/liquid/file_system.rb +73 -0
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +16 -0
- 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 +485 -0
- data/lib/liquid/strainer.rb +66 -0
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +43 -0
- data/lib/liquid/tags/assign.rb +59 -0
- data/lib/liquid/tags/break.rb +18 -0
- data/lib/liquid/tags/capture.rb +38 -0
- data/lib/liquid/tags/case.rb +94 -0
- data/lib/liquid/tags/comment.rb +16 -0
- data/lib/liquid/tags/continue.rb +18 -0
- data/lib/liquid/tags/cycle.rb +65 -0
- data/lib/liquid/tags/decrement.rb +35 -0
- data/lib/liquid/tags/for.rb +203 -0
- data/lib/liquid/tags/if.rb +122 -0
- data/lib/liquid/tags/ifchanged.rb +18 -0
- data/lib/liquid/tags/include.rb +124 -0
- data/lib/liquid/tags/increment.rb +31 -0
- data/lib/liquid/tags/raw.rb +47 -0
- data/lib/liquid/tags/table_row.rb +62 -0
- data/lib/liquid/tags/unless.rb +30 -0
- data/lib/liquid/template.rb +254 -0
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +83 -0
- data/lib/liquid/variable.rb +148 -0
- data/lib/liquid/variable_lookup.rb +88 -0
- data/lib/liquid/version.rb +4 -0
- 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/integration/capture_test.rb +50 -0
- 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 +698 -0
- data/test/integration/tags/break_tag_test.rb +15 -0
- data/test/integration/tags/continue_tag_test.rb +15 -0
- 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 +245 -0
- data/test/integration/tags/increment_tag_test.rb +23 -0
- data/test/integration/tags/raw_tag_test.rb +31 -0
- data/test/integration/tags/standard_tag_test.rb +296 -0
- data/test/integration/tags/statements_test.rb +111 -0
- data/test/integration/tags/table_row_test.rb +64 -0
- 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 +116 -0
- data/test/unit/block_unit_test.rb +58 -0
- data/test/unit/condition_unit_test.rb +166 -0
- data/test/unit/context_unit_test.rb +489 -0
- 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/unit/regexp_unit_test.rb +44 -0
- 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 +224 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Liquid
|
2
|
+
# An interrupt is any command that breaks processing of a block (ex: a for loop).
|
3
|
+
class Interrupt
|
4
|
+
attr_reader :message
|
5
|
+
|
6
|
+
def initialize(message = nil)
|
7
|
+
@message = message || "interrupt".freeze
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Interrupt that is thrown whenever a {% break %} is called.
|
12
|
+
class BreakInterrupt < Interrupt; end
|
13
|
+
|
14
|
+
# Interrupt that is thrown whenever a {% continue %} is called.
|
15
|
+
class ContinueInterrupt < Interrupt; end
|
16
|
+
end
|
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
|
+
}
|
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
|