liquid 3.0.6 → 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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +98 -58
  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 +50 -46
  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_switching.rb +4 -4
  21. data/lib/liquid/profiler/hooks.rb +7 -7
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/range_lookup.rb +16 -1
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +121 -61
  26. data/lib/liquid/strainer.rb +14 -7
  27. data/lib/liquid/tablerowloop_drop.rb +62 -0
  28. data/lib/liquid/tag.rb +9 -8
  29. data/lib/liquid/tags/assign.rb +17 -4
  30. data/lib/liquid/tags/break.rb +0 -3
  31. data/lib/liquid/tags/capture.rb +1 -1
  32. data/lib/liquid/tags/case.rb +19 -12
  33. data/lib/liquid/tags/comment.rb +2 -2
  34. data/lib/liquid/tags/cycle.rb +6 -6
  35. data/lib/liquid/tags/decrement.rb +1 -4
  36. data/lib/liquid/tags/for.rb +95 -75
  37. data/lib/liquid/tags/if.rb +49 -44
  38. data/lib/liquid/tags/ifchanged.rb +0 -2
  39. data/lib/liquid/tags/include.rb +61 -52
  40. data/lib/liquid/tags/raw.rb +32 -4
  41. data/lib/liquid/tags/table_row.rb +12 -30
  42. data/lib/liquid/tags/unless.rb +3 -4
  43. data/lib/liquid/template.rb +42 -54
  44. data/lib/liquid/tokenizer.rb +31 -0
  45. data/lib/liquid/utils.rb +52 -8
  46. data/lib/liquid/variable.rb +46 -45
  47. data/lib/liquid/variable_lookup.rb +7 -5
  48. data/lib/liquid/version.rb +1 -1
  49. data/lib/liquid.rb +9 -7
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +99 -46
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/hash_ordering_test.rb +9 -9
  58. data/test/integration/output_test.rb +26 -27
  59. data/test/integration/parsing_quirks_test.rb +15 -13
  60. data/test/integration/render_profiling_test.rb +20 -20
  61. data/test/integration/security_test.rb +9 -7
  62. data/test/integration/standard_filter_test.rb +179 -40
  63. data/test/integration/tags/break_tag_test.rb +1 -2
  64. data/test/integration/tags/continue_tag_test.rb +0 -1
  65. data/test/integration/tags/for_tag_test.rb +133 -98
  66. data/test/integration/tags/if_else_tag_test.rb +75 -77
  67. data/test/integration/tags/include_tag_test.rb +34 -30
  68. data/test/integration/tags/increment_tag_test.rb +10 -11
  69. data/test/integration/tags/raw_tag_test.rb +7 -1
  70. data/test/integration/tags/standard_tag_test.rb +121 -122
  71. data/test/integration/tags/statements_test.rb +3 -5
  72. data/test/integration/tags/table_row_test.rb +20 -19
  73. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  74. data/test/integration/template_test.rb +190 -49
  75. data/test/integration/trim_mode_test.rb +525 -0
  76. data/test/integration/variable_test.rb +23 -13
  77. data/test/test_helper.rb +33 -5
  78. data/test/unit/block_unit_test.rb +8 -5
  79. data/test/unit/condition_unit_test.rb +86 -77
  80. data/test/unit/context_unit_test.rb +48 -57
  81. data/test/unit/file_system_unit_test.rb +3 -3
  82. data/test/unit/i18n_unit_test.rb +2 -2
  83. data/test/unit/lexer_unit_test.rb +11 -8
  84. data/test/unit/parser_unit_test.rb +2 -2
  85. data/test/unit/regexp_unit_test.rb +1 -1
  86. data/test/unit/strainer_unit_test.rb +80 -1
  87. data/test/unit/tag_unit_test.rb +7 -2
  88. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  89. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  90. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  91. data/test/unit/template_unit_test.rb +14 -5
  92. data/test/unit/tokenizer_unit_test.rb +24 -7
  93. data/test/unit/variable_unit_test.rb +60 -43
  94. metadata +19 -14
  95. data/lib/liquid/module_ex.rb +0 -62
  96. data/lib/liquid/token.rb +0 -18
  97. data/test/unit/module_ex_unit_test.rb +0 -87
  98. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -20,8 +20,13 @@ module Liquid
20
20
  push_block('if'.freeze, markup)
21
21
  end
22
22
 
23
+ def parse(tokens)
24
+ while parse_body(@blocks.last.attachment, tokens)
25
+ end
26
+ end
27
+
23
28
  def nodelist
24
- @blocks.flat_map(&:attachment)
29
+ @blocks.map(&:attachment)
25
30
  end
26
31
 
27
32
  def unknown_tag(tag, markup, tokens)
@@ -36,7 +41,7 @@ module Liquid
36
41
  context.stack do
37
42
  @blocks.each do |block|
38
43
  if block.evaluate(context)
39
- return render_all(block.attachment, context)
44
+ return block.attachment.render(context)
40
45
  end
41
46
  end
42
47
  ''.freeze
@@ -45,61 +50,61 @@ module Liquid
45
50
 
46
51
  private
47
52
 
48
- def push_block(tag, markup)
49
- block = if tag == 'else'.freeze
50
- ElseCondition.new
51
- else
52
- parse_with_selected_parser(markup)
53
- end
54
-
55
- @blocks.push(block)
56
- @nodelist = block.attach(Array.new)
53
+ def push_block(tag, markup)
54
+ block = if tag == 'else'.freeze
55
+ ElseCondition.new
56
+ else
57
+ parse_with_selected_parser(markup)
57
58
  end
58
59
 
59
- def lax_parse(markup)
60
- expressions = markup.scan(ExpressionsAndOperators)
61
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
60
+ @blocks.push(block)
61
+ block.attach(BlockBody.new)
62
+ end
62
63
 
63
- condition = Condition.new($1, $2, $3)
64
+ def lax_parse(markup)
65
+ expressions = markup.scan(ExpressionsAndOperators)
66
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
64
67
 
65
- while not expressions.empty?
66
- operator = expressions.pop.to_s.strip
68
+ condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
67
69
 
68
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
70
+ until expressions.empty?
71
+ operator = expressions.pop.to_s.strip
69
72
 
70
- new_condition = Condition.new($1, $2, $3)
71
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
72
- new_condition.send(operator, condition)
73
- condition = new_condition
74
- end
73
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
75
74
 
76
- condition
75
+ new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
76
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
77
+ new_condition.send(operator, condition)
78
+ condition = new_condition
77
79
  end
78
80
 
79
- def strict_parse(markup)
80
- p = Parser.new(markup)
81
- condition = parse_binary_comparison(p)
82
- p.consume(:end_of_string)
83
- condition
84
- end
81
+ condition
82
+ end
85
83
 
86
- def parse_binary_comparison(p)
87
- condition = parse_comparison(p)
88
- if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
89
- condition.send(op, parse_binary_comparison(p))
90
- end
91
- condition
84
+ def strict_parse(markup)
85
+ p = Parser.new(markup)
86
+ condition = parse_binary_comparison(p)
87
+ p.consume(:end_of_string)
88
+ condition
89
+ end
90
+
91
+ def parse_binary_comparison(p)
92
+ condition = parse_comparison(p)
93
+ if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
94
+ condition.send(op, parse_binary_comparison(p))
92
95
  end
96
+ condition
97
+ end
93
98
 
94
- def parse_comparison(p)
95
- a = p.expression
96
- if op = p.consume?(:comparison)
97
- b = p.expression
98
- Condition.new(a, op, b)
99
- else
100
- Condition.new(a)
101
- end
99
+ def parse_comparison(p)
100
+ a = Expression.parse(p.expression)
101
+ if op = p.consume?(:comparison)
102
+ b = Expression.parse(p.expression)
103
+ Condition.new(a, op, b)
104
+ else
105
+ Condition.new(a)
102
106
  end
107
+ end
103
108
  end
104
109
 
105
110
  Template.register_tag('if'.freeze, If)
@@ -1,9 +1,7 @@
1
1
  module Liquid
2
2
  class Ifchanged < Block
3
-
4
3
  def render(context)
5
4
  context.stack do
6
-
7
5
  output = super
8
6
 
9
7
  if output != context.registers[:ifchanged]
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Include allows templates to relate with other templates
4
3
  #
5
4
  # Simply include another template:
@@ -22,12 +21,15 @@ module Liquid
22
21
 
23
22
  if markup =~ Syntax
24
23
 
25
- @template_name = $1
26
- @variable_name = $3
27
- @attributes = {}
24
+ template_name = $1
25
+ variable_name = $3
26
+
27
+ @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
28
+ @template_name_expr = Expression.parse(template_name)
29
+ @attributes = {}
28
30
 
29
31
  markup.scan(TagAttributes) do |key, value|
30
- @attributes[key] = value
32
+ @attributes[key] = Expression.parse(value)
31
33
  end
32
34
 
33
35
  else
@@ -35,69 +37,76 @@ module Liquid
35
37
  end
36
38
  end
37
39
 
38
- def parse(tokens)
40
+ def parse(_tokens)
39
41
  end
40
42
 
41
43
  def render(context)
42
- partial = load_cached_partial(context)
43
- variable = context[@variable_name || @template_name[1..-2]]
44
+ template_name = context.evaluate(@template_name_expr)
45
+ raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
44
46
 
45
- context.stack do
46
- @attributes.each do |key, value|
47
- context[key] = context[value]
48
- end
47
+ partial = load_cached_partial(template_name, context)
48
+ context_variable_name = template_name.split('/'.freeze).last
49
49
 
50
- context_variable_name = @template_name[1..-2].split('/'.freeze).last
51
- if variable.is_a?(Array)
52
- variable.collect do |var|
53
- context[context_variable_name] = var
50
+ variable = if @variable_name_expr
51
+ context.evaluate(@variable_name_expr)
52
+ else
53
+ context.find_variable(template_name)
54
+ end
55
+
56
+ old_template_name = context.template_name
57
+ old_partial = context.partial
58
+ begin
59
+ context.template_name = template_name
60
+ context.partial = true
61
+ context.stack do
62
+ @attributes.each do |key, value|
63
+ context[key] = context.evaluate(value)
64
+ end
65
+
66
+ if variable.is_a?(Array)
67
+ variable.collect do |var|
68
+ context[context_variable_name] = var
69
+ partial.render(context)
70
+ end
71
+ else
72
+ context[context_variable_name] = variable
54
73
  partial.render(context)
55
74
  end
56
- else
57
- context[context_variable_name] = variable
58
- partial.render(context)
59
75
  end
76
+ ensure
77
+ context.template_name = old_template_name
78
+ context.partial = old_partial
60
79
  end
61
80
  end
62
81
 
63
82
  private
64
- def load_cached_partial(context)
65
- cached_partials = context.registers[:cached_partials] || {}
66
- template_name = context[@template_name]
67
83
 
68
- if cached = cached_partials[template_name]
69
- return cached
70
- end
71
- source = read_template_from_file_system(context)
72
- partial = Liquid::Template.parse(source, pass_options)
73
- cached_partials[template_name] = partial
74
- context.registers[:cached_partials] = cached_partials
75
- partial
76
- end
84
+ alias_method :parse_context, :options
85
+ private :parse_context
77
86
 
78
- def read_template_from_file_system(context)
79
- file_system = context.registers[:file_system] || Liquid::Template.file_system
80
-
81
- # make read_template_file call backwards-compatible.
82
- case file_system.method(:read_template_file).arity
83
- when 1
84
- file_system.read_template_file(context[@template_name])
85
- when 2
86
- file_system.read_template_file(context[@template_name], context)
87
- else
88
- raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
89
- end
90
- end
87
+ def load_cached_partial(template_name, context)
88
+ cached_partials = context.registers[:cached_partials] || {}
91
89
 
92
- def pass_options
93
- dont_pass = @options[:include_options_blacklist]
94
- return {locale: @options[:locale]} if dont_pass == true
95
- opts = @options.merge(included: true, include_options_blacklist: false)
96
- if dont_pass.is_a?(Array)
97
- dont_pass.each {|o| opts.delete(o)}
98
- end
99
- opts
90
+ if cached = cached_partials[template_name]
91
+ return cached
92
+ end
93
+ source = read_template_from_file_system(context)
94
+ begin
95
+ parse_context.partial = true
96
+ partial = Liquid::Template.parse(source, parse_context)
97
+ ensure
98
+ parse_context.partial = false
100
99
  end
100
+ cached_partials[template_name] = partial
101
+ context.registers[:cached_partials] = cached_partials
102
+ partial
103
+ end
104
+
105
+ def read_template_from_file_system(context)
106
+ file_system = context.registers[:file_system] || Liquid::Template.file_system
107
+
108
+ file_system.read_template_file(context.evaluate(@template_name_expr))
109
+ end
101
110
  end
102
111
 
103
112
  Template.register_tag('include'.freeze, Include)
@@ -1,16 +1,44 @@
1
1
  module Liquid
2
2
  class Raw < Block
3
+ Syntax = /\A\s*\z/
3
4
  FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
4
5
 
6
+ def initialize(tag_name, markup, parse_context)
7
+ super
8
+
9
+ ensure_valid_markup(tag_name, markup, parse_context)
10
+ end
11
+
5
12
  def parse(tokens)
6
- @nodelist ||= []
7
- @nodelist.clear
13
+ @body = ''
8
14
  while token = tokens.shift
9
15
  if token =~ FullTokenPossiblyInvalid
10
- @nodelist << $1 if $1 != "".freeze
16
+ @body << $1 if $1 != "".freeze
11
17
  return if block_delimiter == $2
12
18
  end
13
- @nodelist << token if not token.empty?
19
+ @body << token unless token.empty?
20
+ end
21
+
22
+ raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
23
+ end
24
+
25
+ def render(_context)
26
+ @body
27
+ end
28
+
29
+ def nodelist
30
+ [@body]
31
+ end
32
+
33
+ def blank?
34
+ @body.empty?
35
+ end
36
+
37
+ protected
38
+
39
+ def ensure_valid_markup(tag_name, markup, parse_context)
40
+ unless markup =~ Syntax
41
+ raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
14
42
  end
15
43
  end
16
44
  end
@@ -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,50 +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
- 'rindex'.freeze => length - index,
46
- 'rindex0'.freeze => length - index - 1,
47
- 'first'.freeze => (index == 0),
48
- 'last'.freeze => (index == length - 1),
49
- 'col_first'.freeze => (col == 0),
50
- 'col_last'.freeze => (col == cols - 1)
51
- }
52
-
53
-
54
- col += 1
55
38
 
56
- result << "<td class=\"col#{col}\">" << super << '</td>'
39
+ result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
57
40
 
58
- if col == cols and (index != length - 1)
59
- col = 0
60
- row += 1
61
- result << "</tr>\n<tr class=\"row#{row}\">"
41
+ if tablerowloop.col_last && !tablerowloop.last
42
+ result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
62
43
  end
63
44
 
45
+ tablerowloop.send(:increment!)
64
46
  end
65
47
  end
66
48
  result << "</tr>\n"
@@ -1,4 +1,4 @@
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.
@@ -8,17 +8,16 @@ module Liquid
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