liquid 2.6.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 (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +272 -26
  3. data/README.md +67 -3
  4. data/lib/liquid/block.rb +62 -94
  5. data/lib/liquid/block_body.rb +255 -0
  6. data/lib/liquid/condition.rb +96 -38
  7. data/lib/liquid/context.rb +172 -154
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +33 -14
  10. data/lib/liquid/errors.rb +56 -10
  11. data/lib/liquid/expression.rb +45 -0
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +27 -14
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +41 -0
  16. data/lib/liquid/interrupts.rb +3 -2
  17. data/lib/liquid/lexer.rb +62 -0
  18. data/lib/liquid/locales/en.yml +29 -0
  19. data/lib/liquid/parse_context.rb +54 -0
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +102 -0
  22. data/lib/liquid/parser_switching.rb +45 -0
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +35 -0
  25. data/lib/liquid/profiler.rb +139 -0
  26. data/lib/liquid/range_lookup.rb +47 -0
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +789 -118
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +121 -0
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +49 -10
  36. data/lib/liquid/tags/assign.rb +61 -19
  37. data/lib/liquid/tags/break.rb +14 -4
  38. data/lib/liquid/tags/capture.rb +29 -21
  39. data/lib/liquid/tags/case.rb +80 -31
  40. data/lib/liquid/tags/comment.rb +24 -2
  41. data/lib/liquid/tags/continue.rb +14 -13
  42. data/lib/liquid/tags/cycle.rb +50 -32
  43. data/lib/liquid/tags/decrement.rb +24 -26
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +164 -100
  46. data/lib/liquid/tags/if.rb +105 -44
  47. data/lib/liquid/tags/ifchanged.rb +10 -11
  48. data/lib/liquid/tags/include.rb +85 -65
  49. data/lib/liquid/tags/increment.rb +24 -22
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +88 -0
  54. data/lib/liquid/tags/unless.rb +37 -21
  55. data/lib/liquid/template.rb +124 -46
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +39 -0
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +68 -5
  60. data/lib/liquid/variable.rb +128 -32
  61. data/lib/liquid/variable_lookup.rb +96 -0
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +36 -13
  64. metadata +69 -77
  65. data/lib/extras/liquid_view.rb +0 -51
  66. data/lib/liquid/htmltags.rb +0 -73
  67. data/lib/liquid/module_ex.rb +0 -62
  68. data/lib/liquid/strainer.rb +0 -53
  69. data/test/liquid/assign_test.rb +0 -21
  70. data/test/liquid/block_test.rb +0 -58
  71. data/test/liquid/capture_test.rb +0 -40
  72. data/test/liquid/condition_test.rb +0 -127
  73. data/test/liquid/context_test.rb +0 -478
  74. data/test/liquid/drop_test.rb +0 -180
  75. data/test/liquid/error_handling_test.rb +0 -81
  76. data/test/liquid/file_system_test.rb +0 -29
  77. data/test/liquid/filter_test.rb +0 -125
  78. data/test/liquid/hash_ordering_test.rb +0 -25
  79. data/test/liquid/module_ex_test.rb +0 -87
  80. data/test/liquid/output_test.rb +0 -116
  81. data/test/liquid/parsing_quirks_test.rb +0 -52
  82. data/test/liquid/regexp_test.rb +0 -44
  83. data/test/liquid/security_test.rb +0 -64
  84. data/test/liquid/standard_filter_test.rb +0 -263
  85. data/test/liquid/strainer_test.rb +0 -52
  86. data/test/liquid/tags/break_tag_test.rb +0 -16
  87. data/test/liquid/tags/continue_tag_test.rb +0 -16
  88. data/test/liquid/tags/for_tag_test.rb +0 -297
  89. data/test/liquid/tags/html_tag_test.rb +0 -63
  90. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  91. data/test/liquid/tags/include_tag_test.rb +0 -166
  92. data/test/liquid/tags/increment_tag_test.rb +0 -24
  93. data/test/liquid/tags/raw_tag_test.rb +0 -24
  94. data/test/liquid/tags/standard_tag_test.rb +0 -295
  95. data/test/liquid/tags/statements_test.rb +0 -134
  96. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  97. data/test/liquid/template_test.rb +0 -146
  98. data/test/liquid/variable_test.rb +0 -186
  99. data/test/test_helper.rb +0 -29
  100. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # StrainerFactory is the factory for the filters system.
5
+ module StrainerFactory
6
+ extend self
7
+
8
+ def add_global_filter(filter)
9
+ strainer_class_cache.clear
10
+ GlobalCache.add_filter(filter)
11
+ end
12
+
13
+ def create(context, filters = [])
14
+ strainer_from_cache(filters).new(context)
15
+ end
16
+
17
+ def global_filter_names
18
+ GlobalCache.filter_method_names
19
+ end
20
+
21
+ GlobalCache = Class.new(StrainerTemplate)
22
+
23
+ private
24
+
25
+ def strainer_from_cache(filters)
26
+ if filters.empty?
27
+ GlobalCache
28
+ else
29
+ strainer_class_cache[filters] ||= begin
30
+ klass = Class.new(GlobalCache)
31
+ filters.each { |f| klass.add_filter(f) }
32
+ klass
33
+ end
34
+ end
35
+ end
36
+
37
+ def strainer_class_cache
38
+ @strainer_class_cache ||= {}
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Liquid
6
+ # StrainerTemplate is the computed class for the filters system.
7
+ # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
8
+ #
9
+ # The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
10
+ # Context#add_filters or Template.register_filter
11
+ class StrainerTemplate
12
+ def initialize(context)
13
+ @context = context
14
+ end
15
+
16
+ class << self
17
+ def add_filter(filter)
18
+ return if include?(filter)
19
+
20
+ invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
21
+ if invokable_non_public_methods.any?
22
+ raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
23
+ end
24
+
25
+ include(filter)
26
+
27
+ filter_methods.merge(filter.public_instance_methods.map(&:to_s))
28
+ end
29
+
30
+ def invokable?(method)
31
+ filter_methods.include?(method.to_s)
32
+ end
33
+
34
+ def inherited(subclass)
35
+ super
36
+ subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
37
+ end
38
+
39
+ def filter_method_names
40
+ filter_methods.map(&:to_s).to_a
41
+ end
42
+
43
+ private
44
+
45
+ def filter_methods
46
+ @filter_methods ||= Set.new
47
+ end
48
+ end
49
+
50
+ def invoke(method, *args)
51
+ if self.class.invokable?(method)
52
+ send(method, *args)
53
+ elsif @context.strict_filters
54
+ raise Liquid::UndefinedFilter, "undefined filter #{method}"
55
+ else
56
+ args.first
57
+ end
58
+ rescue ::ArgumentError => e
59
+ raise Liquid::ArgumentError, e.message, e.backtrace
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type object
6
+ # @liquid_name tablerowloop
7
+ # @liquid_summary
8
+ # Information about a parent [`tablerow` loop](/api/liquid/tags#tablerow).
9
+ class TablerowloopDrop < Drop
10
+ def initialize(length, cols)
11
+ @length = length
12
+ @row = 1
13
+ @col = 1
14
+ @cols = cols
15
+ @index = 0
16
+ end
17
+
18
+ # @liquid_public_docs
19
+ # @liquid_summary
20
+ # The total number of iterations in the loop.
21
+ # @liquid_return [number]
22
+ attr_reader :length
23
+
24
+ # @liquid_public_docs
25
+ # @liquid_summary
26
+ # The 1-based index of the current column.
27
+ # @liquid_return [number]
28
+ attr_reader :col
29
+
30
+ # @liquid_public_docs
31
+ # @liquid_summary
32
+ # The 1-based index of current row.
33
+ # @liquid_return [number]
34
+ attr_reader :row
35
+
36
+ # @liquid_public_docs
37
+ # @liquid_summary
38
+ # The 1-based index of the current iteration.
39
+ # @liquid_return [number]
40
+ def index
41
+ @index + 1
42
+ end
43
+
44
+ # @liquid_public_docs
45
+ # @liquid_summary
46
+ # The 0-based index of the current iteration.
47
+ # @liquid_return [number]
48
+ def index0
49
+ @index
50
+ end
51
+
52
+ # @liquid_public_docs
53
+ # @liquid_summary
54
+ # The 0-based index of the current column.
55
+ # @liquid_return [number]
56
+ def col0
57
+ @col - 1
58
+ end
59
+
60
+ # @liquid_public_docs
61
+ # @liquid_summary
62
+ # The 1-based index of the current iteration, in reverse order.
63
+ # @liquid_return [number]
64
+ def rindex
65
+ @length - @index
66
+ end
67
+
68
+ # @liquid_public_docs
69
+ # @liquid_summary
70
+ # The 0-based index of the current iteration, in reverse order.
71
+ # @liquid_return [number]
72
+ def rindex0
73
+ @length - @index - 1
74
+ end
75
+
76
+ # @liquid_public_docs
77
+ # @liquid_summary
78
+ # Returns `true` if the current iteration is the first. Returns `false` if not.
79
+ # @liquid_return [boolean]
80
+ def first
81
+ @index == 0
82
+ end
83
+
84
+ # @liquid_public_docs
85
+ # @liquid_summary
86
+ # Returns `true` if the current iteration is the last. Returns `false` if not.
87
+ # @liquid_return [boolean]
88
+ def last
89
+ @index == @length - 1
90
+ end
91
+
92
+ # @liquid_public_docs
93
+ # @liquid_summary
94
+ # Returns `true` if the current column is the first in the row. Returns `false` if not.
95
+ # @liquid_return [boolean]
96
+ def col_first
97
+ @col == 1
98
+ end
99
+
100
+ # @liquid_public_docs
101
+ # @liquid_summary
102
+ # Returns `true` if the current column is the last in the row. Returns `false` if not.
103
+ # @liquid_return [boolean]
104
+ def col_last
105
+ @col == @cols
106
+ end
107
+
108
+ protected
109
+
110
+ def increment!
111
+ @index += 1
112
+
113
+ if @col == @cols
114
+ @col = 1
115
+ @row += 1
116
+ else
117
+ @col += 1
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Tag
5
+ module Disableable
6
+ def render_to_output_buffer(context, output)
7
+ if context.tag_disabled?(tag_name)
8
+ output << disabled_error(context)
9
+ return
10
+ end
11
+ super
12
+ end
13
+
14
+ def disabled_error(context)
15
+ # raise then rescue the exception so that the Context#exception_renderer can re-raise it
16
+ raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
17
+ rescue DisabledError => exc
18
+ context.handle_error(exc, line_number)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Tag
5
+ module Disabler
6
+ module ClassMethods
7
+ attr_reader :disabled_tags
8
+ end
9
+
10
+ def self.prepended(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ def render_to_output_buffer(context, output)
15
+ context.with_disabled_tags(self.class.disabled_tags) do
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/liquid/tag.rb CHANGED
@@ -1,26 +1,65 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
+ module Liquid
3
4
  class Tag
5
+ attr_reader :nodelist, :tag_name, :line_number, :parse_context
6
+ alias_method :options, :parse_context
7
+ include ParserSwitching
8
+
9
+ class << self
10
+ def parse(tag_name, markup, tokenizer, parse_context)
11
+ tag = new(tag_name, markup, parse_context)
12
+ tag.parse(tokenizer)
13
+ tag
14
+ end
15
+
16
+ def disable_tags(*tag_names)
17
+ @disabled_tags ||= []
18
+ @disabled_tags.concat(tag_names)
19
+ prepend(Disabler)
20
+ end
21
+
22
+ private :new
23
+ end
4
24
 
5
- attr_accessor :nodelist
25
+ def initialize(tag_name, markup, parse_context)
26
+ @tag_name = tag_name
27
+ @markup = markup
28
+ @parse_context = parse_context
29
+ @line_number = parse_context.line_number
30
+ end
6
31
 
7
- def initialize(tag_name, markup, tokens)
8
- @tag_name = tag_name
9
- @markup = markup
10
- parse(tokens)
32
+ def parse(_tokens)
11
33
  end
12
34
 
13
- def parse(tokens)
35
+ def raw
36
+ "#{@tag_name} #{@markup}"
14
37
  end
15
38
 
16
39
  def name
17
40
  self.class.name.downcase
18
41
  end
19
42
 
20
- def render(context)
43
+ def render(_context)
21
44
  ''
22
45
  end
23
46
 
24
- end # Tag
47
+ # For backwards compatibility with custom tags. In a future release, the semantics
48
+ # of the `render_to_output_buffer` method will become the default and the `render`
49
+ # method will be removed.
50
+ def render_to_output_buffer(context, output)
51
+ output << render(context)
52
+ output
53
+ end
54
+
55
+ def blank?
56
+ false
57
+ end
58
+
59
+ private
25
60
 
26
- end # Liquid
61
+ def parse_expression(markup)
62
+ parse_context.parse_expression(markup)
63
+ end
64
+ end
65
+ end
@@ -1,34 +1,76 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
- # Assign sets a variable in your template.
4
- #
5
- # {% assign foo = 'monkey' %}
6
- #
7
- # You can then use the variable later in the page.
8
- #
9
- # {{ foo }}
10
- #
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category variable
7
+ # @liquid_name assign
8
+ # @liquid_summary
9
+ # Creates a new variable.
10
+ # @liquid_description
11
+ # You can create variables of any [basic type](/api/liquid/basics#types), [object](/api/liquid/objects), or object property.
12
+ # @liquid_syntax
13
+ # {% assign variable_name = value %}
14
+ # @liquid_syntax_keyword variable_name The name of the variable being created.
15
+ # @liquid_syntax_keyword value The value you want to assign to the variable.
11
16
  class Assign < Tag
12
- Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
17
+ Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
18
+
19
+ # @api private
20
+ def self.raise_syntax_error(parse_context)
21
+ raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
22
+ end
23
+
24
+ attr_reader :to, :from
13
25
 
14
- def initialize(tag_name, markup, tokens)
26
+ def initialize(tag_name, markup, parse_context)
27
+ super
15
28
  if markup =~ Syntax
16
- @to = $1
17
- @from = Variable.new($2)
29
+ @to = Regexp.last_match(1)
30
+ @from = Variable.new(Regexp.last_match(2), parse_context)
18
31
  else
19
- raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
32
+ self.class.raise_syntax_error(parse_context)
20
33
  end
21
-
22
- super
23
34
  end
24
35
 
25
- def render(context)
36
+ def render_to_output_buffer(context, output)
26
37
  val = @from.render(context)
27
38
  context.scopes.last[@to] = val
28
- context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
29
- ''
39
+ context.resource_limits.increment_assign_score(assign_score_of(val))
40
+ output
30
41
  end
31
42
 
43
+ def blank?
44
+ true
45
+ end
46
+
47
+ private
48
+
49
+ def assign_score_of(val)
50
+ if val.instance_of?(String)
51
+ val.bytesize
52
+ elsif val.instance_of?(Array)
53
+ sum = 1
54
+ # Uses #each to avoid extra allocations.
55
+ val.each { |child| sum += assign_score_of(child) }
56
+ sum
57
+ elsif val.instance_of?(Hash)
58
+ sum = 1
59
+ val.each do |key, entry_value|
60
+ sum += assign_score_of(key)
61
+ sum += assign_score_of(entry_value)
62
+ end
63
+ sum
64
+ else
65
+ 1
66
+ end
67
+ end
68
+
69
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
70
+ def children
71
+ [@node.from]
72
+ end
73
+ end
32
74
  end
33
75
 
34
76
  Template.register_tag('assign', Assign)
@@ -1,5 +1,6 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
+ module Liquid
3
4
  # Break tag to be used to break out of a for loop.
4
5
  #
5
6
  # == Basic Usage:
@@ -9,12 +10,21 @@ module Liquid
9
10
  # {% endif %}
10
11
  # {% endfor %}
11
12
  #
13
+ # @liquid_public_docs
14
+ # @liquid_type tag
15
+ # @liquid_category iteration
16
+ # @liquid_name break
17
+ # @liquid_summary
18
+ # Stops a [`for` loop](/api/liquid/tags#for) from iterating.
19
+ # @liquid_syntax
20
+ # {% break %}
12
21
  class Break < Tag
22
+ INTERRUPT = BreakInterrupt.new.freeze
13
23
 
14
- def interrupt
15
- BreakInterrupt.new
24
+ def render_to_output_buffer(context, output)
25
+ context.push_interrupt(INTERRUPT)
26
+ output
16
27
  end
17
-
18
28
  end
19
29
 
20
30
  Template.register_tag('break', Break)
@@ -1,34 +1,42 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
- # Capture stores the result of a block into a variable without rendering it inplace.
4
- #
5
- # {% capture heading %}
6
- # Monkeys!
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category variable
7
+ # @liquid_name capture
8
+ # @liquid_summary
9
+ # Creates a new variable with a string value.
10
+ # @liquid_description
11
+ # You can create complex strings with Liquid logic and variables.
12
+ # @liquid_syntax
13
+ # {% capture variable %}
14
+ # value
7
15
  # {% endcapture %}
8
- # ...
9
- # <h1>{{ heading }}</h1>
10
- #
11
- # Capture is useful for saving content for use later in your template, such as
12
- # in a sidebar or footer.
13
- #
16
+ # @liquid_syntax_keyword variable The name of the variable being created.
17
+ # @liquid_syntax_keyword value The value you want to assign to the variable.
14
18
  class Capture < Block
15
- Syntax = /(\w+)/
19
+ Syntax = /(#{VariableSignature}+)/o
16
20
 
17
- def initialize(tag_name, markup, tokens)
21
+ def initialize(tag_name, markup, options)
22
+ super
18
23
  if markup =~ Syntax
19
- @to = $1
24
+ @to = Regexp.last_match(1)
20
25
  else
21
- raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
26
+ raise SyntaxError, options[:locale].t("errors.syntax.capture")
22
27
  end
28
+ end
23
29
 
24
- super
30
+ def render_to_output_buffer(context, output)
31
+ context.resource_limits.with_capture do
32
+ capture_output = render(context)
33
+ context.scopes.last[@to] = capture_output
34
+ end
35
+ output
25
36
  end
26
37
 
27
- def render(context)
28
- output = super
29
- context.scopes.last[@to] = output
30
- context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
31
- ''
38
+ def blank?
39
+ true
32
40
  end
33
41
  end
34
42
 
@@ -1,22 +1,62 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category conditional
7
+ # @liquid_name case
8
+ # @liquid_summary
9
+ # Renders a specific expression depending on the value of a specific variable.
10
+ # @liquid_syntax
11
+ # {% case variable %}
12
+ # {% when first_value %}
13
+ # first_expression
14
+ # {% when second_value %}
15
+ # second_expression
16
+ # {% else %}
17
+ # third_expression
18
+ # {% endcase %}
19
+ # @liquid_syntax_keyword variable The name of the variable you want to base your case statement on.
20
+ # @liquid_syntax_keyword first_value A specific value to check for.
21
+ # @liquid_syntax_keyword second_value A specific value to check for.
22
+ # @liquid_syntax_keyword first_expression An expression to be rendered when the variable's value matches `first_value`.
23
+ # @liquid_syntax_keyword second_expression An expression to be rendered when the variable's value matches `second_value`.
24
+ # @liquid_syntax_keyword third_expression An expression to be rendered when the variable's value has no match.
2
25
  class Case < Block
3
26
  Syntax = /(#{QuotedFragment})/o
4
- WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
27
+ WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
28
+
29
+ attr_reader :blocks, :left
5
30
 
6
- def initialize(tag_name, markup, tokens)
31
+ def initialize(tag_name, markup, options)
32
+ super
7
33
  @blocks = []
8
34
 
9
35
  if markup =~ Syntax
10
- @left = $1
36
+ @left = parse_expression(Regexp.last_match(1))
11
37
  else
12
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
38
+ raise SyntaxError, options[:locale].t("errors.syntax.case")
13
39
  end
40
+ end
14
41
 
15
- super
42
+ def parse(tokens)
43
+ body = case_body = new_body
44
+ body = @blocks.last.attachment while parse_body(body, tokens)
45
+ @blocks.reverse_each do |condition|
46
+ body = condition.attachment
47
+ unless body.frozen?
48
+ body.remove_blank_strings if blank?
49
+ body.freeze
50
+ end
51
+ end
52
+ case_body.freeze
53
+ end
54
+
55
+ def nodelist
56
+ @blocks.map(&:attachment)
16
57
  end
17
58
 
18
59
  def unknown_tag(tag, markup, tokens)
19
- @nodelist = []
20
60
  case tag
21
61
  when 'when'
22
62
  record_when_condition(markup)
@@ -27,52 +67,61 @@ module Liquid
27
67
  end
28
68
  end
29
69
 
30
- def render(context)
31
- context.stack do
32
- execute_else_block = true
33
-
34
- output = ''
35
- @blocks.each do |block|
36
- if block.else?
37
- return render_all(block.attachment, context) if execute_else_block
38
- elsif block.evaluate(context)
39
- execute_else_block = false
40
- output << render_all(block.attachment, context)
41
- end
70
+ def render_to_output_buffer(context, output)
71
+ execute_else_block = true
72
+
73
+ @blocks.each do |block|
74
+ if block.else?
75
+ block.attachment.render_to_output_buffer(context, output) if execute_else_block
76
+ next
77
+ end
78
+
79
+ result = Liquid::Utils.to_liquid_value(
80
+ block.evaluate(context)
81
+ )
82
+
83
+ if result
84
+ execute_else_block = false
85
+ block.attachment.render_to_output_buffer(context, output)
42
86
  end
43
- output
44
87
  end
88
+
89
+ output
45
90
  end
46
91
 
47
92
  private
48
93
 
49
94
  def record_when_condition(markup)
95
+ body = new_body
96
+
50
97
  while markup
51
- # Create a new nodelist and assign it to the new block
52
- if not markup =~ WhenSyntax
53
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
98
+ unless markup =~ WhenSyntax
99
+ raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when")
54
100
  end
55
101
 
56
- markup = $2
102
+ markup = Regexp.last_match(2)
57
103
 
58
- block = Condition.new(@left, '==', $1)
59
- block.attach(@nodelist)
60
- @blocks.push(block)
104
+ block = Condition.new(@left, '==', Condition.parse_expression(parse_context, Regexp.last_match(1)))
105
+ block.attach(body)
106
+ @blocks << block
61
107
  end
62
108
  end
63
109
 
64
110
  def record_else_condition(markup)
65
-
66
- if not markup.strip.empty?
67
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
111
+ unless markup.strip.empty?
112
+ raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else")
68
113
  end
69
114
 
70
115
  block = ElseCondition.new
71
- block.attach(@nodelist)
116
+ block.attach(new_body)
72
117
  @blocks << block
73
118
  end
74
119
 
75
-
120
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
121
+ def children
122
+ [@node.left] + @node.blocks
123
+ end
124
+ end
76
125
  end
77
126
 
78
127
  Template.register_tag('case', Case)