liquid 3.0.6 → 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 (113) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +243 -58
  3. data/README.md +43 -4
  4. data/lib/liquid/block.rb +57 -123
  5. data/lib/liquid/block_body.rb +217 -85
  6. data/lib/liquid/condition.rb +92 -45
  7. data/lib/liquid/context.rb +154 -89
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +20 -17
  10. data/lib/liquid/errors.rb +27 -29
  11. data/lib/liquid/expression.rb +32 -20
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +17 -15
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +10 -8
  16. data/lib/liquid/interrupts.rb +4 -3
  17. data/lib/liquid/lexer.rb +37 -26
  18. data/lib/liquid/locales/en.yml +13 -6
  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 +30 -18
  22. data/lib/liquid/parser_switching.rb +20 -6
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +72 -92
  26. data/lib/liquid/range_lookup.rb +28 -3
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +715 -132
  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 +35 -12
  36. data/lib/liquid/tags/assign.rb +57 -18
  37. data/lib/liquid/tags/break.rb +15 -5
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +79 -30
  40. data/lib/liquid/tags/comment.rb +19 -4
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +47 -27
  43. data/lib/liquid/tags/decrement.rb +23 -24
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +155 -124
  46. data/lib/liquid/tags/if.rb +97 -63
  47. data/lib/liquid/tags/ifchanged.rb +11 -12
  48. data/lib/liquid/tags/include.rb +82 -73
  49. data/lib/liquid/tags/increment.rb +23 -17
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -8
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +57 -41
  54. data/lib/liquid/tags/unless.rb +38 -20
  55. data/lib/liquid/template.rb +71 -103
  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 +63 -9
  60. data/lib/liquid/variable.rb +74 -56
  61. data/lib/liquid/variable_lookup.rb +31 -15
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +27 -12
  64. metadata +30 -106
  65. data/lib/liquid/module_ex.rb +0 -62
  66. data/lib/liquid/strainer.rb +0 -59
  67. data/lib/liquid/token.rb +0 -18
  68. data/test/fixtures/en_locale.yml +0 -9
  69. data/test/integration/assign_test.rb +0 -48
  70. data/test/integration/blank_test.rb +0 -106
  71. data/test/integration/capture_test.rb +0 -50
  72. data/test/integration/context_test.rb +0 -32
  73. data/test/integration/drop_test.rb +0 -271
  74. data/test/integration/error_handling_test.rb +0 -207
  75. data/test/integration/filter_test.rb +0 -138
  76. data/test/integration/hash_ordering_test.rb +0 -23
  77. data/test/integration/output_test.rb +0 -124
  78. data/test/integration/parsing_quirks_test.rb +0 -116
  79. data/test/integration/render_profiling_test.rb +0 -154
  80. data/test/integration/security_test.rb +0 -64
  81. data/test/integration/standard_filter_test.rb +0 -396
  82. data/test/integration/tags/break_tag_test.rb +0 -16
  83. data/test/integration/tags/continue_tag_test.rb +0 -16
  84. data/test/integration/tags/for_tag_test.rb +0 -375
  85. data/test/integration/tags/if_else_tag_test.rb +0 -190
  86. data/test/integration/tags/include_tag_test.rb +0 -234
  87. data/test/integration/tags/increment_tag_test.rb +0 -24
  88. data/test/integration/tags/raw_tag_test.rb +0 -25
  89. data/test/integration/tags/standard_tag_test.rb +0 -297
  90. data/test/integration/tags/statements_test.rb +0 -113
  91. data/test/integration/tags/table_row_test.rb +0 -63
  92. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  93. data/test/integration/template_test.rb +0 -182
  94. data/test/integration/variable_test.rb +0 -82
  95. data/test/test_helper.rb +0 -89
  96. data/test/unit/block_unit_test.rb +0 -55
  97. data/test/unit/condition_unit_test.rb +0 -149
  98. data/test/unit/context_unit_test.rb +0 -492
  99. data/test/unit/file_system_unit_test.rb +0 -35
  100. data/test/unit/i18n_unit_test.rb +0 -37
  101. data/test/unit/lexer_unit_test.rb +0 -48
  102. data/test/unit/module_ex_unit_test.rb +0 -87
  103. data/test/unit/parser_unit_test.rb +0 -82
  104. data/test/unit/regexp_unit_test.rb +0 -44
  105. data/test/unit/strainer_unit_test.rb +0 -69
  106. data/test/unit/tag_unit_test.rb +0 -16
  107. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  108. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  109. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  110. data/test/unit/template_unit_test.rb +0 -69
  111. data/test/unit/tokenizer_unit_test.rb +0 -38
  112. data/test/unit/variable_unit_test.rb +0 -145
  113. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -1,106 +1,140 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- # If is the conditional block
3
- #
4
- # {% if user.admin %}
5
- # Admin user!
6
- # {% else %}
7
- # Not admin user
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category conditional
7
+ # @liquid_name if
8
+ # @liquid_summary
9
+ # Renders an expression if a specific condition is `true`.
10
+ # @liquid_syntax
11
+ # {% if condition %}
12
+ # expression
8
13
  # {% endif %}
9
- #
10
- # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
11
- #
14
+ # @liquid_syntax_keyword condition The condition to evaluate.
15
+ # @liquid_syntax_keyword expression The expression to render if the condition is met.
12
16
  class If < Block
13
- Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
17
+ Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
14
18
  ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
15
- BOOLEAN_OPERATORS = %w(and or)
19
+ BOOLEAN_OPERATORS = %w(and or).freeze
20
+
21
+ attr_reader :blocks
16
22
 
17
23
  def initialize(tag_name, markup, options)
18
24
  super
19
25
  @blocks = []
20
- push_block('if'.freeze, markup)
26
+ push_block('if', markup)
21
27
  end
22
28
 
23
29
  def nodelist
24
- @blocks.flat_map(&:attachment)
30
+ @blocks.map(&:attachment)
25
31
  end
26
32
 
33
+ def parse(tokens)
34
+ while parse_body(@blocks.last.attachment, tokens)
35
+ end
36
+ @blocks.reverse_each do |block|
37
+ block.attachment.remove_blank_strings if blank?
38
+ block.attachment.freeze
39
+ end
40
+ end
41
+
42
+ ELSE_TAG_NAMES = ['elsif', 'else'].freeze
43
+ private_constant :ELSE_TAG_NAMES
44
+
27
45
  def unknown_tag(tag, markup, tokens)
28
- if ['elsif'.freeze, 'else'.freeze].include?(tag)
46
+ if ELSE_TAG_NAMES.include?(tag)
29
47
  push_block(tag, markup)
30
48
  else
31
49
  super
32
50
  end
33
51
  end
34
52
 
35
- def render(context)
36
- context.stack do
37
- @blocks.each do |block|
38
- if block.evaluate(context)
39
- return render_all(block.attachment, context)
40
- end
53
+ def render_to_output_buffer(context, output)
54
+ @blocks.each do |block|
55
+ result = Liquid::Utils.to_liquid_value(
56
+ block.evaluate(context)
57
+ )
58
+
59
+ if result
60
+ return block.attachment.render_to_output_buffer(context, output)
41
61
  end
42
- ''.freeze
43
62
  end
63
+
64
+ output
44
65
  end
45
66
 
46
67
  private
47
68
 
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)
69
+ def push_block(tag, markup)
70
+ block = if tag == 'else'
71
+ ElseCondition.new
72
+ else
73
+ parse_with_selected_parser(markup)
57
74
  end
58
75
 
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
76
+ @blocks.push(block)
77
+ block.attach(new_body)
78
+ end
79
+
80
+ def parse_expression(markup)
81
+ Condition.parse_expression(parse_context, markup)
82
+ end
62
83
 
63
- condition = Condition.new($1, $2, $3)
84
+ def lax_parse(markup)
85
+ expressions = markup.scan(ExpressionsAndOperators)
86
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
64
87
 
65
- while not expressions.empty?
66
- operator = expressions.pop.to_s.strip
88
+ condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
67
89
 
68
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
90
+ until expressions.empty?
91
+ operator = expressions.pop.to_s.strip
69
92
 
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
93
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
75
94
 
76
- condition
95
+ new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
96
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
97
+ new_condition.send(operator, condition)
98
+ condition = new_condition
77
99
  end
78
100
 
79
- def strict_parse(markup)
80
- p = Parser.new(markup)
81
- condition = parse_binary_comparison(p)
82
- p.consume(:end_of_string)
83
- condition
101
+ condition
102
+ end
103
+
104
+ def strict_parse(markup)
105
+ p = Parser.new(markup)
106
+ condition = parse_binary_comparisons(p)
107
+ p.consume(:end_of_string)
108
+ condition
109
+ end
110
+
111
+ def parse_binary_comparisons(p)
112
+ condition = parse_comparison(p)
113
+ first_condition = condition
114
+ while (op = (p.id?('and') || p.id?('or')))
115
+ child_condition = parse_comparison(p)
116
+ condition.send(op, child_condition)
117
+ condition = child_condition
84
118
  end
119
+ first_condition
120
+ end
85
121
 
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
122
+ def parse_comparison(p)
123
+ a = parse_expression(p.expression)
124
+ if (op = p.consume?(:comparison))
125
+ b = parse_expression(p.expression)
126
+ Condition.new(a, op, b)
127
+ else
128
+ Condition.new(a)
92
129
  end
130
+ end
93
131
 
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
132
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
133
+ def children
134
+ @node.blocks
102
135
  end
136
+ end
103
137
  end
104
138
 
105
- Template.register_tag('if'.freeze, If)
139
+ Template.register_tag('if', If)
106
140
  end
@@ -1,20 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Ifchanged < Block
5
+ def render_to_output_buffer(context, output)
6
+ block_output = +''
7
+ super(context, block_output)
3
8
 
4
- def render(context)
5
- context.stack do
6
-
7
- output = super
8
-
9
- if output != context.registers[:ifchanged]
10
- context.registers[:ifchanged] = output
11
- output
12
- else
13
- ''.freeze
14
- end
9
+ if block_output != context.registers[:ifchanged]
10
+ context.registers[:ifchanged] = block_output
11
+ output << block_output
15
12
  end
13
+
14
+ output
16
15
  end
17
16
  end
18
17
 
19
- Template.register_tag('ifchanged'.freeze, Ifchanged)
18
+ Template.register_tag('ifchanged', Ifchanged)
20
19
  end
@@ -1,104 +1,113 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
- # Include allows templates to relate with other templates
4
- #
5
- # Simply include another template:
6
- #
7
- # {% include 'product' %}
8
- #
9
- # Include a template with a local variable:
10
- #
11
- # {% include 'product' with products[0] %}
12
- #
13
- # Include a template for a collection:
14
- #
15
- # {% include 'product' for products %}
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category theme
7
+ # @liquid_name include
8
+ # @liquid_summary
9
+ # Renders a [snippet](/themes/architecture#snippets).
10
+ # @liquid_description
11
+ # Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags#variable-tags) outside of the
12
+ # snippet.
13
+ # @liquid_syntax
14
+ # {% include 'filename' %}
15
+ # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
16
+ # @liquid_deprecated
17
+ # Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
16
18
  #
19
+ # The `include` tag has been replaced by [`render`](/api/liquid/tags#render).
17
20
  class Include < Tag
18
- Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
21
+ prepend Tag::Disableable
22
+
23
+ SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
24
+ Syntax = SYNTAX
25
+
26
+ attr_reader :template_name_expr, :variable_name_expr, :attributes
19
27
 
20
28
  def initialize(tag_name, markup, options)
21
29
  super
22
30
 
23
- if markup =~ Syntax
31
+ if markup =~ SYNTAX
24
32
 
25
- @template_name = $1
26
- @variable_name = $3
27
- @attributes = {}
33
+ template_name = Regexp.last_match(1)
34
+ variable_name = Regexp.last_match(3)
35
+
36
+ @alias_name = Regexp.last_match(5)
37
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
38
+ @template_name_expr = parse_expression(template_name)
39
+ @attributes = {}
28
40
 
29
41
  markup.scan(TagAttributes) do |key, value|
30
- @attributes[key] = value
42
+ @attributes[key] = parse_expression(value)
31
43
  end
32
44
 
33
45
  else
34
- raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
46
+ raise SyntaxError, options[:locale].t("errors.syntax.include")
35
47
  end
36
48
  end
37
49
 
38
- def parse(tokens)
50
+ def parse(_tokens)
39
51
  end
40
52
 
41
- def render(context)
42
- partial = load_cached_partial(context)
43
- variable = context[@variable_name || @template_name[1..-2]]
53
+ def render_to_output_buffer(context, output)
54
+ template_name = context.evaluate(@template_name_expr)
55
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
44
56
 
45
- context.stack do
46
- @attributes.each do |key, value|
47
- context[key] = context[value]
48
- end
57
+ partial = PartialCache.load(
58
+ template_name,
59
+ context: context,
60
+ parse_context: parse_context
61
+ )
49
62
 
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
54
- partial.render(context)
55
- end
56
- else
57
- context[context_variable_name] = variable
58
- partial.render(context)
59
- end
63
+ context_variable_name = @alias_name || template_name.split('/').last
64
+
65
+ variable = if @variable_name_expr
66
+ context.evaluate(@variable_name_expr)
67
+ else
68
+ context.find_variable(template_name, raise_on_not_found: false)
60
69
  end
61
- end
62
70
 
63
- private
64
- def load_cached_partial(context)
65
- cached_partials = context.registers[:cached_partials] || {}
66
- template_name = context[@template_name]
71
+ old_template_name = context.template_name
72
+ old_partial = context.partial
73
+ begin
74
+ context.template_name = template_name
75
+ context.partial = true
76
+ context.stack do
77
+ @attributes.each do |key, value|
78
+ context[key] = context.evaluate(value)
79
+ end
67
80
 
68
- if cached = cached_partials[template_name]
69
- return cached
81
+ if variable.is_a?(Array)
82
+ variable.each do |var|
83
+ context[context_variable_name] = var
84
+ partial.render_to_output_buffer(context, output)
85
+ end
86
+ else
87
+ context[context_variable_name] = variable
88
+ partial.render_to_output_buffer(context, output)
89
+ end
70
90
  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
91
+ ensure
92
+ context.template_name = old_template_name
93
+ context.partial = old_partial
76
94
  end
77
95
 
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
96
+ output
97
+ end
91
98
 
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
99
+ alias_method :parse_context, :options
100
+ private :parse_context
101
+
102
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
103
+ def children
104
+ [
105
+ @node.template_name_expr,
106
+ @node.variable_name_expr,
107
+ ] + @node.attributes.values
100
108
  end
109
+ end
101
110
  end
102
111
 
103
- Template.register_tag('include'.freeze, Include)
112
+ Template.register_tag('include', Include)
104
113
  end
@@ -1,31 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- # increment is used in a place where one needs to insert a counter
3
- # into a template, and needs the counter to survive across
4
- # multiple instantiations of the template.
5
- # (To achieve the survival, the application must keep the context)
6
- #
7
- # if the variable does not exist, it is created with value 0.
8
- #
9
- # Hello: {% increment variable %}
10
- #
11
- # gives you:
12
- #
13
- # Hello: 0
14
- # Hello: 1
15
- # Hello: 2
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category variable
7
+ # @liquid_name increment
8
+ # @liquid_summary
9
+ # Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.
10
+ # @liquid_description
11
+ # Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
12
+ # or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
13
+ # [snippets](/themes/architecture#snippets) included in the file.
16
14
  #
15
+ # Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags#assign)
16
+ # and [`capture`](/api/liquid/tags#capture). However, `increment` and [`decrement`](/api/liquid/tags#decrement) share
17
+ # variables.
18
+ # @liquid_syntax
19
+ # {% increment variable_name %}
20
+ # @liquid_syntax_keyword variable_name The name of the variable being incremented.
17
21
  class Increment < Tag
18
22
  def initialize(tag_name, markup, options)
19
23
  super
20
24
  @variable = markup.strip
21
25
  end
22
26
 
23
- def render(context)
27
+ def render_to_output_buffer(context, output)
24
28
  value = context.environments.first[@variable] ||= 0
25
29
  context.environments.first[@variable] = value + 1
26
- value.to_s
30
+
31
+ output << value.to_s
32
+ output
27
33
  end
28
34
  end
29
35
 
30
- Template.register_tag('increment'.freeze, Increment)
36
+ Template.register_tag('increment', Increment)
31
37
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category syntax
7
+ # @liquid_name inline_comment
8
+ # @liquid_summary
9
+ # Prevents an expression from being rendered or output.
10
+ # @liquid_description
11
+ # Any text inside an `inline_comment` tag won't be rendered or output.
12
+ #
13
+ # You can create multi-line inline comments. However, each line must begin with a `#`.
14
+ # @liquid_syntax
15
+ # {% # content %}
16
+ # @liquid_syntax_keyword content The content of the comment.
17
+ class InlineComment < Tag
18
+ def initialize(tag_name, markup, options)
19
+ super
20
+
21
+ # Semantically, a comment should only ignore everything after it on the line.
22
+ # Currently, this implementation doesn't support mixing a comment with another tag
23
+ # but we need to reserve future support for this and prevent the introduction
24
+ # of inline comments from being backward incompatible change.
25
+ #
26
+ # As such, we're forcing users to put a # symbol on every line otherwise this
27
+ # tag will throw an error.
28
+ if markup.match?(/\n\s*[^#\s]/)
29
+ raise SyntaxError, options[:locale].t("errors.syntax.inline_comment_invalid")
30
+ end
31
+ end
32
+
33
+ def render_to_output_buffer(_context, output)
34
+ output
35
+ end
36
+
37
+ def blank?
38
+ true
39
+ end
40
+ end
41
+
42
+ Template.register_tag('#', InlineComment)
43
+ end
@@ -1,19 +1,61 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category syntax
7
+ # @liquid_name raw
8
+ # @liquid_summary
9
+ # Outputs any Liquid code as text instead of rendering it.
10
+ # @liquid_syntax
11
+ # {% raw %}
12
+ # expression
13
+ # {% endraw %}
14
+ # @liquid_syntax_keyword expression The expression to be output without being rendered.
2
15
  class Raw < Block
16
+ Syntax = /\A\s*\z/
3
17
  FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
4
18
 
19
+ def initialize(tag_name, markup, parse_context)
20
+ super
21
+
22
+ ensure_valid_markup(tag_name, markup, parse_context)
23
+ end
24
+
5
25
  def parse(tokens)
6
- @nodelist ||= []
7
- @nodelist.clear
8
- while token = tokens.shift
9
- if token =~ FullTokenPossiblyInvalid
10
- @nodelist << $1 if $1 != "".freeze
11
- return if block_delimiter == $2
26
+ @body = +''
27
+ while (token = tokens.shift)
28
+ if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
29
+ @body << Regexp.last_match(1) if Regexp.last_match(1) != ""
30
+ return
12
31
  end
13
- @nodelist << token if not token.empty?
32
+ @body << token unless token.empty?
33
+ end
34
+
35
+ raise_tag_never_closed(block_name)
36
+ end
37
+
38
+ def render_to_output_buffer(_context, output)
39
+ output << @body
40
+ output
41
+ end
42
+
43
+ def nodelist
44
+ [@body]
45
+ end
46
+
47
+ def blank?
48
+ @body.empty?
49
+ end
50
+
51
+ protected
52
+
53
+ def ensure_valid_markup(tag_name, markup, parse_context)
54
+ unless Syntax.match?(markup)
55
+ raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name)
14
56
  end
15
57
  end
16
58
  end
17
59
 
18
- Template.register_tag('raw'.freeze, Raw)
60
+ Template.register_tag('raw', Raw)
19
61
  end