liquid 3.0.0 → 4.0.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +130 -62
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +51 -60
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser.rb +1 -1
  21. data/lib/liquid/parser_switching.rb +4 -4
  22. data/lib/liquid/profiler/hooks.rb +7 -7
  23. data/lib/liquid/profiler.rb +18 -19
  24. data/lib/liquid/range_lookup.rb +16 -1
  25. data/lib/liquid/resource_limits.rb +23 -0
  26. data/lib/liquid/standardfilters.rb +121 -61
  27. data/lib/liquid/strainer.rb +32 -29
  28. data/lib/liquid/tablerowloop_drop.rb +62 -0
  29. data/lib/liquid/tag.rb +9 -8
  30. data/lib/liquid/tags/assign.rb +17 -4
  31. data/lib/liquid/tags/break.rb +0 -3
  32. data/lib/liquid/tags/capture.rb +2 -2
  33. data/lib/liquid/tags/case.rb +19 -12
  34. data/lib/liquid/tags/comment.rb +2 -2
  35. data/lib/liquid/tags/cycle.rb +6 -6
  36. data/lib/liquid/tags/decrement.rb +1 -4
  37. data/lib/liquid/tags/for.rb +95 -75
  38. data/lib/liquid/tags/if.rb +48 -43
  39. data/lib/liquid/tags/ifchanged.rb +0 -2
  40. data/lib/liquid/tags/include.rb +61 -52
  41. data/lib/liquid/tags/raw.rb +32 -4
  42. data/lib/liquid/tags/table_row.rb +12 -31
  43. data/lib/liquid/tags/unless.rb +4 -5
  44. data/lib/liquid/template.rb +42 -54
  45. data/lib/liquid/tokenizer.rb +31 -0
  46. data/lib/liquid/utils.rb +52 -8
  47. data/lib/liquid/variable.rb +46 -45
  48. data/lib/liquid/variable_lookup.rb +9 -5
  49. data/lib/liquid/version.rb +1 -1
  50. data/lib/liquid.rb +9 -7
  51. data/test/integration/assign_test.rb +18 -8
  52. data/test/integration/blank_test.rb +14 -14
  53. data/test/integration/capture_test.rb +10 -0
  54. data/test/integration/context_test.rb +2 -2
  55. data/test/integration/document_test.rb +19 -0
  56. data/test/integration/drop_test.rb +42 -40
  57. data/test/integration/error_handling_test.rb +99 -46
  58. data/test/integration/filter_test.rb +72 -19
  59. data/test/integration/hash_ordering_test.rb +9 -9
  60. data/test/integration/output_test.rb +34 -27
  61. data/test/integration/parsing_quirks_test.rb +15 -13
  62. data/test/integration/render_profiling_test.rb +20 -20
  63. data/test/integration/security_test.rb +9 -7
  64. data/test/integration/standard_filter_test.rb +198 -42
  65. data/test/integration/tags/break_tag_test.rb +1 -2
  66. data/test/integration/tags/continue_tag_test.rb +0 -1
  67. data/test/integration/tags/for_tag_test.rb +133 -98
  68. data/test/integration/tags/if_else_tag_test.rb +96 -77
  69. data/test/integration/tags/include_tag_test.rb +34 -30
  70. data/test/integration/tags/increment_tag_test.rb +10 -11
  71. data/test/integration/tags/raw_tag_test.rb +7 -1
  72. data/test/integration/tags/standard_tag_test.rb +121 -122
  73. data/test/integration/tags/statements_test.rb +3 -5
  74. data/test/integration/tags/table_row_test.rb +20 -19
  75. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  76. data/test/integration/template_test.rb +190 -49
  77. data/test/integration/trim_mode_test.rb +525 -0
  78. data/test/integration/variable_test.rb +23 -13
  79. data/test/test_helper.rb +44 -9
  80. data/test/unit/block_unit_test.rb +8 -5
  81. data/test/unit/condition_unit_test.rb +86 -77
  82. data/test/unit/context_unit_test.rb +48 -57
  83. data/test/unit/file_system_unit_test.rb +3 -3
  84. data/test/unit/i18n_unit_test.rb +2 -2
  85. data/test/unit/lexer_unit_test.rb +11 -8
  86. data/test/unit/parser_unit_test.rb +2 -2
  87. data/test/unit/regexp_unit_test.rb +1 -1
  88. data/test/unit/strainer_unit_test.rb +85 -8
  89. data/test/unit/tag_unit_test.rb +7 -2
  90. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  91. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  92. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  93. data/test/unit/template_unit_test.rb +14 -5
  94. data/test/unit/tokenizer_unit_test.rb +24 -7
  95. data/test/unit/variable_unit_test.rb +66 -43
  96. metadata +55 -50
  97. data/lib/liquid/module_ex.rb +0 -62
  98. data/lib/liquid/token.rb +0 -18
  99. data/test/unit/module_ex_unit_test.rb +0 -87
  100. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -6,10 +6,10 @@ module Liquid
6
6
  super
7
7
  if markup =~ Syntax
8
8
  @variable_name = $1
9
- @collection_name = $2
9
+ @collection_name = Expression.parse($2)
10
10
  @attributes = {}
11
11
  markup.scan(TagAttributes) do |key, value|
12
- @attributes[key] = value
12
+ @attributes[key] = Expression.parse(value)
13
13
  end
14
14
  else
15
15
  raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
@@ -17,51 +17,32 @@ module Liquid
17
17
  end
18
18
 
19
19
  def render(context)
20
- collection = context[@collection_name] or return ''.freeze
20
+ collection = context.evaluate(@collection_name) or return ''.freeze
21
21
 
22
- from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
23
- to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
22
+ from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
23
+ to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
24
24
 
25
25
  collection = Utils.slice_collection(collection, from, to)
26
26
 
27
27
  length = collection.length
28
28
 
29
- cols = context[@attributes['cols'.freeze]].to_i
30
-
31
- row = 1
32
- col = 0
29
+ cols = context.evaluate(@attributes['cols'.freeze]).to_i
33
30
 
34
31
  result = "<tr class=\"row1\">\n"
35
32
  context.stack do
33
+ tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
34
+ context['tablerowloop'.freeze] = tablerowloop
36
35
 
37
36
  collection.each_with_index do |item, index|
38
37
  context[@variable_name] = item
39
- context['tablerowloop'.freeze] = {
40
- 'length'.freeze => length,
41
- 'index'.freeze => index + 1,
42
- 'index0'.freeze => index,
43
- 'col'.freeze => col + 1,
44
- 'col0'.freeze => col,
45
- 'index0'.freeze => index,
46
- 'rindex'.freeze => length - index,
47
- 'rindex0'.freeze => length - index - 1,
48
- 'first'.freeze => (index == 0),
49
- 'last'.freeze => (index == length - 1),
50
- 'col_first'.freeze => (col == 0),
51
- 'col_last'.freeze => (col == cols - 1)
52
- }
53
-
54
-
55
- col += 1
56
38
 
57
- result << "<td class=\"col#{col}\">" << super << '</td>'
39
+ result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
58
40
 
59
- if col == cols and (index != length - 1)
60
- col = 0
61
- row += 1
62
- result << "</tr>\n<tr class=\"row#{row}\">"
41
+ if tablerowloop.col_last && !tablerowloop.last
42
+ result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
63
43
  end
64
44
 
45
+ tablerowloop.send(:increment!)
65
46
  end
66
47
  end
67
48
  result << "</tr>\n"
@@ -1,24 +1,23 @@
1
- require File.dirname(__FILE__) + '/if'
1
+ require_relative 'if'
2
2
 
3
3
  module Liquid
4
4
  # Unless is a conditional just like 'if' but works on the inverse logic.
5
5
  #
6
- # {% unless x < 0 %} x is greater than zero {% end %}
6
+ # {% unless x < 0 %} x is greater than zero {% endunless %}
7
7
  #
8
8
  class Unless < If
9
9
  def render(context)
10
10
  context.stack do
11
-
12
11
  # First condition is interpreted backwards ( if not )
13
12
  first_block = @blocks.first
14
13
  unless first_block.evaluate(context)
15
- return render_all(first_block.attachment, context)
14
+ return first_block.attachment.render(context)
16
15
  end
17
16
 
18
17
  # After the first condition unless works just like if
19
18
  @blocks[1..-1].each do |block|
20
19
  if block.evaluate(context)
21
- return render_all(block.attachment, context)
20
+ return block.attachment.render(context)
22
21
  end
23
22
  end
24
23
 
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Templates are central to liquid.
4
3
  # Interpretating templates is a two step process. First you compile the
5
4
  # source code you got. During compile time some extensive error checking is performed.
@@ -14,21 +13,21 @@ module Liquid
14
13
  # template.render('user_name' => 'bob')
15
14
  #
16
15
  class Template
17
- DEFAULT_OPTIONS = {
18
- :locale => I18n.new
19
- }
16
+ attr_accessor :root
17
+ attr_reader :resource_limits, :warnings
20
18
 
21
- attr_accessor :root, :resource_limits
22
19
  @@file_system = BlankFileSystem.new
23
20
 
24
21
  class TagRegistry
22
+ include Enumerable
23
+
25
24
  def initialize
26
- @tags = {}
25
+ @tags = {}
27
26
  @cache = {}
28
27
  end
29
28
 
30
29
  def [](tag_name)
31
- return nil unless @tags.has_key?(tag_name)
30
+ return nil unless @tags.key?(tag_name)
32
31
  return @cache[tag_name] if Liquid.cache_classes
33
32
 
34
33
  lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
@@ -44,6 +43,10 @@ module Liquid
44
43
  @cache.delete(tag_name)
45
44
  end
46
45
 
46
+ def each(&block)
47
+ @tags.each(&block)
48
+ end
49
+
47
50
  private
48
51
 
49
52
  def lookup_class(name)
@@ -66,6 +69,11 @@ module Liquid
66
69
  # :error raises an error when tainted output is used
67
70
  attr_writer :taint_mode
68
71
 
72
+ attr_accessor :default_exception_renderer
73
+ Template.default_exception_renderer = lambda do |exception|
74
+ exception
75
+ end
76
+
69
77
  def file_system
70
78
  @@file_system
71
79
  end
@@ -83,11 +91,11 @@ module Liquid
83
91
  end
84
92
 
85
93
  def error_mode
86
- @error_mode || :lax
94
+ @error_mode ||= :lax
87
95
  end
88
96
 
89
97
  def taint_mode
90
- @taint_mode || :lax
98
+ @taint_mode ||= :lax
91
99
  end
92
100
 
93
101
  # Pass a module with filter methods which should be available
@@ -110,7 +118,8 @@ module Liquid
110
118
  end
111
119
 
112
120
  def initialize
113
- @resource_limits = self.class.default_resource_limits.dup
121
+ @rethrow_errors = false
122
+ @resource_limits = ResourceLimits.new(self.class.default_resource_limits)
114
123
  end
115
124
 
116
125
  # Parse source code.
@@ -119,16 +128,12 @@ module Liquid
119
128
  @options = options
120
129
  @profiling = options[:profile]
121
130
  @line_numbers = options[:line_numbers] || @profiling
122
- @root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
123
- @warnings = nil
131
+ parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
132
+ @root = Document.parse(tokenize(source), parse_context)
133
+ @warnings = parse_context.warnings
124
134
  self
125
135
  end
126
136
 
127
- def warnings
128
- return [] unless @root
129
- @warnings ||= @root.warnings
130
- end
131
-
132
137
  def registers
133
138
  @registers ||= {}
134
139
  end
@@ -167,7 +172,7 @@ module Liquid
167
172
  c = args.shift
168
173
 
169
174
  if @rethrow_errors
170
- c.exception_handler = ->(e) { true }
175
+ c.exception_renderer = ->(e) { raise }
171
176
  end
172
177
 
173
178
  c
@@ -186,27 +191,20 @@ module Liquid
186
191
  when Hash
187
192
  options = args.pop
188
193
 
189
- if options[:registers].is_a?(Hash)
190
- self.registers.merge!(options[:registers])
191
- end
192
-
193
- if options[:filters]
194
- context.add_filters(options[:filters])
195
- end
194
+ registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
196
195
 
197
- if options[:exception_handler]
198
- context.exception_handler = options[:exception_handler]
199
- end
200
- when Module
201
- context.add_filters(args.pop)
202
- when Array
196
+ apply_options_to_context(context, options)
197
+ when Module, Array
203
198
  context.add_filters(args.pop)
204
199
  end
205
200
 
201
+ # Retrying a render resets resource usage
202
+ context.resource_limits.reset
203
+
206
204
  begin
207
205
  # render the nodelist.
208
206
  # for performance reasons we get an array back here. join will make a string out of it.
209
- result = with_profiling do
207
+ result = with_profiling(context) do
210
208
  @root.render(context)
211
209
  end
212
210
  result.respond_to?(:join) ? result.join : result
@@ -224,32 +222,14 @@ module Liquid
224
222
 
225
223
  private
226
224
 
227
- # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
228
225
  def tokenize(source)
229
- source = source.source if source.respond_to?(:source)
230
- return [] if source.to_s.empty?
231
-
232
- tokens = calculate_line_numbers(source.split(TemplateParser))
233
-
234
- # removes the rogue empty element at the beginning of the array
235
- tokens.shift if tokens[0] and tokens[0].empty?
236
-
237
- tokens
226
+ Tokenizer.new(source, @line_numbers)
238
227
  end
239
228
 
240
- def calculate_line_numbers(raw_tokens)
241
- return raw_tokens unless @line_numbers
229
+ def with_profiling(context)
230
+ if @profiling && !context.partial
231
+ raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
242
232
 
243
- current_line = 1
244
- raw_tokens.map do |token|
245
- Token.new(token, current_line).tap do
246
- current_line += token.count("\n")
247
- end
248
- end
249
- end
250
-
251
- def with_profiling
252
- if @profiling && !@options[:included]
253
233
  @profiler = Profiler.new
254
234
  @profiler.start
255
235
 
@@ -262,5 +242,13 @@ module Liquid
262
242
  yield
263
243
  end
264
244
  end
245
+
246
+ def apply_options_to_context(context, options)
247
+ context.add_filters(options[:filters]) if options[:filters]
248
+ context.global_filter = options[:global_filter] if options[:global_filter]
249
+ 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]
252
+ end
265
253
  end
266
254
  end
@@ -0,0 +1,31 @@
1
+ module Liquid
2
+ class Tokenizer
3
+ attr_reader :line_number
4
+
5
+ def initialize(source, line_numbers = false)
6
+ @source = source
7
+ @line_number = line_numbers ? 1 : nil
8
+ @tokens = tokenize
9
+ end
10
+
11
+ def shift
12
+ token = @tokens.shift
13
+ @line_number += token.count("\n") if @line_number && token
14
+ token
15
+ end
16
+
17
+ private
18
+
19
+ def tokenize
20
+ @source = @source.source if @source.respond_to?(:source)
21
+ return [] if @source.to_s.empty?
22
+
23
+ tokens = @source.split(TemplateParser)
24
+
25
+ # removes the rogue empty element at the beginning of the array
26
+ tokens.shift if tokens[0] && tokens[0].empty?
27
+
28
+ tokens
29
+ end
30
+ end
31
+ end
data/lib/liquid/utils.rb CHANGED
@@ -1,27 +1,24 @@
1
1
  module Liquid
2
2
  module Utils
3
-
4
3
  def self.slice_collection(collection, from, to)
5
- if (from != 0 || to != nil) && collection.respond_to?(:load_slice)
4
+ if (from != 0 || !to.nil?) && collection.respond_to?(:load_slice)
6
5
  collection.load_slice(from, to)
7
6
  else
8
7
  slice_collection_using_each(collection, from, to)
9
8
  end
10
9
  end
11
10
 
12
- def self.non_blank_string?(collection)
13
- collection.is_a?(String) && collection != ''.freeze
14
- end
15
-
16
11
  def self.slice_collection_using_each(collection, from, to)
17
12
  segments = []
18
13
  index = 0
19
14
 
20
15
  # Maintains Ruby 1.8.7 String#each behaviour on 1.9
21
- return [collection] if non_blank_string?(collection)
16
+ if collection.is_a?(String)
17
+ return collection.empty? ? [] : [collection]
18
+ end
19
+ return [] unless collection.respond_to?(:each)
22
20
 
23
21
  collection.each do |item|
24
-
25
22
  if to && to <= index
26
23
  break
27
24
  end
@@ -35,5 +32,52 @@ module Liquid
35
32
 
36
33
  segments
37
34
  end
35
+
36
+ def self.to_integer(num)
37
+ return num if num.is_a?(Integer)
38
+ num = num.to_s
39
+ begin
40
+ Integer(num)
41
+ rescue ::ArgumentError
42
+ raise Liquid::ArgumentError, "invalid integer"
43
+ end
44
+ end
45
+
46
+ def self.to_number(obj)
47
+ case obj
48
+ when Float
49
+ BigDecimal.new(obj.to_s)
50
+ when Numeric
51
+ obj
52
+ when String
53
+ (obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
54
+ else
55
+ if obj.respond_to?(:to_number)
56
+ obj.to_number
57
+ else
58
+ 0
59
+ end
60
+ end
61
+ end
62
+
63
+ def self.to_date(obj)
64
+ return obj if obj.respond_to?(:strftime)
65
+
66
+ if obj.is_a?(String)
67
+ return nil if obj.empty?
68
+ obj = obj.downcase
69
+ end
70
+
71
+ case obj
72
+ when 'now'.freeze, 'today'.freeze
73
+ Time.now
74
+ when /\A\d+\z/, Integer
75
+ Time.at(obj.to_i)
76
+ when String
77
+ Time.parse(obj)
78
+ end
79
+ rescue ::ArgumentError
80
+ nil
81
+ end
38
82
  end
39
83
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Holds variables. Variables are only loaded "just in time"
4
3
  # and are not evaluated as part of the render stage
5
4
  #
@@ -12,15 +11,16 @@ module Liquid
12
11
  #
13
12
  class Variable
14
13
  FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
15
- EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
16
- attr_accessor :filters, :name, :warnings
17
- attr_accessor :line_number
14
+ attr_accessor :filters, :name, :line_number
15
+ attr_reader :parse_context
16
+ alias_method :options, :parse_context
18
17
  include ParserSwitching
19
18
 
20
- def initialize(markup, options = {})
19
+ def initialize(markup, parse_context)
21
20
  @markup = markup
22
21
  @name = nil
23
- @options = options || {}
22
+ @parse_context = parse_context
23
+ @line_number = parse_context.line_number
24
24
 
25
25
  parse_with_selected_parser(markup)
26
26
  end
@@ -35,35 +35,27 @@ module Liquid
35
35
 
36
36
  def lax_parse(markup)
37
37
  @filters = []
38
- if markup =~ /(#{QuotedFragment})(.*)/om
39
- name_markup = $1
40
- filter_markup = $2
41
- @name = Expression.parse(name_markup)
42
- if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
43
- filters = $1.scan(FilterParser)
44
- filters.each do |f|
45
- if f =~ /\w+/
46
- filtername = Regexp.last_match(0)
47
- filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
48
- @filters << parse_filter_expressions(filtername, filterargs)
49
- end
50
- end
38
+ return unless markup =~ /(#{QuotedFragment})(.*)/om
39
+
40
+ name_markup = $1
41
+ filter_markup = $2
42
+ @name = Expression.parse(name_markup)
43
+ if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
44
+ filters = $1.scan(FilterParser)
45
+ filters.each do |f|
46
+ next unless f =~ /\w+/
47
+ filtername = Regexp.last_match(0)
48
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
49
+ @filters << parse_filter_expressions(filtername, filterargs)
51
50
  end
52
51
  end
53
52
  end
54
53
 
55
54
  def strict_parse(markup)
56
- # Very simple valid cases
57
- if markup =~ EasyParse
58
- @name = Expression.parse($1)
59
- @filters = []
60
- return
61
- end
62
-
63
55
  @filters = []
64
56
  p = Parser.new(markup)
65
- # Could be just filters with no input
66
- @name = p.look(:pipe) ? nil : Expression.parse(p.expression)
57
+
58
+ @name = Expression.parse(p.expression)
67
59
  while p.consume?(:pipe)
68
60
  filtername = p.consume(:id)
69
61
  filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@@ -76,17 +68,21 @@ module Liquid
76
68
  # first argument
77
69
  filterargs = [p.argument]
78
70
  # followed by comma separated others
79
- while p.consume?(:comma)
80
- filterargs << p.argument
81
- end
71
+ filterargs << p.argument while p.consume?(:comma)
82
72
  filterargs
83
73
  end
84
74
 
85
75
  def render(context)
86
- @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
76
+ obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
87
77
  filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
88
- output = context.invoke(filter_name, output, *filter_args)
89
- end.tap{ |obj| taint_check(obj) }
78
+ context.invoke(filter_name, output, *filter_args)
79
+ end
80
+
81
+ obj = context.apply_global_filter(obj)
82
+
83
+ taint_check(context, obj)
84
+
85
+ obj
90
86
  end
91
87
 
92
88
  private
@@ -118,17 +114,22 @@ module Liquid
118
114
  parsed_args
119
115
  end
120
116
 
121
- def taint_check(obj)
122
- if obj.tainted?
123
- @markup =~ QuotedFragment
124
- name = Regexp.last_match(0)
125
- case Template.taint_mode
126
- when :warn
127
- @warnings ||= []
128
- @warnings << "variable '#{name}' is tainted and was not escaped"
129
- when :error
130
- raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped"
131
- end
117
+ def taint_check(context, obj)
118
+ return unless obj.tainted?
119
+ return if Template.taint_mode == :lax
120
+
121
+ @markup =~ QuotedFragment
122
+ name = Regexp.last_match(0)
123
+
124
+ error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
125
+ error.line_number = line_number
126
+ error.template_name = context.template_name
127
+
128
+ case Template.taint_mode
129
+ when :warn
130
+ context.warnings << error
131
+ when :error
132
+ raise error
132
133
  end
133
134
  end
134
135
  end
@@ -3,6 +3,8 @@ module Liquid
3
3
  SQUARE_BRACKETED = /\A\[(.*)\]\z/m
4
4
  COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
5
5
 
6
+ attr_reader :name, :lookups
7
+
6
8
  def self.parse(markup)
7
9
  new(markup)
8
10
  end
@@ -39,8 +41,8 @@ module Liquid
39
41
  # If object is a hash- or array-like object we look for the
40
42
  # presence of the key and if its available we return it
41
43
  if object.respond_to?(:[]) &&
42
- ((object.respond_to?(:has_key?) && object.has_key?(key)) ||
43
- (object.respond_to?(:fetch) && key.is_a?(Integer)))
44
+ ((object.respond_to?(:key?) && object.key?(key)) ||
45
+ (object.respond_to?(:fetch) && key.is_a?(Integer)))
44
46
 
45
47
  # if its a proc we will replace the entry with the proc
46
48
  res = context.lookup_and_evaluate(object, key)
@@ -53,9 +55,11 @@ module Liquid
53
55
  object = object.send(key).to_liquid
54
56
 
55
57
  # No key was present with the desired value and it wasn't one of the directly supported
56
- # keywords either. The only thing we got left is to return nil
58
+ # keywords either. The only thing we got left is to return nil or
59
+ # raise an exception if `strict_variables` option is set to true
57
60
  else
58
- return nil
61
+ return nil unless context.strict_variables
62
+ raise Liquid::UndefinedVariable, "undefined variable #{key}"
59
63
  end
60
64
 
61
65
  # If we are dealing with a drop here we have to
@@ -66,7 +70,7 @@ module Liquid
66
70
  end
67
71
 
68
72
  def ==(other)
69
- self.class == other.class && self.state == other.state
73
+ self.class == other.class && state == other.state
70
74
  end
71
75
 
72
76
  protected
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Liquid
3
- VERSION = "3.0.0"
3
+ VERSION = "4.0.0"
4
4
  end
data/lib/liquid.rb CHANGED
@@ -24,6 +24,7 @@ module Liquid
24
24
  ArgumentSeparator = ','.freeze
25
25
  FilterArgumentSeparator = ':'.freeze
26
26
  VariableAttributeSeparator = '.'.freeze
27
+ WhitespaceControl = '-'.freeze
27
28
  TagStart = /\{\%/
28
29
  TagEnd = /\%\}/
29
30
  VariableSignature = /\(?[\w\-\.\[\]]\)?/
@@ -34,7 +35,7 @@ module Liquid
34
35
  QuotedString = /"[^"]*"|'[^']*'/
35
36
  QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
36
37
  TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
37
- AnyStartingTag = /\{\{|\{\%/
38
+ AnyStartingTag = /#{TagStart}|#{VariableStart}/o
38
39
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
39
40
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
40
41
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
@@ -48,6 +49,8 @@ require 'liquid/lexer'
48
49
  require 'liquid/parser'
49
50
  require 'liquid/i18n'
50
51
  require 'liquid/drop'
52
+ require 'liquid/tablerowloop_drop'
53
+ require 'liquid/forloop_drop'
51
54
  require 'liquid/extensions'
52
55
  require 'liquid/errors'
53
56
  require 'liquid/interrupts'
@@ -57,21 +60,20 @@ require 'liquid/context'
57
60
  require 'liquid/parser_switching'
58
61
  require 'liquid/tag'
59
62
  require 'liquid/block'
63
+ require 'liquid/block_body'
60
64
  require 'liquid/document'
61
65
  require 'liquid/variable'
62
66
  require 'liquid/variable_lookup'
63
67
  require 'liquid/range_lookup'
64
68
  require 'liquid/file_system'
69
+ require 'liquid/resource_limits'
65
70
  require 'liquid/template'
66
71
  require 'liquid/standardfilters'
67
72
  require 'liquid/condition'
68
- require 'liquid/module_ex'
69
73
  require 'liquid/utils'
70
- require 'liquid/token'
74
+ require 'liquid/tokenizer'
75
+ require 'liquid/parse_context'
71
76
 
72
77
  # Load all the tags of the standard library
73
78
  #
74
- Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
75
-
76
- require 'liquid/profiler'
77
- require 'liquid/profiler/hooks'
79
+ Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }