liquid 4.0.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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +89 -0
  3. data/README.md +10 -4
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +169 -57
  6. data/lib/liquid/condition.rb +48 -21
  7. data/lib/liquid/context.rb +111 -52
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +28 -32
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +54 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +8 -5
  19. data/lib/liquid/parse_context.rb +20 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +13 -3
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +551 -114
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +64 -5
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tags/assign.rb +36 -18
  37. data/lib/liquid/tags/break.rb +16 -3
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +61 -27
  40. data/lib/liquid/tags/comment.rb +18 -3
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +37 -25
  43. data/lib/liquid/tags/decrement.rb +22 -20
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +90 -87
  46. data/lib/liquid/tags/if.rb +50 -32
  47. data/lib/liquid/tags/ifchanged.rb +11 -10
  48. data/lib/liquid/tags/include.rb +49 -60
  49. data/lib/liquid/tags/increment.rb +23 -17
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +25 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +45 -19
  54. data/lib/liquid/tags/unless.rb +38 -19
  55. data/lib/liquid/template.rb +52 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +49 -44
  61. data/lib/liquid/variable_lookup.rb +18 -10
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +18 -6
  64. metadata +20 -108
  65. data/lib/liquid/strainer.rb +0 -66
  66. data/lib/liquid/truffle.rb +0 -5
  67. data/test/fixtures/en_locale.yml +0 -9
  68. data/test/integration/assign_test.rb +0 -48
  69. data/test/integration/blank_test.rb +0 -106
  70. data/test/integration/block_test.rb +0 -12
  71. data/test/integration/capture_test.rb +0 -50
  72. data/test/integration/context_test.rb +0 -32
  73. data/test/integration/document_test.rb +0 -19
  74. data/test/integration/drop_test.rb +0 -273
  75. data/test/integration/error_handling_test.rb +0 -260
  76. data/test/integration/filter_test.rb +0 -178
  77. data/test/integration/hash_ordering_test.rb +0 -23
  78. data/test/integration/output_test.rb +0 -123
  79. data/test/integration/parse_tree_visitor_test.rb +0 -247
  80. data/test/integration/parsing_quirks_test.rb +0 -122
  81. data/test/integration/render_profiling_test.rb +0 -154
  82. data/test/integration/security_test.rb +0 -80
  83. data/test/integration/standard_filter_test.rb +0 -776
  84. data/test/integration/tags/break_tag_test.rb +0 -15
  85. data/test/integration/tags/continue_tag_test.rb +0 -15
  86. data/test/integration/tags/for_tag_test.rb +0 -410
  87. data/test/integration/tags/if_else_tag_test.rb +0 -188
  88. data/test/integration/tags/include_tag_test.rb +0 -253
  89. data/test/integration/tags/increment_tag_test.rb +0 -23
  90. data/test/integration/tags/raw_tag_test.rb +0 -31
  91. data/test/integration/tags/standard_tag_test.rb +0 -296
  92. data/test/integration/tags/statements_test.rb +0 -111
  93. data/test/integration/tags/table_row_test.rb +0 -64
  94. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  95. data/test/integration/template_test.rb +0 -332
  96. data/test/integration/trim_mode_test.rb +0 -529
  97. data/test/integration/variable_test.rb +0 -96
  98. data/test/test_helper.rb +0 -116
  99. data/test/truffle/truffle_test.rb +0 -9
  100. data/test/unit/block_unit_test.rb +0 -58
  101. data/test/unit/condition_unit_test.rb +0 -166
  102. data/test/unit/context_unit_test.rb +0 -489
  103. data/test/unit/file_system_unit_test.rb +0 -35
  104. data/test/unit/i18n_unit_test.rb +0 -37
  105. data/test/unit/lexer_unit_test.rb +0 -51
  106. data/test/unit/parser_unit_test.rb +0 -82
  107. data/test/unit/regexp_unit_test.rb +0 -44
  108. data/test/unit/strainer_unit_test.rb +0 -164
  109. data/test/unit/tag_unit_test.rb +0 -21
  110. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  111. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  112. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  113. data/test/unit/template_unit_test.rb +0 -78
  114. data/test/unit/tokenizer_unit_test.rb +0 -55
  115. data/test/unit/variable_unit_test.rb +0 -162
data/lib/liquid/lexer.rb CHANGED
@@ -1,24 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "strscan"
2
4
  module Liquid
3
5
  class Lexer
4
6
  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
7
+ '|' => :pipe,
8
+ '.' => :dot,
9
+ ':' => :colon,
10
+ ',' => :comma,
11
+ '[' => :open_square,
12
+ ']' => :close_square,
13
+ '(' => :open_round,
14
+ ')' => :close_round,
15
+ '?' => :question,
16
+ '-' => :dash,
15
17
  }.freeze
16
- IDENTIFIER = /[a-zA-Z_][\w-]*\??/
18
+ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
17
19
  SINGLE_STRING_LITERAL = /'[^\']*'/
18
20
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
19
- NUMBER_LITERAL = /-?\d+(\.\d+)?/
20
- DOTDOT = /\.\./
21
- COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
21
+ NUMBER_LITERAL = /-?\d+(\.\d+)?/
22
+ DOTDOT = /\.\./
23
+ COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
22
24
  WHITESPACE_OR_NOTHING = /\s*/
23
25
 
24
26
  def initialize(input)
@@ -31,16 +33,21 @@ module Liquid
31
33
  until @ss.eos?
32
34
  @ss.skip(WHITESPACE_OR_NOTHING)
33
35
  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]
36
+ tok = if (t = @ss.scan(COMPARISON_OPERATOR))
37
+ [:comparison, t]
38
+ elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
39
+ [:string, t]
40
+ elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
41
+ [:string, t]
42
+ elsif (t = @ss.scan(NUMBER_LITERAL))
43
+ [:number, t]
44
+ elsif (t = @ss.scan(IDENTIFIER))
45
+ [:id, t]
46
+ elsif (t = @ss.scan(DOTDOT))
47
+ [:dotdot, t]
41
48
  else
42
- c = @ss.getch
43
- if s = SPECIALS[c]
49
+ c = @ss.getch
50
+ if (s = SPECIALS[c])
44
51
  [s, c]
45
52
  else
46
53
  raise SyntaxError, "Unexpected character #{c}"
@@ -13,14 +13,17 @@
13
13
  for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
14
14
  if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
15
15
  include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
16
- unknown_tag: "Unknown tag '%{tag}'"
16
+ inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
17
17
  invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
18
+ render: "Syntax error in tag 'render' - Template name must be a quoted string"
19
+ table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
20
+ tag_never_closed: "'%{block_name}' tag was never closed"
21
+ tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
18
22
  unexpected_else: "%{block_name} tag does not expect 'else' tag"
19
23
  unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
20
- tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
24
+ unknown_tag: "Unknown tag '%{tag}'"
21
25
  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
26
  argument:
26
27
  include: "Argument error in tag 'include' - Illegal template name"
28
+ disabled:
29
+ tag: "usage is not allowed in this context"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class ParseContext
3
5
  attr_accessor :locale, :line_number, :trim_whitespace, :depth
@@ -5,9 +7,11 @@ module Liquid
5
7
 
6
8
  def initialize(options = {})
7
9
  @template_options = options ? options.dup : {}
8
- @locale = @template_options[:locale] ||= I18n.new
10
+
11
+ @locale = @template_options[:locale] ||= I18n.new
9
12
  @warnings = []
10
- self.depth = 0
13
+
14
+ self.depth = 0
11
15
  self.partial = false
12
16
  end
13
17
 
@@ -15,11 +19,23 @@ module Liquid
15
19
  @options[option_key]
16
20
  end
17
21
 
22
+ def new_block_body
23
+ Liquid::BlockBody.new
24
+ end
25
+
26
+ def new_tokenizer(markup, start_line_number: nil, for_liquid_tag: false)
27
+ Tokenizer.new(markup, line_number: start_line_number, for_liquid_tag: for_liquid_tag)
28
+ end
29
+
30
+ def parse_expression(markup)
31
+ Expression.parse(markup)
32
+ end
33
+
18
34
  def partial=(value)
19
35
  @partial = value
20
36
  @options = value ? partial_options : @template_options
37
+
21
38
  @error_mode = @options[:error_mode] || Template.error_mode
22
- value
23
39
  end
24
40
 
25
41
  def partial_options
@@ -28,7 +44,7 @@ module Liquid
28
44
  if dont_pass == true
29
45
  { locale: locale }
30
46
  elsif dont_pass.is_a?(Array)
31
- @template_options.reject { |k, v| dont_pass.include?(k) }
47
+ @template_options.reject { |k, _v| dont_pass.include?(k) }
32
48
  else
33
49
  @template_options
34
50
  end
@@ -11,7 +11,7 @@ module Liquid
11
11
  end
12
12
 
13
13
  def initialize(node, callbacks)
14
- @node = node
14
+ @node = node
15
15
  @callbacks = callbacks
16
16
  end
17
17
 
@@ -28,7 +28,7 @@ module Liquid
28
28
  item, new_context = @callbacks[node.class].call(node, context)
29
29
  [
30
30
  item,
31
- ParseTreeVisitor.for(node, @callbacks).visit(new_context || context)
31
+ ParseTreeVisitor.for(node, @callbacks).visit(new_context || context),
32
32
  ]
33
33
  end
34
34
  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
- l = Lexer.new(input)
6
+ l = Lexer.new(input)
5
7
  @tokens = l.tokenize
6
- @p = 0 # pointer to current location
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
- if token[0] == :id
50
- variable_signature
51
- elsif [:string, :number].include? token[0]
51
+ case token[0]
52
+ when :id
53
+ str = consume
54
+ str << variable_lookups
55
+ when :open_square
56
+ str = consume
57
+ str << expression
58
+ str << consume(:close_square)
59
+ str << variable_lookups
60
+ when :string, :number
52
61
  consume
53
- elsif token.first == :open_round
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 << ' '.freeze
78
+ str << consume << consume << ' '
70
79
  end
71
80
 
72
81
  str << expression
73
82
  str
74
83
  end
75
84
 
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
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,15 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  module ParserSwitching
5
+ def strict_parse_with_error_mode_fallback(markup)
6
+ strict_parse_with_error_context(markup)
7
+ rescue SyntaxError => e
8
+ case parse_context.error_mode
9
+ when :strict
10
+ raise
11
+ when :warn
12
+ parse_context.warnings << e
13
+ end
14
+ lax_parse(markup)
15
+ end
16
+
3
17
  def parse_with_selected_parser(markup)
4
18
  case parse_context.error_mode
5
19
  when :strict then strict_parse_with_error_context(markup)
6
20
  when :lax then lax_parse(markup)
7
21
  when :warn
8
22
  begin
9
- return strict_parse_with_error_context(markup)
23
+ strict_parse_with_error_context(markup)
10
24
  rescue SyntaxError => e
11
25
  parse_context.warnings << e
12
- return lax_parse(markup)
26
+ lax_parse(markup)
13
27
  end
14
28
  end
15
29
  end
@@ -19,7 +33,7 @@ module Liquid
19
33
  def strict_parse_with_error_context(markup)
20
34
  strict_parse(markup)
21
35
  rescue SyntaxError => e
22
- e.line_number = line_number
36
+ e.line_number = line_number
23
37
  e.markup_context = markup_context(markup)
24
38
  raise e
25
39
  end
@@ -0,0 +1,24 @@
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
+ cached = cached_partials[template_name]
8
+ return cached if cached
9
+
10
+ file_system = context.registers[:file_system]
11
+ source = file_system.read_template_file(template_name)
12
+
13
+ parse_context.partial = true
14
+
15
+ template_factory = context.registers[:template_factory]
16
+ template = template_factory.for(template_name)
17
+
18
+ partial = template.parse(source, parse_context)
19
+ cached_partials[template_name] = partial
20
+ ensure
21
+ parse_context.partial = false
22
+ end
23
+ end
24
+ end
@@ -1,23 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- class BlockBody
3
- def render_node_with_profiling(node, output, context, skip_output = false)
4
- Profiler.profile_node_render(node) do
5
- render_node_without_profiling(node, output, context, skip_output)
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_to_output
10
- alias_method :render_node_to_output, :render_node_with_profiling
11
14
  end
15
+ BlockBody.prepend(BlockBodyProfilingHook)
12
16
 
13
- class Include < Tag
14
- def render_with_profiling(context)
15
- Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
16
- render_without_profiling(context)
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
- alias_method :render_without_profiling, :render
21
- alias_method :render, :render_with_profiling
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
@@ -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
- # # If top level, this will be "<root>".
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, :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 = []
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 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
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
- 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
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 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
98
+ def children
99
+ children = @root_children
100
+ if children.length == 1
101
+ children.first.children
90
102
  else
91
- yield
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
- @root_timing.children.each(&block)
108
+ children.each(&block)
125
109
  end
126
110
 
127
111
  def [](idx)
128
- @root_timing.children[idx]
112
+ children[idx]
129
113
  end
130
114
 
131
115
  def length
132
- @root_timing.children.length
116
+ children.length
133
117
  end
134
118
 
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
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
- def push_partial(partial_name)
151
- @partial_stack.push(partial_name)
152
- end
133
+ private
153
134
 
154
- def pop_partial
155
- @partial_stack.pop
135
+ def monotonic_time
136
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
156
137
  end
157
138
  end
158
139
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class RangeLookup
3
5
  def self.parse(start_markup, end_markup)
4
6
  start_obj = Expression.parse(start_markup)
5
- end_obj = Expression.parse(end_markup)
7
+ end_obj = Expression.parse(end_markup)
6
8
  if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
7
9
  new(start_obj, end_obj)
8
10
  else
@@ -10,14 +12,16 @@ module Liquid
10
12
  end
11
13
  end
12
14
 
15
+ attr_reader :start_obj, :end_obj
16
+
13
17
  def initialize(start_obj, end_obj)
14
18
  @start_obj = start_obj
15
- @end_obj = end_obj
19
+ @end_obj = end_obj
16
20
  end
17
21
 
18
22
  def evaluate(context)
19
23
  start_int = to_integer(context.evaluate(@start_obj))
20
- end_int = to_integer(context.evaluate(@end_obj))
24
+ end_int = to_integer(context.evaluate(@end_obj))
21
25
  start_int..end_int
22
26
  end
23
27
 
@@ -33,5 +37,11 @@ module Liquid
33
37
  Utils.to_integer(input)
34
38
  end
35
39
  end
40
+
41
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
42
+ def children
43
+ [@node.start_obj, @node.end_obj]
44
+ end
45
+ end
36
46
  end
37
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