liquid 4.0.3 → 5.1.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +54 -0
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +166 -54
  6. data/lib/liquid/condition.rb +41 -20
  7. data/lib/liquid/context.rb +107 -52
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -34
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +11 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +3 -1
  19. data/lib/liquid/parse_context.rb +20 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +13 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +95 -46
  30. data/lib/liquid/static_registers.rb +44 -0
  31. data/lib/liquid/strainer_factory.rb +36 -0
  32. data/lib/liquid/strainer_template.rb +53 -0
  33. data/lib/liquid/tablerowloop_drop.rb +6 -4
  34. data/lib/liquid/tag/disableable.rb +22 -0
  35. data/lib/liquid/tag/disabler.rb +21 -0
  36. data/lib/liquid/tag.rb +28 -6
  37. data/lib/liquid/tags/assign.rb +24 -10
  38. data/lib/liquid/tags/break.rb +8 -3
  39. data/lib/liquid/tags/capture.rb +11 -8
  40. data/lib/liquid/tags/case.rb +40 -27
  41. data/lib/liquid/tags/comment.rb +5 -3
  42. data/lib/liquid/tags/continue.rb +8 -3
  43. data/lib/liquid/tags/cycle.rb +25 -14
  44. data/lib/liquid/tags/decrement.rb +6 -3
  45. data/lib/liquid/tags/echo.rb +34 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +39 -23
  48. data/lib/liquid/tags/ifchanged.rb +11 -10
  49. data/lib/liquid/tags/include.rb +34 -47
  50. data/lib/liquid/tags/increment.rb +7 -3
  51. data/lib/liquid/tags/raw.rb +14 -11
  52. data/lib/liquid/tags/render.rb +84 -0
  53. data/lib/liquid/tags/table_row.rb +23 -19
  54. data/lib/liquid/tags/unless.rb +23 -15
  55. data/lib/liquid/template.rb +53 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +46 -41
  61. data/lib/liquid/variable_lookup.rb +11 -6
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +17 -5
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +385 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +132 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +74 -32
  99. data/test/test_helper.rb +113 -22
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +16 -2
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +76 -50
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/lib/liquid/truffle.rb +0 -5
  123. data/test/truffle/truffle_test.rb +0 -9
  124. data/test/unit/context_unit_test.rb +0 -489
  125. data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,18 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Ifchanged < Block
3
- def render(context)
4
- context.stack do
5
- output = super
5
+ def render_to_output_buffer(context, output)
6
+ block_output = +''
7
+ super(context, block_output)
6
8
 
7
- if output != context.registers[:ifchanged]
8
- context.registers[:ifchanged] = output
9
- output
10
- else
11
- ''.freeze
12
- end
9
+ if block_output != context.registers[:ifchanged]
10
+ context.registers[:ifchanged] = block_output
11
+ output << block_output
13
12
  end
13
+
14
+ output
14
15
  end
15
16
  end
16
17
 
17
- Template.register_tag('ifchanged'.freeze, Ifchanged)
18
+ Template.register_tag('ifchanged', Ifchanged)
18
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Include allows templates to relate with other templates
3
5
  #
@@ -14,40 +16,49 @@ module Liquid
14
16
  # {% include 'product' for products %}
15
17
  #
16
18
  class Include < Tag
17
- Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
19
+ prepend Tag::Disableable
20
+
21
+ SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
22
+ Syntax = SYNTAX
18
23
 
19
24
  attr_reader :template_name_expr, :variable_name_expr, :attributes
20
25
 
21
26
  def initialize(tag_name, markup, options)
22
27
  super
23
28
 
24
- if markup =~ Syntax
29
+ if markup =~ SYNTAX
25
30
 
26
- template_name = $1
27
- variable_name = $3
31
+ template_name = Regexp.last_match(1)
32
+ variable_name = Regexp.last_match(3)
28
33
 
29
- @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
30
- @template_name_expr = Expression.parse(template_name)
31
- @attributes = {}
34
+ @alias_name = Regexp.last_match(5)
35
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
36
+ @template_name_expr = parse_expression(template_name)
37
+ @attributes = {}
32
38
 
33
39
  markup.scan(TagAttributes) do |key, value|
34
- @attributes[key] = Expression.parse(value)
40
+ @attributes[key] = parse_expression(value)
35
41
  end
36
42
 
37
43
  else
38
- raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
44
+ raise SyntaxError, options[:locale].t("errors.syntax.include")
39
45
  end
40
46
  end
41
47
 
42
48
  def parse(_tokens)
43
49
  end
44
50
 
45
- def render(context)
51
+ def render_to_output_buffer(context, output)
46
52
  template_name = context.evaluate(@template_name_expr)
47
- raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
53
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
48
54
 
49
- partial = load_cached_partial(template_name, context)
50
- context_variable_name = template_name.split('/'.freeze).last
55
+ partial = PartialCache.load(
56
+ template_name,
57
+ context: context,
58
+ parse_context: parse_context
59
+ )
60
+
61
+ context_variable_name = @alias_name || template_name.split('/').last
51
62
 
52
63
  variable = if @variable_name_expr
53
64
  context.evaluate(@variable_name_expr)
@@ -56,69 +67,45 @@ module Liquid
56
67
  end
57
68
 
58
69
  old_template_name = context.template_name
59
- old_partial = context.partial
70
+ old_partial = context.partial
60
71
  begin
61
72
  context.template_name = template_name
62
- context.partial = true
73
+ context.partial = true
63
74
  context.stack do
64
75
  @attributes.each do |key, value|
65
76
  context[key] = context.evaluate(value)
66
77
  end
67
78
 
68
79
  if variable.is_a?(Array)
69
- variable.collect do |var|
80
+ variable.each do |var|
70
81
  context[context_variable_name] = var
71
- partial.render(context)
82
+ partial.render_to_output_buffer(context, output)
72
83
  end
73
84
  else
74
85
  context[context_variable_name] = variable
75
- partial.render(context)
86
+ partial.render_to_output_buffer(context, output)
76
87
  end
77
88
  end
78
89
  ensure
79
90
  context.template_name = old_template_name
80
- context.partial = old_partial
91
+ context.partial = old_partial
81
92
  end
82
- end
83
93
 
84
- private
94
+ output
95
+ end
85
96
 
86
97
  alias_method :parse_context, :options
87
98
  private :parse_context
88
99
 
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
101
- end
102
- cached_partials[template_name] = partial
103
- context.registers[:cached_partials] = cached_partials
104
- partial
105
- end
106
-
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
100
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
114
101
  def children
115
102
  [
116
103
  @node.template_name_expr,
117
- @node.variable_name_expr
104
+ @node.variable_name_expr,
118
105
  ] + @node.attributes.values
119
106
  end
120
107
  end
121
108
  end
122
109
 
123
- Template.register_tag('include'.freeze, Include)
110
+ Template.register_tag('include', Include)
124
111
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # increment is used in a place where one needs to insert a counter
3
5
  # into a template, and needs the counter to survive across
@@ -20,12 +22,14 @@ module Liquid
20
22
  @variable = markup.strip
21
23
  end
22
24
 
23
- def render(context)
25
+ def render_to_output_buffer(context, output)
24
26
  value = context.environments.first[@variable] ||= 0
25
27
  context.environments.first[@variable] = value + 1
26
- value.to_s
28
+
29
+ output << value.to_s
30
+ output
27
31
  end
28
32
  end
29
33
 
30
- Template.register_tag('increment'.freeze, Increment)
34
+ Template.register_tag('increment', Increment)
31
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Raw < Block
3
5
  Syntax = /\A\s*\z/
@@ -10,20 +12,21 @@ module Liquid
10
12
  end
11
13
 
12
14
  def parse(tokens)
13
- @body = ''
14
- while token = tokens.shift
15
- if token =~ FullTokenPossiblyInvalid
16
- @body << $1 if $1 != "".freeze
17
- return if block_delimiter == $2
15
+ @body = +''
16
+ while (token = tokens.shift)
17
+ if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
18
+ @body << Regexp.last_match(1) if Regexp.last_match(1) != ""
19
+ return
18
20
  end
19
21
  @body << token unless token.empty?
20
22
  end
21
23
 
22
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
24
+ raise_tag_never_closed(block_name)
23
25
  end
24
26
 
25
- def render(_context)
26
- @body
27
+ def render_to_output_buffer(_context, output)
28
+ output << @body
29
+ output
27
30
  end
28
31
 
29
32
  def nodelist
@@ -37,11 +40,11 @@ module Liquid
37
40
  protected
38
41
 
39
42
  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))
43
+ unless Syntax.match?(markup)
44
+ raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name)
42
45
  end
43
46
  end
44
47
  end
45
48
 
46
- Template.register_tag('raw'.freeze, Raw)
49
+ Template.register_tag('raw', Raw)
47
50
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Render < Tag
5
+ FOR = 'for'
6
+ SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
7
+
8
+ disable_tags "include"
9
+
10
+ attr_reader :template_name_expr, :attributes
11
+
12
+ def initialize(tag_name, markup, options)
13
+ super
14
+
15
+ raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
16
+
17
+ template_name = Regexp.last_match(1)
18
+ with_or_for = Regexp.last_match(3)
19
+ variable_name = Regexp.last_match(4)
20
+
21
+ @alias_name = Regexp.last_match(6)
22
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
23
+ @template_name_expr = parse_expression(template_name)
24
+ @for = (with_or_for == FOR)
25
+
26
+ @attributes = {}
27
+ markup.scan(TagAttributes) do |key, value|
28
+ @attributes[key] = parse_expression(value)
29
+ end
30
+ end
31
+
32
+ def render_to_output_buffer(context, output)
33
+ render_tag(context, output)
34
+ end
35
+
36
+ def render_tag(context, output)
37
+ # Though we evaluate this here we will only ever parse it as a string literal.
38
+ template_name = context.evaluate(@template_name_expr)
39
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
40
+
41
+ partial = PartialCache.load(
42
+ template_name,
43
+ context: context,
44
+ parse_context: parse_context
45
+ )
46
+
47
+ context_variable_name = @alias_name || template_name.split('/').last
48
+
49
+ render_partial_func = ->(var, forloop) {
50
+ inner_context = context.new_isolated_subcontext
51
+ inner_context.template_name = template_name
52
+ inner_context.partial = true
53
+ inner_context['forloop'] = forloop if forloop
54
+
55
+ @attributes.each do |key, value|
56
+ inner_context[key] = context.evaluate(value)
57
+ end
58
+ inner_context[context_variable_name] = var unless var.nil?
59
+ partial.render_to_output_buffer(inner_context, output)
60
+ forloop&.send(:increment!)
61
+ }
62
+
63
+ variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
64
+ if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
65
+ forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
66
+ variable.each { |var| render_partial_func.call(var, forloop) }
67
+ else
68
+ render_partial_func.call(variable, nil)
69
+ end
70
+
71
+ output
72
+ end
73
+
74
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
75
+ def children
76
+ [
77
+ @node.template_name_expr,
78
+ ] + @node.attributes.values
79
+ end
80
+ end
81
+ end
82
+
83
+ Template.register_tag('render', Render)
84
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class TableRow < Block
3
5
  Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
@@ -7,48 +9,50 @@ module Liquid
7
9
  def initialize(tag_name, markup, options)
8
10
  super
9
11
  if markup =~ Syntax
10
- @variable_name = $1
11
- @collection_name = Expression.parse($2)
12
- @attributes = {}
12
+ @variable_name = Regexp.last_match(1)
13
+ @collection_name = parse_expression(Regexp.last_match(2))
14
+ @attributes = {}
13
15
  markup.scan(TagAttributes) do |key, value|
14
- @attributes[key] = Expression.parse(value)
16
+ @attributes[key] = parse_expression(value)
15
17
  end
16
18
  else
17
- raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
19
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row")
18
20
  end
19
21
  end
20
22
 
21
- def render(context)
22
- collection = context.evaluate(@collection_name) or return ''.freeze
23
+ def render_to_output_buffer(context, output)
24
+ (collection = context.evaluate(@collection_name)) || (return '')
23
25
 
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
+ from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
27
+ to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
26
28
 
27
29
  collection = Utils.slice_collection(collection, from, to)
30
+ length = collection.length
28
31
 
29
- length = collection.length
30
-
31
- cols = context.evaluate(@attributes['cols'.freeze]).to_i
32
+ cols = context.evaluate(@attributes['cols']).to_i
32
33
 
33
- result = "<tr class=\"row1\">\n"
34
+ output << "<tr class=\"row1\">\n"
34
35
  context.stack do
35
36
  tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
36
- context['tablerowloop'.freeze] = tablerowloop
37
+ context['tablerowloop'] = tablerowloop
37
38
 
38
39
  collection.each do |item|
39
40
  context[@variable_name] = item
40
41
 
41
- result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
42
+ output << "<td class=\"col#{tablerowloop.col}\">"
43
+ super
44
+ output << '</td>'
42
45
 
43
46
  if tablerowloop.col_last && !tablerowloop.last
44
- result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
47
+ output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
45
48
  end
46
49
 
47
50
  tablerowloop.send(:increment!)
48
51
  end
49
52
  end
50
- result << "</tr>\n"
51
- result
53
+
54
+ output << "</tr>\n"
55
+ output
52
56
  end
53
57
 
54
58
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
@@ -58,5 +62,5 @@ module Liquid
58
62
  end
59
63
  end
60
64
 
61
- Template.register_tag('tablerow'.freeze, TableRow)
65
+ Template.register_tag('tablerow', TableRow)
62
66
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'if'
2
4
 
3
5
  module Liquid
@@ -6,25 +8,31 @@ module Liquid
6
8
  # {% unless x < 0 %} x is greater than zero {% endunless %}
7
9
  #
8
10
  class Unless < If
9
- def render(context)
10
- context.stack do
11
- # First condition is interpreted backwards ( if not )
12
- first_block = @blocks.first
13
- unless first_block.evaluate(context)
14
- return first_block.attachment.render(context)
15
- end
11
+ def render_to_output_buffer(context, output)
12
+ # First condition is interpreted backwards ( if not )
13
+ first_block = @blocks.first
14
+ result = Liquid::Utils.to_liquid_value(
15
+ first_block.evaluate(context)
16
+ )
16
17
 
17
- # After the first condition unless works just like if
18
- @blocks[1..-1].each do |block|
19
- if block.evaluate(context)
20
- return block.attachment.render(context)
21
- end
22
- end
18
+ unless result
19
+ return first_block.attachment.render_to_output_buffer(context, output)
20
+ end
23
21
 
24
- ''.freeze
22
+ # After the first condition unless works just like if
23
+ @blocks[1..-1].each do |block|
24
+ result = Liquid::Utils.to_liquid_value(
25
+ block.evaluate(context)
26
+ )
27
+
28
+ if result
29
+ return block.attachment.render_to_output_buffer(context, output)
30
+ end
25
31
  end
32
+
33
+ output
26
34
  end
27
35
  end
28
36
 
29
- Template.register_tag('unless'.freeze, Unless)
37
+ Template.register_tag('unless', Unless)
30
38
  end