liquid 4.0.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +89 -0
  3. data/README.md +10 -4
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +169 -57
  6. data/lib/liquid/condition.rb +48 -21
  7. data/lib/liquid/context.rb +111 -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 +28 -32
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +54 -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 +8 -5
  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/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +551 -114
  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 +64 -5
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tags/assign.rb +36 -18
  37. data/lib/liquid/tags/break.rb +16 -3
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +61 -27
  40. data/lib/liquid/tags/comment.rb +18 -3
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +37 -25
  43. data/lib/liquid/tags/decrement.rb +22 -20
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +90 -87
  46. data/lib/liquid/tags/if.rb +50 -32
  47. data/lib/liquid/tags/ifchanged.rb +11 -10
  48. data/lib/liquid/tags/include.rb +49 -60
  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 +25 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +45 -19
  54. data/lib/liquid/tags/unless.rb +38 -19
  55. data/lib/liquid/template.rb +52 -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 +49 -44
  61. data/lib/liquid/variable_lookup.rb +18 -10
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +18 -6
  64. metadata +20 -108
  65. data/lib/liquid/strainer.rb +0 -66
  66. data/lib/liquid/truffle.rb +0 -5
  67. data/test/fixtures/en_locale.yml +0 -9
  68. data/test/integration/assign_test.rb +0 -48
  69. data/test/integration/blank_test.rb +0 -106
  70. data/test/integration/block_test.rb +0 -12
  71. data/test/integration/capture_test.rb +0 -50
  72. data/test/integration/context_test.rb +0 -32
  73. data/test/integration/document_test.rb +0 -19
  74. data/test/integration/drop_test.rb +0 -273
  75. data/test/integration/error_handling_test.rb +0 -260
  76. data/test/integration/filter_test.rb +0 -178
  77. data/test/integration/hash_ordering_test.rb +0 -23
  78. data/test/integration/output_test.rb +0 -123
  79. data/test/integration/parse_tree_visitor_test.rb +0 -247
  80. data/test/integration/parsing_quirks_test.rb +0 -122
  81. data/test/integration/render_profiling_test.rb +0 -154
  82. data/test/integration/security_test.rb +0 -80
  83. data/test/integration/standard_filter_test.rb +0 -776
  84. data/test/integration/tags/break_tag_test.rb +0 -15
  85. data/test/integration/tags/continue_tag_test.rb +0 -15
  86. data/test/integration/tags/for_tag_test.rb +0 -410
  87. data/test/integration/tags/if_else_tag_test.rb +0 -188
  88. data/test/integration/tags/include_tag_test.rb +0 -253
  89. data/test/integration/tags/increment_tag_test.rb +0 -23
  90. data/test/integration/tags/raw_tag_test.rb +0 -31
  91. data/test/integration/tags/standard_tag_test.rb +0 -296
  92. data/test/integration/tags/statements_test.rb +0 -111
  93. data/test/integration/tags/table_row_test.rb +0 -64
  94. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  95. data/test/integration/template_test.rb +0 -332
  96. data/test/integration/trim_mode_test.rb +0 -529
  97. data/test/integration/variable_test.rb +0 -96
  98. data/test/test_helper.rb +0 -116
  99. data/test/truffle/truffle_test.rb +0 -9
  100. data/test/unit/block_unit_test.rb +0 -58
  101. data/test/unit/condition_unit_test.rb +0 -166
  102. data/test/unit/context_unit_test.rb +0 -489
  103. data/test/unit/file_system_unit_test.rb +0 -35
  104. data/test/unit/i18n_unit_test.rb +0 -37
  105. data/test/unit/lexer_unit_test.rb +0 -51
  106. data/test/unit/parser_unit_test.rb +0 -82
  107. data/test/unit/regexp_unit_test.rb +0 -44
  108. data/test/unit/strainer_unit_test.rb +0 -164
  109. data/test/unit/tag_unit_test.rb +0 -21
  110. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  111. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  112. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  113. data/test/unit/template_unit_test.rb +0 -78
  114. data/test/unit/tokenizer_unit_test.rb +0 -55
  115. data/test/unit/variable_unit_test.rb +0 -162
@@ -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,53 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- # Include allows templates to relate with other templates
3
- #
4
- # Simply include another template:
5
- #
6
- # {% include 'product' %}
7
- #
8
- # Include a template with a local variable:
9
- #
10
- # {% include 'product' with products[0] %}
11
- #
12
- # Include a template for a collection:
13
- #
14
- # {% include 'product' for products %}
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.
15
18
  #
19
+ # The `include` tag has been replaced by [`render`](/api/liquid/tags#render).
16
20
  class Include < Tag
17
- 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
18
25
 
19
26
  attr_reader :template_name_expr, :variable_name_expr, :attributes
20
27
 
21
28
  def initialize(tag_name, markup, options)
22
29
  super
23
30
 
24
- if markup =~ Syntax
31
+ if markup =~ SYNTAX
25
32
 
26
- template_name = $1
27
- variable_name = $3
33
+ template_name = Regexp.last_match(1)
34
+ variable_name = Regexp.last_match(3)
28
35
 
29
- @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
30
- @template_name_expr = Expression.parse(template_name)
31
- @attributes = {}
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 = {}
32
40
 
33
41
  markup.scan(TagAttributes) do |key, value|
34
- @attributes[key] = Expression.parse(value)
42
+ @attributes[key] = parse_expression(value)
35
43
  end
36
44
 
37
45
  else
38
- raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
46
+ raise SyntaxError, options[:locale].t("errors.syntax.include")
39
47
  end
40
48
  end
41
49
 
42
50
  def parse(_tokens)
43
51
  end
44
52
 
45
- def render(context)
53
+ def render_to_output_buffer(context, output)
46
54
  template_name = context.evaluate(@template_name_expr)
47
- raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
55
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
56
+
57
+ partial = PartialCache.load(
58
+ template_name,
59
+ context: context,
60
+ parse_context: parse_context
61
+ )
48
62
 
49
- partial = load_cached_partial(template_name, context)
50
- context_variable_name = template_name.split('/'.freeze).last
63
+ context_variable_name = @alias_name || template_name.split('/').last
51
64
 
52
65
  variable = if @variable_name_expr
53
66
  context.evaluate(@variable_name_expr)
@@ -56,69 +69,45 @@ module Liquid
56
69
  end
57
70
 
58
71
  old_template_name = context.template_name
59
- old_partial = context.partial
72
+ old_partial = context.partial
60
73
  begin
61
74
  context.template_name = template_name
62
- context.partial = true
75
+ context.partial = true
63
76
  context.stack do
64
77
  @attributes.each do |key, value|
65
78
  context[key] = context.evaluate(value)
66
79
  end
67
80
 
68
81
  if variable.is_a?(Array)
69
- variable.collect do |var|
82
+ variable.each do |var|
70
83
  context[context_variable_name] = var
71
- partial.render(context)
84
+ partial.render_to_output_buffer(context, output)
72
85
  end
73
86
  else
74
87
  context[context_variable_name] = variable
75
- partial.render(context)
88
+ partial.render_to_output_buffer(context, output)
76
89
  end
77
90
  end
78
91
  ensure
79
92
  context.template_name = old_template_name
80
- context.partial = old_partial
93
+ context.partial = old_partial
81
94
  end
82
- end
83
95
 
84
- private
96
+ output
97
+ end
85
98
 
86
99
  alias_method :parse_context, :options
87
100
  private :parse_context
88
101
 
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
102
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
114
103
  def children
115
104
  [
116
105
  @node.template_name_expr,
117
- @node.variable_name_expr
106
+ @node.variable_name_expr,
118
107
  ] + @node.attributes.values
119
108
  end
120
109
  end
121
110
  end
122
111
 
123
- Template.register_tag('include'.freeze, Include)
112
+ Template.register_tag('include', Include)
124
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,4 +1,17 @@
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
3
16
  Syntax = /\A\s*\z/
4
17
  FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
@@ -10,20 +23,21 @@ module Liquid
10
23
  end
11
24
 
12
25
  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
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
18
31
  end
19
32
  @body << token unless token.empty?
20
33
  end
21
34
 
22
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
35
+ raise_tag_never_closed(block_name)
23
36
  end
24
37
 
25
- def render(_context)
26
- @body
38
+ def render_to_output_buffer(_context, output)
39
+ output << @body
40
+ output
27
41
  end
28
42
 
29
43
  def nodelist
@@ -37,11 +51,11 @@ module Liquid
37
51
  protected
38
52
 
39
53
  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))
54
+ unless Syntax.match?(markup)
55
+ raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name)
42
56
  end
43
57
  end
44
58
  end
45
59
 
46
- Template.register_tag('raw'.freeze, Raw)
60
+ Template.register_tag('raw', Raw)
47
61
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category theme
7
+ # @liquid_name render
8
+ # @liquid_summary
9
+ # Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
10
+ # @liquid_description
11
+ # Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags#variable-tags) outside
12
+ # of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags#render-passing-variables-to-snippets)
13
+ # to pass outside variables to snippets.
14
+ #
15
+ # While you can't directly access created variables, you can access global objects, as well as any objects that are
16
+ # directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
17
+ # can access the [`product` object](/api/liquid/objects#product), and a snippet or app block inside a [section](/themes/architecture/sections)
18
+ # can access the [`section` object](/api/liquid/objects#section).
19
+ #
20
+ # Outside a snippet or app block, you can't access variables created inside the snippet or app block.
21
+ #
22
+ # > Note:
23
+ # > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags#include)
24
+ # > inside the snippet.
25
+ # @liquid_syntax
26
+ # {% render 'filename' %}
27
+ # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
28
+ class Render < Tag
29
+ FOR = 'for'
30
+ SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
31
+
32
+ disable_tags "include"
33
+
34
+ attr_reader :template_name_expr, :variable_name_expr, :attributes
35
+
36
+ def initialize(tag_name, markup, options)
37
+ super
38
+
39
+ raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
40
+
41
+ template_name = Regexp.last_match(1)
42
+ with_or_for = Regexp.last_match(3)
43
+ variable_name = Regexp.last_match(4)
44
+
45
+ @alias_name = Regexp.last_match(6)
46
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
47
+ @template_name_expr = parse_expression(template_name)
48
+ @for = (with_or_for == FOR)
49
+
50
+ @attributes = {}
51
+ markup.scan(TagAttributes) do |key, value|
52
+ @attributes[key] = parse_expression(value)
53
+ end
54
+ end
55
+
56
+ def render_to_output_buffer(context, output)
57
+ render_tag(context, output)
58
+ end
59
+
60
+ def render_tag(context, output)
61
+ # The expression should be a String literal, which parses to a String object
62
+ template_name = @template_name_expr
63
+ raise ::ArgumentError unless template_name.is_a?(String)
64
+
65
+ partial = PartialCache.load(
66
+ template_name,
67
+ context: context,
68
+ parse_context: parse_context
69
+ )
70
+
71
+ context_variable_name = @alias_name || template_name.split('/').last
72
+
73
+ render_partial_func = ->(var, forloop) {
74
+ inner_context = context.new_isolated_subcontext
75
+ inner_context.template_name = template_name
76
+ inner_context.partial = true
77
+ inner_context['forloop'] = forloop if forloop
78
+
79
+ @attributes.each do |key, value|
80
+ inner_context[key] = context.evaluate(value)
81
+ end
82
+ inner_context[context_variable_name] = var unless var.nil?
83
+ partial.render_to_output_buffer(inner_context, output)
84
+ forloop&.send(:increment!)
85
+ }
86
+
87
+ variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
88
+ if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
89
+ forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
90
+ variable.each { |var| render_partial_func.call(var, forloop) }
91
+ else
92
+ render_partial_func.call(variable, nil)
93
+ end
94
+
95
+ output
96
+ end
97
+
98
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
99
+ def children
100
+ [
101
+ @node.template_name_expr,
102
+ @node.variable_name_expr,
103
+ ] + @node.attributes.values
104
+ end
105
+ end
106
+ end
107
+
108
+ Template.register_tag('render', Render)
109
+ end
@@ -1,4 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category iteration
7
+ # @liquid_name tablerow
8
+ # @liquid_summary
9
+ # Generates HTML table rows for every item in an array.
10
+ # @liquid_description
11
+ # The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
12
+ #
13
+ # > Tip:
14
+ # > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects#tablerowloop) with information about the loop.
15
+ # @liquid_syntax
16
+ # {% tablerow variable in array %}
17
+ # expression
18
+ # {% endtablerow %}
19
+ # @liquid_syntax_keyword variable The current item in the array.
20
+ # @liquid_syntax_keyword array The array to iterate over.
21
+ # @liquid_syntax_keyword expression The expression to render.
22
+ # @liquid_optional_param cols [number] The number of columns that the table should have.
23
+ # @liquid_optional_param limit [number] The number of iterations to perform.
24
+ # @liquid_optional_param offset [number] The 1-based index to start iterating at.
25
+ # @liquid_optional_param range [untyped] A custom numeric range to iterate over.
2
26
  class TableRow < Block
3
27
  Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
4
28
 
@@ -7,48 +31,50 @@ module Liquid
7
31
  def initialize(tag_name, markup, options)
8
32
  super
9
33
  if markup =~ Syntax
10
- @variable_name = $1
11
- @collection_name = Expression.parse($2)
12
- @attributes = {}
34
+ @variable_name = Regexp.last_match(1)
35
+ @collection_name = parse_expression(Regexp.last_match(2))
36
+ @attributes = {}
13
37
  markup.scan(TagAttributes) do |key, value|
14
- @attributes[key] = Expression.parse(value)
38
+ @attributes[key] = parse_expression(value)
15
39
  end
16
40
  else
17
- raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
41
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row")
18
42
  end
19
43
  end
20
44
 
21
- def render(context)
22
- collection = context.evaluate(@collection_name) or return ''.freeze
45
+ def render_to_output_buffer(context, output)
46
+ (collection = context.evaluate(@collection_name)) || (return '')
23
47
 
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
48
+ from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
49
+ to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
26
50
 
27
51
  collection = Utils.slice_collection(collection, from, to)
52
+ length = collection.length
28
53
 
29
- length = collection.length
30
-
31
- cols = context.evaluate(@attributes['cols'.freeze]).to_i
54
+ cols = context.evaluate(@attributes['cols']).to_i
32
55
 
33
- result = "<tr class=\"row1\">\n"
56
+ output << "<tr class=\"row1\">\n"
34
57
  context.stack do
35
58
  tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
36
- context['tablerowloop'.freeze] = tablerowloop
59
+ context['tablerowloop'] = tablerowloop
37
60
 
38
61
  collection.each do |item|
39
62
  context[@variable_name] = item
40
63
 
41
- result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
64
+ output << "<td class=\"col#{tablerowloop.col}\">"
65
+ super
66
+ output << '</td>'
42
67
 
43
68
  if tablerowloop.col_last && !tablerowloop.last
44
- result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
69
+ output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
45
70
  end
46
71
 
47
72
  tablerowloop.send(:increment!)
48
73
  end
49
74
  end
50
- result << "</tr>\n"
51
- result
75
+
76
+ output << "</tr>\n"
77
+ output
52
78
  end
53
79
 
54
80
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
@@ -58,5 +84,5 @@ module Liquid
58
84
  end
59
85
  end
60
86
 
61
- Template.register_tag('tablerow'.freeze, TableRow)
87
+ Template.register_tag('tablerow', TableRow)
62
88
  end
@@ -1,30 +1,49 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'if'
2
4
 
3
5
  module Liquid
4
- # Unless is a conditional just like 'if' but works on the inverse logic.
5
- #
6
- # {% unless x < 0 %} x is greater than zero {% endunless %}
7
- #
6
+ # @liquid_public_docs
7
+ # @liquid_type tag
8
+ # @liquid_category conditional
9
+ # @liquid_name unless
10
+ # @liquid_summary
11
+ # Renders an expression unless a specific condition is `true`.
12
+ # @liquid_description
13
+ # > Tip:
14
+ # > Similar to the [`if` tag](/api/liquid/tags#if), you can use `elsif` to add more conditions to an `unless` tag.
15
+ # @liquid_syntax
16
+ # {% unless condition %}
17
+ # expression
18
+ # {% endunless %}
19
+ # @liquid_syntax_keyword condition The condition to evaluate.
20
+ # @liquid_syntax_keyword expression The expression to render unless the condition is met.
8
21
  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
22
+ def render_to_output_buffer(context, output)
23
+ # First condition is interpreted backwards ( if not )
24
+ first_block = @blocks.first
25
+ result = Liquid::Utils.to_liquid_value(
26
+ first_block.evaluate(context)
27
+ )
16
28
 
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
29
+ unless result
30
+ return first_block.attachment.render_to_output_buffer(context, output)
31
+ end
23
32
 
24
- ''.freeze
33
+ # After the first condition unless works just like if
34
+ @blocks[1..-1].each do |block|
35
+ result = Liquid::Utils.to_liquid_value(
36
+ block.evaluate(context)
37
+ )
38
+
39
+ if result
40
+ return block.attachment.render_to_output_buffer(context, output)
41
+ end
25
42
  end
43
+
44
+ output
26
45
  end
27
46
  end
28
47
 
29
- Template.register_tag('unless'.freeze, Unless)
48
+ Template.register_tag('unless', Unless)
30
49
  end