liquid 2.6.1 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +194 -29
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/README.md +60 -2
  5. data/lib/liquid.rb +25 -14
  6. data/lib/liquid/block.rb +47 -96
  7. data/lib/liquid/block_body.rb +143 -0
  8. data/lib/liquid/condition.rb +70 -39
  9. data/lib/liquid/context.rb +116 -157
  10. data/lib/liquid/document.rb +19 -9
  11. data/lib/liquid/drop.rb +31 -14
  12. data/lib/liquid/errors.rb +54 -10
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +19 -7
  15. data/lib/liquid/file_system.rb +25 -14
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +2 -3
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +311 -77
  30. data/lib/liquid/strainer.rb +39 -26
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +28 -11
  33. data/lib/liquid/tags/assign.rb +34 -10
  34. data/lib/liquid/tags/break.rb +1 -4
  35. data/lib/liquid/tags/capture.rb +11 -9
  36. data/lib/liquid/tags/case.rb +37 -22
  37. data/lib/liquid/tags/comment.rb +10 -3
  38. data/lib/liquid/tags/continue.rb +1 -4
  39. data/lib/liquid/tags/cycle.rb +20 -14
  40. data/lib/liquid/tags/decrement.rb +4 -8
  41. data/lib/liquid/tags/for.rb +121 -60
  42. data/lib/liquid/tags/if.rb +73 -30
  43. data/lib/liquid/tags/ifchanged.rb +3 -5
  44. data/lib/liquid/tags/include.rb +77 -46
  45. data/lib/liquid/tags/increment.rb +4 -8
  46. data/lib/liquid/tags/raw.rb +35 -10
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +6 -9
  49. data/lib/liquid/template.rb +130 -32
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/truffle.rb +5 -0
  52. data/lib/liquid/utils.rb +57 -4
  53. data/lib/liquid/variable.rb +121 -30
  54. data/lib/liquid/variable_lookup.rb +88 -0
  55. data/lib/liquid/version.rb +2 -1
  56. data/test/fixtures/en_locale.yml +9 -0
  57. data/test/integration/assign_test.rb +48 -0
  58. data/test/integration/blank_test.rb +106 -0
  59. data/test/integration/block_test.rb +12 -0
  60. data/test/{liquid → integration}/capture_test.rb +13 -3
  61. data/test/integration/context_test.rb +32 -0
  62. data/test/integration/document_test.rb +19 -0
  63. data/test/integration/drop_test.rb +273 -0
  64. data/test/integration/error_handling_test.rb +260 -0
  65. data/test/integration/filter_test.rb +178 -0
  66. data/test/integration/hash_ordering_test.rb +23 -0
  67. data/test/integration/output_test.rb +123 -0
  68. data/test/integration/parse_tree_visitor_test.rb +247 -0
  69. data/test/integration/parsing_quirks_test.rb +122 -0
  70. data/test/integration/render_profiling_test.rb +154 -0
  71. data/test/integration/security_test.rb +80 -0
  72. data/test/integration/standard_filter_test.rb +776 -0
  73. data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
  74. data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
  75. data/test/integration/tags/for_tag_test.rb +410 -0
  76. data/test/integration/tags/if_else_tag_test.rb +188 -0
  77. data/test/integration/tags/include_tag_test.rb +253 -0
  78. data/test/integration/tags/increment_tag_test.rb +23 -0
  79. data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
  80. data/test/integration/tags/standard_tag_test.rb +296 -0
  81. data/test/integration/tags/statements_test.rb +111 -0
  82. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
  83. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  84. data/test/integration/template_test.rb +332 -0
  85. data/test/integration/trim_mode_test.rb +529 -0
  86. data/test/integration/variable_test.rb +96 -0
  87. data/test/test_helper.rb +106 -19
  88. data/test/truffle/truffle_test.rb +9 -0
  89. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
  90. data/test/unit/condition_unit_test.rb +166 -0
  91. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
  92. data/test/unit/file_system_unit_test.rb +35 -0
  93. data/test/unit/i18n_unit_test.rb +37 -0
  94. data/test/unit/lexer_unit_test.rb +51 -0
  95. data/test/unit/parser_unit_test.rb +82 -0
  96. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
  97. data/test/unit/strainer_unit_test.rb +164 -0
  98. data/test/unit/tag_unit_test.rb +21 -0
  99. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  100. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  101. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  102. data/test/unit/template_unit_test.rb +78 -0
  103. data/test/unit/tokenizer_unit_test.rb +55 -0
  104. data/test/unit/variable_unit_test.rb +162 -0
  105. metadata +157 -77
  106. data/lib/extras/liquid_view.rb +0 -51
  107. data/lib/liquid/htmltags.rb +0 -74
  108. data/lib/liquid/module_ex.rb +0 -62
  109. data/test/liquid/assign_test.rb +0 -21
  110. data/test/liquid/condition_test.rb +0 -127
  111. data/test/liquid/drop_test.rb +0 -180
  112. data/test/liquid/error_handling_test.rb +0 -81
  113. data/test/liquid/file_system_test.rb +0 -29
  114. data/test/liquid/filter_test.rb +0 -125
  115. data/test/liquid/hash_ordering_test.rb +0 -25
  116. data/test/liquid/module_ex_test.rb +0 -87
  117. data/test/liquid/output_test.rb +0 -116
  118. data/test/liquid/parsing_quirks_test.rb +0 -52
  119. data/test/liquid/security_test.rb +0 -64
  120. data/test/liquid/standard_filter_test.rb +0 -251
  121. data/test/liquid/strainer_test.rb +0 -52
  122. data/test/liquid/tags/for_tag_test.rb +0 -297
  123. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  124. data/test/liquid/tags/include_tag_test.rb +0 -166
  125. data/test/liquid/tags/increment_tag_test.rb +0 -24
  126. data/test/liquid/tags/standard_tag_test.rb +0 -295
  127. data/test/liquid/tags/statements_test.rb +0 -134
  128. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  129. data/test/liquid/template_test.rb +0 -146
  130. data/test/liquid/variable_test.rb +0 -186
@@ -1,20 +1,18 @@
1
1
  module Liquid
2
2
  class Ifchanged < Block
3
-
4
3
  def render(context)
5
4
  context.stack do
6
-
7
- output = render_all(@nodelist, context)
5
+ output = super
8
6
 
9
7
  if output != context.registers[:ifchanged]
10
8
  context.registers[:ifchanged] = output
11
9
  output
12
10
  else
13
- ''
11
+ ''.freeze
14
12
  end
15
13
  end
16
14
  end
17
15
  end
18
16
 
19
- Template.register_tag('ifchanged', Ifchanged)
17
+ Template.register_tag('ifchanged'.freeze, Ifchanged)
20
18
  end
@@ -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:
@@ -17,77 +16,109 @@ module Liquid
17
16
  class Include < Tag
18
17
  Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
19
18
 
20
- def initialize(tag_name, markup, tokens)
19
+ attr_reader :template_name_expr, :variable_name_expr, :attributes
20
+
21
+ def initialize(tag_name, markup, options)
22
+ super
23
+
21
24
  if markup =~ Syntax
22
25
 
23
- @template_name = $1
24
- @variable_name = $3
25
- @attributes = {}
26
+ template_name = $1
27
+ variable_name = $3
28
+
29
+ @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
30
+ @template_name_expr = Expression.parse(template_name)
31
+ @attributes = {}
26
32
 
27
33
  markup.scan(TagAttributes) do |key, value|
28
- @attributes[key] = value
34
+ @attributes[key] = Expression.parse(value)
29
35
  end
30
36
 
31
37
  else
32
- raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
38
+ raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
33
39
  end
34
-
35
- super
36
40
  end
37
41
 
38
- def parse(tokens)
42
+ def parse(_tokens)
39
43
  end
40
44
 
41
45
  def render(context)
42
- partial = load_cached_partial(context)
43
- variable = context[@variable_name || @template_name[1..-2]]
46
+ template_name = context.evaluate(@template_name_expr)
47
+ raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
44
48
 
45
- context.stack do
46
- @attributes.each do |key, value|
47
- context[key] = context[value]
48
- end
49
+ partial = load_cached_partial(template_name, context)
50
+ context_variable_name = template_name.split('/'.freeze).last
51
+
52
+ variable = if @variable_name_expr
53
+ context.evaluate(@variable_name_expr)
54
+ else
55
+ context.find_variable(template_name, raise_on_not_found: false)
56
+ end
49
57
 
50
- if variable.is_a?(Array)
51
- variable.collect do |var|
52
- context[@template_name[1..-2]] = var
58
+ old_template_name = context.template_name
59
+ old_partial = context.partial
60
+ begin
61
+ context.template_name = template_name
62
+ context.partial = true
63
+ context.stack do
64
+ @attributes.each do |key, value|
65
+ context[key] = context.evaluate(value)
66
+ end
67
+
68
+ if variable.is_a?(Array)
69
+ variable.collect do |var|
70
+ context[context_variable_name] = var
71
+ partial.render(context)
72
+ end
73
+ else
74
+ context[context_variable_name] = variable
53
75
  partial.render(context)
54
76
  end
55
- else
56
- context[@template_name[1..-2]] = variable
57
- partial.render(context)
58
77
  end
78
+ ensure
79
+ context.template_name = old_template_name
80
+ context.partial = old_partial
59
81
  end
60
82
  end
61
83
 
62
84
  private
63
- def load_cached_partial(context)
64
- cached_partials = context.registers[:cached_partials] || {}
65
- template_name = context[@template_name]
66
85
 
67
- if cached = cached_partials[template_name]
68
- return cached
69
- end
70
- source = read_template_from_file_system(context)
71
- partial = Liquid::Template.parse(source)
72
- cached_partials[template_name] = partial
73
- context.registers[:cached_partials] = cached_partials
74
- partial
86
+ alias_method :parse_context, :options
87
+ private :parse_context
88
+
89
+ def load_cached_partial(template_name, context)
90
+ cached_partials = context.registers[:cached_partials] || {}
91
+
92
+ if cached = cached_partials[template_name]
93
+ return cached
94
+ end
95
+ source = read_template_from_file_system(context)
96
+ begin
97
+ parse_context.partial = true
98
+ partial = Liquid::Template.parse(source, parse_context)
99
+ ensure
100
+ parse_context.partial = false
75
101
  end
102
+ cached_partials[template_name] = partial
103
+ context.registers[:cached_partials] = cached_partials
104
+ partial
105
+ end
76
106
 
77
- def read_template_from_file_system(context)
78
- file_system = context.registers[:file_system] || Liquid::Template.file_system
79
-
80
- # make read_template_file call backwards-compatible.
81
- case file_system.method(:read_template_file).arity
82
- when 1
83
- file_system.read_template_file(context[@template_name])
84
- when 2
85
- file_system.read_template_file(context[@template_name], context)
86
- else
87
- raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
88
- end
107
+ def read_template_from_file_system(context)
108
+ file_system = context.registers[:file_system] || Liquid::Template.file_system
109
+
110
+ file_system.read_template_file(context.evaluate(@template_name_expr))
111
+ end
112
+
113
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
114
+ def children
115
+ [
116
+ @node.template_name_expr,
117
+ @node.variable_name_expr
118
+ ] + @node.attributes.values
89
119
  end
120
+ end
90
121
  end
91
122
 
92
- Template.register_tag('include', Include)
123
+ Template.register_tag('include'.freeze, Include)
93
124
  end
@@ -1,12 +1,11 @@
1
1
  module Liquid
2
-
3
2
  # increment is used in a place where one needs to insert a counter
4
3
  # into a template, and needs the counter to survive across
5
4
  # multiple instantiations of the template.
6
5
  # (To achieve the survival, the application must keep the context)
7
6
  #
8
7
  # if the variable does not exist, it is created with value 0.
9
-
8
+ #
10
9
  # Hello: {% increment variable %}
11
10
  #
12
11
  # gives you:
@@ -16,10 +15,9 @@ module Liquid
16
15
  # Hello: 2
17
16
  #
18
17
  class Increment < Tag
19
- def initialize(tag_name, markup, tokens)
20
- @variable = markup.strip
21
-
18
+ def initialize(tag_name, markup, options)
22
19
  super
20
+ @variable = markup.strip
23
21
  end
24
22
 
25
23
  def render(context)
@@ -27,9 +25,7 @@ module Liquid
27
25
  context.environments.first[@variable] = value + 1
28
26
  value.to_s
29
27
  end
30
-
31
- private
32
28
  end
33
29
 
34
- Template.register_tag('increment', Increment)
30
+ Template.register_tag('increment'.freeze, Increment)
35
31
  end
@@ -1,22 +1,47 @@
1
1
  module Liquid
2
2
  class Raw < Block
3
- FullTokenPossiblyInvalid = /^(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
3
+ Syntax = /\A\s*\z/
4
+ FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
5
+
6
+ def initialize(tag_name, markup, parse_context)
7
+ super
8
+
9
+ ensure_valid_markup(tag_name, markup, parse_context)
10
+ end
4
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 != ""
11
- if block_delimiter == $2
12
- end_tag
13
- return
14
- end
16
+ @body << $1 if $1 != "".freeze
17
+ return if block_delimiter == $2
15
18
  end
16
- @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))
17
42
  end
18
43
  end
19
44
  end
20
45
 
21
- Template.register_tag('raw', Raw)
46
+ Template.register_tag('raw'.freeze, Raw)
22
47
  end
@@ -0,0 +1,62 @@
1
+ module Liquid
2
+ class TableRow < Block
3
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
4
+
5
+ attr_reader :variable_name, :collection_name, :attributes
6
+
7
+ def initialize(tag_name, markup, options)
8
+ super
9
+ if markup =~ Syntax
10
+ @variable_name = $1
11
+ @collection_name = Expression.parse($2)
12
+ @attributes = {}
13
+ markup.scan(TagAttributes) do |key, value|
14
+ @attributes[key] = Expression.parse(value)
15
+ end
16
+ else
17
+ raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
18
+ end
19
+ end
20
+
21
+ def render(context)
22
+ collection = context.evaluate(@collection_name) or return ''.freeze
23
+
24
+ from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
25
+ to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
26
+
27
+ collection = Utils.slice_collection(collection, from, to)
28
+
29
+ length = collection.length
30
+
31
+ cols = context.evaluate(@attributes['cols'.freeze]).to_i
32
+
33
+ result = "<tr class=\"row1\">\n"
34
+ context.stack do
35
+ tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
36
+ context['tablerowloop'.freeze] = tablerowloop
37
+
38
+ collection.each do |item|
39
+ context[@variable_name] = item
40
+
41
+ result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
42
+
43
+ if tablerowloop.col_last && !tablerowloop.last
44
+ result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
45
+ end
46
+
47
+ tablerowloop.send(:increment!)
48
+ end
49
+ end
50
+ result << "</tr>\n"
51
+ result
52
+ end
53
+
54
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
55
+ def children
56
+ super + @node.attributes.values + [@node.collection_name]
57
+ end
58
+ end
59
+ end
60
+
61
+ Template.register_tag('tablerow'.freeze, TableRow)
62
+ end
@@ -1,33 +1,30 @@
1
- require File.dirname(__FILE__) + '/if'
1
+ require_relative 'if'
2
2
 
3
3
  module Liquid
4
-
5
4
  # Unless is a conditional just like 'if' but works on the inverse logic.
6
5
  #
7
- # {% unless x < 0 %} x is greater than zero {% end %}
6
+ # {% unless x < 0 %} x is greater than zero {% endunless %}
8
7
  #
9
8
  class Unless < If
10
9
  def render(context)
11
10
  context.stack do
12
-
13
11
  # First condition is interpreted backwards ( if not )
14
12
  first_block = @blocks.first
15
13
  unless first_block.evaluate(context)
16
- return render_all(first_block.attachment, context)
14
+ return first_block.attachment.render(context)
17
15
  end
18
16
 
19
17
  # After the first condition unless works just like if
20
18
  @blocks[1..-1].each do |block|
21
19
  if block.evaluate(context)
22
- return render_all(block.attachment, context)
20
+ return block.attachment.render(context)
23
21
  end
24
22
  end
25
23
 
26
- ''
24
+ ''.freeze
27
25
  end
28
26
  end
29
27
  end
30
28
 
31
-
32
- Template.register_tag('unless', Unless)
29
+ Template.register_tag('unless'.freeze, Unless)
33
30
  end
@@ -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,10 +13,67 @@ module Liquid
14
13
  # template.render('user_name' => 'bob')
15
14
  #
16
15
  class Template
17
- attr_accessor :root, :resource_limits
16
+ attr_accessor :root
17
+ attr_reader :resource_limits, :warnings
18
+
18
19
  @@file_system = BlankFileSystem.new
19
20
 
21
+ class TagRegistry
22
+ include Enumerable
23
+
24
+ def initialize
25
+ @tags = {}
26
+ @cache = {}
27
+ end
28
+
29
+ def [](tag_name)
30
+ return nil unless @tags.key?(tag_name)
31
+ return @cache[tag_name] if Liquid.cache_classes
32
+
33
+ lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
34
+ end
35
+
36
+ def []=(tag_name, klass)
37
+ @tags[tag_name] = klass.name
38
+ @cache[tag_name] = klass
39
+ end
40
+
41
+ def delete(tag_name)
42
+ @tags.delete(tag_name)
43
+ @cache.delete(tag_name)
44
+ end
45
+
46
+ def each(&block)
47
+ @tags.each(&block)
48
+ end
49
+
50
+ private
51
+
52
+ def lookup_class(name)
53
+ name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
54
+ end
55
+ end
56
+
57
+ attr_reader :profiler
58
+
20
59
  class << self
60
+ # Sets how strict the parser should be.
61
+ # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
62
+ # :warn is the default and will give deprecation warnings when invalid syntax is used.
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
71
+
72
+ attr_accessor :default_exception_renderer
73
+ Template.default_exception_renderer = lambda do |exception|
74
+ exception
75
+ end
76
+
21
77
  def file_system
22
78
  @@file_system
23
79
  end
@@ -31,7 +87,15 @@ module Liquid
31
87
  end
32
88
 
33
89
  def tags
34
- @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
35
99
  end
36
100
 
37
101
  # Pass a module with filter methods which should be available
@@ -40,23 +104,33 @@ module Liquid
40
104
  Strainer.global_filter(mod)
41
105
  end
42
106
 
107
+ def default_resource_limits
108
+ @default_resource_limits ||= {}
109
+ end
110
+
43
111
  # creates a new <tt>Template</tt> object from liquid source code
44
- def parse(source)
112
+ # To enable profiling, pass in <tt>profile: true</tt> as an option.
113
+ # See Liquid::Profiler for more information
114
+ def parse(source, options = {})
45
115
  template = Template.new
46
- template.parse(source)
47
- template
116
+ template.parse(source, options)
48
117
  end
49
118
  end
50
119
 
51
- # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
52
120
  def initialize
53
- @resource_limits = {}
121
+ @rethrow_errors = false
122
+ @resource_limits = ResourceLimits.new(self.class.default_resource_limits)
54
123
  end
55
124
 
56
125
  # Parse source code.
57
126
  # Returns self for easy chaining
58
- def parse(source)
59
- @root = Document.new(tokenize(source))
127
+ 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
60
134
  self
61
135
  end
62
136
 
@@ -81,6 +155,9 @@ module Liquid
81
155
  # if you use the same filters over and over again consider registering them globally
82
156
  # with <tt>Template.register_filter</tt>
83
157
  #
158
+ # if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
159
+ # will be available via <tt>Template#profiler</tt>
160
+ #
84
161
  # Following options can be passed:
85
162
  #
86
163
  # * <tt>filters</tt> : array with local filters
@@ -88,11 +165,17 @@ module Liquid
88
165
  # filters and tags and might be useful to integrate liquid more with its host application
89
166
  #
90
167
  def render(*args)
91
- return '' if @root.nil?
168
+ return ''.freeze if @root.nil?
92
169
 
93
170
  context = case args.first
94
171
  when Liquid::Context
95
- args.shift
172
+ c = args.shift
173
+
174
+ if @rethrow_errors
175
+ c.exception_renderer = ->(e) { raise }
176
+ end
177
+
178
+ c
96
179
  when Liquid::Drop
97
180
  drop = args.shift
98
181
  drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
@@ -101,31 +184,29 @@ module Liquid
101
184
  when nil
102
185
  Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
103
186
  else
104
- raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
187
+ raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
105
188
  end
106
189
 
107
190
  case args.last
108
191
  when Hash
109
192
  options = args.pop
110
193
 
111
- if options[:registers].is_a?(Hash)
112
- self.registers.merge!(options[:registers])
113
- end
194
+ registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
114
195
 
115
- if options[:filters]
116
- context.add_filters(options[:filters])
117
- end
118
-
119
- when Module
120
- context.add_filters(args.pop)
121
- when Array
196
+ apply_options_to_context(context, options)
197
+ when Module, Array
122
198
  context.add_filters(args.pop)
123
199
  end
124
200
 
201
+ # Retrying a render resets resource usage
202
+ context.resource_limits.reset
203
+
125
204
  begin
126
205
  # render the nodelist.
127
206
  # for performance reasons we get an array back here. join will make a string out of it.
128
- result = @root.render(context)
207
+ result = with_profiling(context) do
208
+ @root.render(context)
209
+ end
129
210
  result.respond_to?(:join) ? result.join : result
130
211
  rescue Liquid::MemoryError => e
131
212
  context.handle_error(e)
@@ -135,22 +216,39 @@ module Liquid
135
216
  end
136
217
 
137
218
  def render!(*args)
138
- @rethrow_errors = true; render(*args)
219
+ @rethrow_errors = true
220
+ render(*args)
139
221
  end
140
222
 
141
223
  private
142
224
 
143
- # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
144
225
  def tokenize(source)
145
- source = source.source if source.respond_to?(:source)
146
- return [] if source.to_s.empty?
147
- tokens = source.split(TemplateParser)
226
+ Tokenizer.new(source, @line_numbers)
227
+ end
148
228
 
149
- # removes the rogue empty element at the beginning of the array
150
- tokens.shift if tokens[0] and tokens[0].empty?
229
+ def with_profiling(context)
230
+ if @profiling && !context.partial
231
+ raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
151
232
 
152
- tokens
233
+ @profiler = Profiler.new
234
+ @profiler.start
235
+
236
+ begin
237
+ yield
238
+ ensure
239
+ @profiler.stop
240
+ end
241
+ else
242
+ yield
243
+ end
153
244
  end
154
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
155
253
  end
156
254
  end