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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Templates are central to liquid.
3
5
  # Interpretating templates is a two step process. First you compile the
@@ -16,13 +18,11 @@ module Liquid
16
18
  attr_accessor :root
17
19
  attr_reader :resource_limits, :warnings
18
20
 
19
- @@file_system = BlankFileSystem.new
20
-
21
21
  class TagRegistry
22
22
  include Enumerable
23
23
 
24
24
  def initialize
25
- @tags = {}
25
+ @tags = {}
26
26
  @cache = {}
27
27
  end
28
28
 
@@ -50,7 +50,7 @@ module Liquid
50
50
  private
51
51
 
52
52
  def lookup_class(name)
53
- name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
53
+ Object.const_get(name)
54
54
  end
55
55
  end
56
56
 
@@ -61,76 +61,54 @@ module Liquid
61
61
  # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
62
62
  # :warn is the default and will give deprecation warnings when invalid syntax is used.
63
63
  # :strict will enforce correct syntax.
64
- attr_writer :error_mode
65
-
66
- # Sets how strict the taint checker should be.
67
- # :lax is the default, and ignores the taint flag completely
68
- # :warn adds a warning, but does not interrupt the rendering
69
- # :error raises an error when tainted output is used
70
- attr_writer :taint_mode
64
+ attr_accessor :error_mode
65
+ Template.error_mode = :lax
71
66
 
72
67
  attr_accessor :default_exception_renderer
73
68
  Template.default_exception_renderer = lambda do |exception|
74
69
  exception
75
70
  end
76
71
 
77
- def file_system
78
- @@file_system
79
- end
72
+ attr_accessor :file_system
73
+ Template.file_system = BlankFileSystem.new
80
74
 
81
- def file_system=(obj)
82
- @@file_system = obj
83
- end
75
+ attr_accessor :tags
76
+ Template.tags = TagRegistry.new
77
+ private :tags=
84
78
 
85
79
  def register_tag(name, klass)
86
80
  tags[name.to_s] = klass
87
81
  end
88
82
 
89
- def tags
90
- @tags ||= TagRegistry.new
91
- end
92
-
93
- def error_mode
94
- @error_mode ||= :lax
95
- end
96
-
97
- def taint_mode
98
- @taint_mode ||= :lax
99
- end
100
-
101
83
  # Pass a module with filter methods which should be available
102
84
  # to all liquid views. Good for registering the standard library
103
85
  def register_filter(mod)
104
- Strainer.global_filter(mod)
86
+ StrainerFactory.add_global_filter(mod)
105
87
  end
106
88
 
107
- def default_resource_limits
108
- @default_resource_limits ||= {}
109
- end
89
+ attr_accessor :default_resource_limits
90
+ Template.default_resource_limits = {}
91
+ private :default_resource_limits=
110
92
 
111
93
  # creates a new <tt>Template</tt> object from liquid source code
112
94
  # To enable profiling, pass in <tt>profile: true</tt> as an option.
113
95
  # See Liquid::Profiler for more information
114
96
  def parse(source, options = {})
115
- template = Template.new
116
- template.parse(source, options)
97
+ new.parse(source, options)
117
98
  end
118
99
  end
119
100
 
120
101
  def initialize
121
- @rethrow_errors = false
122
- @resource_limits = ResourceLimits.new(self.class.default_resource_limits)
102
+ @rethrow_errors = false
103
+ @resource_limits = ResourceLimits.new(Template.default_resource_limits)
123
104
  end
124
105
 
125
106
  # Parse source code.
126
107
  # Returns self for easy chaining
127
108
  def parse(source, options = {})
128
- @options = options
129
- @profiling = options[:profile]
130
- @line_numbers = options[:line_numbers] || @profiling
131
- parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
132
- @root = Document.parse(tokenize(source), parse_context)
133
- @warnings = parse_context.warnings
109
+ parse_context = configure_options(options)
110
+ tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
111
+ @root = Document.parse(tokenizer, parse_context)
134
112
  self
135
113
  end
136
114
 
@@ -165,19 +143,19 @@ module Liquid
165
143
  # filters and tags and might be useful to integrate liquid more with its host application
166
144
  #
167
145
  def render(*args)
168
- return ''.freeze if @root.nil?
146
+ return '' if @root.nil?
169
147
 
170
148
  context = case args.first
171
149
  when Liquid::Context
172
150
  c = args.shift
173
151
 
174
152
  if @rethrow_errors
175
- c.exception_renderer = ->(e) { raise }
153
+ c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
176
154
  end
177
155
 
178
156
  c
179
157
  when Liquid::Drop
180
- drop = args.shift
158
+ drop = args.shift
181
159
  drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
182
160
  when Hash
183
161
  Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
@@ -187,11 +165,17 @@ module Liquid
187
165
  raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
188
166
  end
189
167
 
168
+ output = nil
169
+
190
170
  case args.last
191
171
  when Hash
192
172
  options = args.pop
173
+ output = options[:output] if options[:output]
174
+ static_registers = context.registers.static
193
175
 
194
- registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
176
+ options[:registers]&.each do |key, register|
177
+ static_registers[key] = register
178
+ end
195
179
 
196
180
  apply_options_to_context(context, options)
197
181
  when Module, Array
@@ -201,13 +185,13 @@ module Liquid
201
185
  # Retrying a render resets resource usage
202
186
  context.resource_limits.reset
203
187
 
188
+ if @profiling && context.profiler.nil?
189
+ @profiler = context.profiler = Liquid::Profiler.new
190
+ end
191
+
204
192
  begin
205
193
  # render the nodelist.
206
- # for performance reasons we get an array back here. join will make a string out of it.
207
- result = with_profiling(context) do
208
- @root.render(context)
209
- end
210
- result.respond_to?(:join) ? result.join : result
194
+ @root.render_to_output_buffer(context, output || +'')
211
195
  rescue Liquid::MemoryError => e
212
196
  context.handle_error(e)
213
197
  ensure
@@ -220,35 +204,31 @@ module Liquid
220
204
  render(*args)
221
205
  end
222
206
 
223
- private
224
-
225
- def tokenize(source)
226
- Tokenizer.new(source, @line_numbers)
207
+ def render_to_output_buffer(context, output)
208
+ render(context, output: output)
227
209
  end
228
210
 
229
- def with_profiling(context)
230
- if @profiling && !context.partial
231
- raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
232
-
233
- @profiler = Profiler.new
234
- @profiler.start
211
+ private
235
212
 
236
- begin
237
- yield
238
- ensure
239
- @profiler.stop
240
- end
241
- else
242
- yield
213
+ def configure_options(options)
214
+ if (profiling = options[:profile])
215
+ raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
243
216
  end
217
+
218
+ @options = options
219
+ @profiling = profiling
220
+ @line_numbers = options[:line_numbers] || @profiling
221
+ parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
222
+ @warnings = parse_context.warnings
223
+ parse_context
244
224
  end
245
225
 
246
226
  def apply_options_to_context(context, options)
247
227
  context.add_filters(options[:filters]) if options[:filters]
248
- context.global_filter = options[:global_filter] if options[:global_filter]
228
+ context.global_filter = options[:global_filter] if options[:global_filter]
249
229
  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
250
- context.strict_variables = options[:strict_variables] if options[:strict_variables]
251
- context.strict_filters = options[:strict_filters] if options[:strict_filters]
230
+ context.strict_variables = options[:strict_variables] if options[:strict_variables]
231
+ context.strict_filters = options[:strict_filters] if options[:strict_filters]
252
232
  end
253
233
  end
254
234
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class TemplateFactory
5
+ def for(_template_name)
6
+ Liquid::Template.new
7
+ end
8
+ end
9
+ end
@@ -1,29 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Tokenizer
3
- attr_reader :line_number
5
+ attr_reader :line_number, :for_liquid_tag
4
6
 
5
- def initialize(source, line_numbers = false)
6
- @source = source
7
- @line_number = line_numbers ? 1 : nil
8
- @tokens = tokenize
7
+ def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
8
+ @source = source.to_s.to_str
9
+ @line_number = line_number || (line_numbers ? 1 : nil)
10
+ @for_liquid_tag = for_liquid_tag
11
+ @tokens = tokenize
9
12
  end
10
13
 
11
14
  def shift
12
- token = @tokens.shift
13
- @line_number += token.count("\n") if @line_number && token
15
+ (token = @tokens.shift) || return
16
+
17
+ if @line_number
18
+ @line_number += @for_liquid_tag ? 1 : token.count("\n")
19
+ end
20
+
14
21
  token
15
22
  end
16
23
 
17
24
  private
18
25
 
19
26
  def tokenize
20
- @source = @source.source if @source.respond_to?(:source)
21
- return [] if @source.to_s.empty?
27
+ return [] if @source.empty?
28
+
29
+ return @source.split("\n") if @for_liquid_tag
22
30
 
23
31
  tokens = @source.split(TemplateParser)
24
32
 
25
33
  # removes the rogue empty element at the beginning of the array
26
- tokens.shift if tokens[0] && tokens[0].empty?
34
+ tokens.shift if tokens[0]&.empty?
27
35
 
28
36
  tokens
29
37
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ module Usage
5
+ def self.increment(name)
6
+ end
7
+ end
8
+ end
data/lib/liquid/utils.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  module Utils
3
5
  def self.slice_collection(collection, from, to)
@@ -10,7 +12,7 @@ module Liquid
10
12
 
11
13
  def self.slice_collection_using_each(collection, from, to)
12
14
  segments = []
13
- index = 0
15
+ index = 0
14
16
 
15
17
  # Maintains Ruby 1.8.7 String#each behaviour on 1.9
16
18
  if collection.is_a?(String)
@@ -50,7 +52,7 @@ module Liquid
50
52
  when Numeric
51
53
  obj
52
54
  when String
53
- (obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i
55
+ /\A-?\d+\.\d+\z/.match?(obj.strip) ? BigDecimal(obj) : obj.to_i
54
56
  else
55
57
  if obj.respond_to?(:to_number)
56
58
  obj.to_number
@@ -69,7 +71,7 @@ module Liquid
69
71
  end
70
72
 
71
73
  case obj
72
- when 'now'.freeze, 'today'.freeze
74
+ when 'now', 'today'
73
75
  Time.now
74
76
  when /\A\d+\z/, Integer
75
77
  Time.at(obj.to_i)
@@ -79,5 +81,13 @@ module Liquid
79
81
  rescue ::ArgumentError
80
82
  nil
81
83
  end
84
+
85
+ def self.to_liquid_value(obj)
86
+ # Enable "obj" to represent itself as a primitive value like integer, string, or boolean
87
+ return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
88
+
89
+ # Otherwise return the object itself
90
+ obj
91
+ end
82
92
  end
83
93
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Holds variables. Variables are only loaded "just in time"
3
5
  # and are not evaluated as part of the render stage
@@ -10,10 +12,10 @@ module Liquid
10
12
  # {{ user | link }}
11
13
  #
12
14
  class Variable
13
- FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
14
- FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
15
- FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
16
- JustTagAttributes = /\A#{TagAttributes}\z/o
15
+ FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
16
+ FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
17
+ FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
18
+ JustTagAttributes = /\A#{TagAttributes}\z/o
17
19
  MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
18
20
 
19
21
  attr_accessor :filters, :name, :line_number
@@ -23,12 +25,12 @@ module Liquid
23
25
  include ParserSwitching
24
26
 
25
27
  def initialize(markup, parse_context)
26
- @markup = markup
27
- @name = nil
28
+ @markup = markup
29
+ @name = nil
28
30
  @parse_context = parse_context
29
- @line_number = parse_context.line_number
31
+ @line_number = parse_context.line_number
30
32
 
31
- parse_with_selected_parser(markup)
33
+ strict_parse_with_error_mode_fallback(markup)
32
34
  end
33
35
 
34
36
  def raw
@@ -43,11 +45,11 @@ module Liquid
43
45
  @filters = []
44
46
  return unless markup =~ MarkupWithQuotedFragment
45
47
 
46
- name_markup = $1
47
- filter_markup = $2
48
- @name = Expression.parse(name_markup)
48
+ name_markup = Regexp.last_match(1)
49
+ filter_markup = Regexp.last_match(2)
50
+ @name = parse_context.parse_expression(name_markup)
49
51
  if filter_markup =~ FilterMarkupRegex
50
- filters = $1.scan(FilterParser)
52
+ filters = Regexp.last_match(1).scan(FilterParser)
51
53
  filters.each do |f|
52
54
  next unless f =~ /\w+/
53
55
  filtername = Regexp.last_match(0)
@@ -61,7 +63,9 @@ module Liquid
61
63
  @filters = []
62
64
  p = Parser.new(markup)
63
65
 
64
- @name = Expression.parse(p.expression)
66
+ return if p.look(:end_of_string)
67
+
68
+ @name = parse_context.parse_expression(p.expression)
65
69
  while p.consume?(:pipe)
66
70
  filtername = p.consume(:id)
67
71
  filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@@ -79,37 +83,57 @@ module Liquid
79
83
  end
80
84
 
81
85
  def render(context)
82
- obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
86
+ obj = context.evaluate(@name)
87
+
88
+ @filters.each do |filter_name, filter_args, filter_kwargs|
83
89
  filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
84
- context.invoke(filter_name, output, *filter_args)
90
+ obj = context.invoke(filter_name, obj, *filter_args)
91
+ end
92
+
93
+ context.apply_global_filter(obj)
94
+ end
95
+
96
+ def render_to_output_buffer(context, output)
97
+ obj = render(context)
98
+
99
+ if obj.is_a?(Array)
100
+ output << obj.join
101
+ elsif obj.nil?
102
+ else
103
+ output << obj.to_s
85
104
  end
86
105
 
87
- obj = context.apply_global_filter(obj)
106
+ output
107
+ end
88
108
 
89
- taint_check(context, obj)
109
+ def disabled?(_context)
110
+ false
111
+ end
90
112
 
91
- obj
113
+ def disabled_tags
114
+ []
92
115
  end
93
116
 
94
117
  private
95
118
 
96
119
  def parse_filter_expressions(filter_name, unparsed_args)
97
- filter_args = []
98
- keyword_args = {}
120
+ filter_args = []
121
+ keyword_args = nil
99
122
  unparsed_args.each do |a|
100
- if matches = a.match(JustTagAttributes)
101
- keyword_args[matches[1]] = Expression.parse(matches[2])
123
+ if (matches = a.match(JustTagAttributes))
124
+ keyword_args ||= {}
125
+ keyword_args[matches[1]] = parse_context.parse_expression(matches[2])
102
126
  else
103
- filter_args << Expression.parse(a)
127
+ filter_args << parse_context.parse_expression(a)
104
128
  end
105
129
  end
106
130
  result = [filter_name, filter_args]
107
- result << keyword_args unless keyword_args.empty?
131
+ result << keyword_args if keyword_args
108
132
  result
109
133
  end
110
134
 
111
135
  def evaluate_filter_expressions(context, filter_args, filter_kwargs)
112
- parsed_args = filter_args.map{ |expr| context.evaluate(expr) }
136
+ parsed_args = filter_args.map { |expr| context.evaluate(expr) }
113
137
  if filter_kwargs
114
138
  parsed_kwargs = {}
115
139
  filter_kwargs.each do |key, expr|
@@ -120,25 +144,6 @@ module Liquid
120
144
  parsed_args
121
145
  end
122
146
 
123
- def taint_check(context, obj)
124
- return unless obj.tainted?
125
- return if Template.taint_mode == :lax
126
-
127
- @markup =~ QuotedFragment
128
- name = Regexp.last_match(0)
129
-
130
- error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
131
- error.line_number = line_number
132
- error.template_name = context.template_name
133
-
134
- case Template.taint_mode
135
- when :warn
136
- context.warnings << error
137
- when :error
138
- raise error
139
- end
140
- end
141
-
142
147
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
143
148
  def children
144
149
  [@node.name] + @node.filters.flatten
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class VariableLookup
3
- SQUARE_BRACKETED = /\A\[(.*)\]\z/m
4
- COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze
5
+ COMMAND_METHODS = ['size', 'first', 'last'].freeze
5
6
 
6
7
  attr_reader :name, :lookups
7
8
 
@@ -13,31 +14,38 @@ module Liquid
13
14
  lookups = markup.scan(VariableParser)
14
15
 
15
16
  name = lookups.shift
16
- if name =~ SQUARE_BRACKETED
17
- name = Expression.parse($1)
17
+ if name&.start_with?('[') && name&.end_with?(']')
18
+ name = Expression.parse(name[1..-2])
18
19
  end
19
20
  @name = name
20
21
 
21
- @lookups = lookups
22
+ @lookups = lookups
22
23
  @command_flags = 0
23
24
 
24
25
  @lookups.each_index do |i|
25
26
  lookup = lookups[i]
26
- if lookup =~ SQUARE_BRACKETED
27
- lookups[i] = Expression.parse($1)
27
+ if lookup&.start_with?('[') && lookup&.end_with?(']')
28
+ lookups[i] = Expression.parse(lookup[1..-2])
28
29
  elsif COMMAND_METHODS.include?(lookup)
29
30
  @command_flags |= 1 << i
30
31
  end
31
32
  end
32
33
  end
33
34
 
35
+ def lookup_command?(lookup_index)
36
+ @command_flags & (1 << lookup_index) != 0
37
+ end
38
+
34
39
  def evaluate(context)
35
- name = context.evaluate(@name)
40
+ name = context.evaluate(@name)
36
41
  object = context.find_variable(name)
37
42
 
38
43
  @lookups.each_index do |i|
39
44
  key = context.evaluate(@lookups[i])
40
45
 
46
+ # Cast "key" to its liquid value to enable it to act as a primitive value
47
+ key = Liquid::Utils.to_liquid_value(key)
48
+
41
49
  # If object is a hash- or array-like object we look for the
42
50
  # presence of the key and if its available we return it
43
51
  if object.respond_to?(:[]) &&
@@ -45,13 +53,13 @@ module Liquid
45
53
  (object.respond_to?(:fetch) && key.is_a?(Integer)))
46
54
 
47
55
  # if its a proc we will replace the entry with the proc
48
- res = context.lookup_and_evaluate(object, key)
56
+ res = context.lookup_and_evaluate(object, key)
49
57
  object = res.to_liquid
50
58
 
51
59
  # Some special cases. If the part wasn't in square brackets and
52
60
  # no key with the same name was found we interpret following calls
53
61
  # as commands and call them on the current object
54
- elsif @command_flags & (1 << i) != 0 && object.respond_to?(key)
62
+ elsif lookup_command?(i) && object.respond_to?(key)
55
63
  object = object.send(key).to_liquid
56
64
 
57
65
  # No key was present with the desired value and it wasn't one of the directly supported
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Liquid
4
- VERSION = "4.0.3".freeze
5
+ VERSION = "5.4.0"
5
6
  end
data/lib/liquid.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright (c) 2005 Tobias Luetke
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining
@@ -21,12 +23,13 @@
21
23
 
22
24
  module Liquid
23
25
  FilterSeparator = /\|/
24
- ArgumentSeparator = ','.freeze
25
- FilterArgumentSeparator = ':'.freeze
26
- VariableAttributeSeparator = '.'.freeze
27
- WhitespaceControl = '-'.freeze
26
+ ArgumentSeparator = ','
27
+ FilterArgumentSeparator = ':'
28
+ VariableAttributeSeparator = '.'
29
+ WhitespaceControl = '-'
28
30
  TagStart = /\{\%/
29
31
  TagEnd = /\%\}/
32
+ TagName = /#|\w+/
30
33
  VariableSignature = /\(?[\w\-\.\[\]]\)?/
31
34
  VariableSegment = /[\w\-]/
32
35
  VariableStart = /\{\{/
@@ -34,12 +37,14 @@ module Liquid
34
37
  VariableIncompleteEnd = /\}\}?/
35
38
  QuotedString = /"[^"]*"|'[^']*'/
36
39
  QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
37
- TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
40
+ TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
38
41
  AnyStartingTag = /#{TagStart}|#{VariableStart}/o
39
42
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
40
43
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
41
44
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
42
45
 
46
+ RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
47
+
43
48
  singleton_class.send(:attr_accessor, :cache_classes)
44
49
  self.cache_classes = true
45
50
  end
@@ -55,11 +60,14 @@ require 'liquid/forloop_drop'
55
60
  require 'liquid/extensions'
56
61
  require 'liquid/errors'
57
62
  require 'liquid/interrupts'
58
- require 'liquid/strainer'
63
+ require 'liquid/strainer_template'
64
+ require 'liquid/strainer_factory'
59
65
  require 'liquid/expression'
60
66
  require 'liquid/context'
61
67
  require 'liquid/parser_switching'
62
68
  require 'liquid/tag'
69
+ require 'liquid/tag/disabler'
70
+ require 'liquid/tag/disableable'
63
71
  require 'liquid/block'
64
72
  require 'liquid/block_body'
65
73
  require 'liquid/document'
@@ -74,6 +82,10 @@ require 'liquid/condition'
74
82
  require 'liquid/utils'
75
83
  require 'liquid/tokenizer'
76
84
  require 'liquid/parse_context'
85
+ require 'liquid/partial_cache'
86
+ require 'liquid/usage'
87
+ require 'liquid/registers'
88
+ require 'liquid/template_factory'
77
89
 
78
90
  # Load all the tags of the standard library
79
91
  #