liquid 2.6.3 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +272 -26
  3. data/README.md +67 -3
  4. data/lib/liquid/block.rb +62 -94
  5. data/lib/liquid/block_body.rb +255 -0
  6. data/lib/liquid/condition.rb +96 -38
  7. data/lib/liquid/context.rb +172 -154
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +33 -14
  10. data/lib/liquid/errors.rb +56 -10
  11. data/lib/liquid/expression.rb +45 -0
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +27 -14
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +41 -0
  16. data/lib/liquid/interrupts.rb +3 -2
  17. data/lib/liquid/lexer.rb +62 -0
  18. data/lib/liquid/locales/en.yml +29 -0
  19. data/lib/liquid/parse_context.rb +54 -0
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +102 -0
  22. data/lib/liquid/parser_switching.rb +45 -0
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +35 -0
  25. data/lib/liquid/profiler.rb +139 -0
  26. data/lib/liquid/range_lookup.rb +47 -0
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +789 -118
  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 +121 -0
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +49 -10
  36. data/lib/liquid/tags/assign.rb +61 -19
  37. data/lib/liquid/tags/break.rb +14 -4
  38. data/lib/liquid/tags/capture.rb +29 -21
  39. data/lib/liquid/tags/case.rb +80 -31
  40. data/lib/liquid/tags/comment.rb +24 -2
  41. data/lib/liquid/tags/continue.rb +14 -13
  42. data/lib/liquid/tags/cycle.rb +50 -32
  43. data/lib/liquid/tags/decrement.rb +24 -26
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +164 -100
  46. data/lib/liquid/tags/if.rb +105 -44
  47. data/lib/liquid/tags/ifchanged.rb +10 -11
  48. data/lib/liquid/tags/include.rb +85 -65
  49. data/lib/liquid/tags/increment.rb +24 -22
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +88 -0
  54. data/lib/liquid/tags/unless.rb +37 -21
  55. data/lib/liquid/template.rb +124 -46
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +39 -0
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +68 -5
  60. data/lib/liquid/variable.rb +128 -32
  61. data/lib/liquid/variable_lookup.rb +96 -0
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +36 -13
  64. metadata +69 -77
  65. data/lib/extras/liquid_view.rb +0 -51
  66. data/lib/liquid/htmltags.rb +0 -73
  67. data/lib/liquid/module_ex.rb +0 -62
  68. data/lib/liquid/strainer.rb +0 -53
  69. data/test/liquid/assign_test.rb +0 -21
  70. data/test/liquid/block_test.rb +0 -58
  71. data/test/liquid/capture_test.rb +0 -40
  72. data/test/liquid/condition_test.rb +0 -127
  73. data/test/liquid/context_test.rb +0 -478
  74. data/test/liquid/drop_test.rb +0 -180
  75. data/test/liquid/error_handling_test.rb +0 -81
  76. data/test/liquid/file_system_test.rb +0 -29
  77. data/test/liquid/filter_test.rb +0 -125
  78. data/test/liquid/hash_ordering_test.rb +0 -25
  79. data/test/liquid/module_ex_test.rb +0 -87
  80. data/test/liquid/output_test.rb +0 -116
  81. data/test/liquid/parsing_quirks_test.rb +0 -52
  82. data/test/liquid/regexp_test.rb +0 -44
  83. data/test/liquid/security_test.rb +0 -64
  84. data/test/liquid/standard_filter_test.rb +0 -263
  85. data/test/liquid/strainer_test.rb +0 -52
  86. data/test/liquid/tags/break_tag_test.rb +0 -16
  87. data/test/liquid/tags/continue_tag_test.rb +0 -16
  88. data/test/liquid/tags/for_tag_test.rb +0 -297
  89. data/test/liquid/tags/html_tag_test.rb +0 -63
  90. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  91. data/test/liquid/tags/include_tag_test.rb +0 -166
  92. data/test/liquid/tags/increment_tag_test.rb +0 -24
  93. data/test/liquid/tags/raw_tag_test.rb +0 -24
  94. data/test/liquid/tags/standard_tag_test.rb +0 -295
  95. data/test/liquid/tags/statements_test.rb +0 -134
  96. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  97. data/test/liquid/template_test.rb +0 -146
  98. data/test/liquid/variable_test.rb +0 -186
  99. data/test/test_helper.rb +0 -29
  100. /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