liquid 4.0.0 → 5.10.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.
- checksums.yaml +5 -5
- data/History.md +235 -2
- data/README.md +58 -8
- data/lib/liquid/block.rb +51 -20
- data/lib/liquid/block_body.rb +216 -82
- data/lib/liquid/condition.rb +83 -32
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +130 -59
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +8 -2
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/errors.rb +23 -20
- data/lib/liquid/expression.rb +114 -31
- data/lib/liquid/extensions.rb +8 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +51 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +165 -39
- data/lib/liquid/locales/en.yml +16 -6
- data/lib/liquid/parse_context.rb +62 -7
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +31 -19
- data/lib/liquid/parser_switching.rb +42 -3
- data/lib/liquid/partial_cache.rb +33 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +26 -6
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/snippet_drop.rb +22 -0
- data/lib/liquid/standardfilters.rb +813 -137
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +64 -5
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +13 -0
- data/lib/liquid/tag.rb +42 -6
- data/lib/liquid/tags/assign.rb +46 -18
- data/lib/liquid/tags/break.rb +15 -4
- data/lib/liquid/tags/capture.rb +26 -18
- data/lib/liquid/tags/case.rb +108 -32
- data/lib/liquid/tags/comment.rb +76 -4
- data/lib/liquid/tags/continue.rb +15 -13
- data/lib/liquid/tags/cycle.rb +117 -34
- data/lib/liquid/tags/decrement.rb +30 -23
- data/lib/liquid/tags/doc.rb +81 -0
- data/lib/liquid/tags/echo.rb +39 -0
- data/lib/liquid/tags/for.rb +109 -96
- data/lib/liquid/tags/if.rb +72 -41
- data/lib/liquid/tags/ifchanged.rb +10 -11
- data/lib/liquid/tags/include.rb +89 -63
- data/lib/liquid/tags/increment.rb +31 -20
- data/lib/liquid/tags/inline_comment.rb +28 -0
- data/lib/liquid/tags/raw.rb +25 -13
- data/lib/liquid/tags/render.rb +151 -0
- data/lib/liquid/tags/snippet.rb +45 -0
- data/lib/liquid/tags/table_row.rb +104 -21
- data/lib/liquid/tags/unless.rb +37 -20
- data/lib/liquid/tags.rb +51 -0
- data/lib/liquid/template.rb +90 -106
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +143 -13
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +114 -5
- data/lib/liquid/variable.rb +119 -45
- data/lib/liquid/variable_lookup.rb +35 -13
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +31 -18
- metadata +56 -107
- data/lib/liquid/strainer.rb +0 -66
- data/test/fixtures/en_locale.yml +0 -9
- data/test/integration/assign_test.rb +0 -48
- data/test/integration/blank_test.rb +0 -106
- data/test/integration/capture_test.rb +0 -50
- data/test/integration/context_test.rb +0 -32
- data/test/integration/document_test.rb +0 -19
- data/test/integration/drop_test.rb +0 -273
- data/test/integration/error_handling_test.rb +0 -260
- data/test/integration/filter_test.rb +0 -178
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -123
- data/test/integration/parsing_quirks_test.rb +0 -118
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -66
- data/test/integration/standard_filter_test.rb +0 -535
- data/test/integration/tags/break_tag_test.rb +0 -15
- data/test/integration/tags/continue_tag_test.rb +0 -15
- data/test/integration/tags/for_tag_test.rb +0 -410
- data/test/integration/tags/if_else_tag_test.rb +0 -188
- data/test/integration/tags/include_tag_test.rb +0 -238
- data/test/integration/tags/increment_tag_test.rb +0 -23
- data/test/integration/tags/raw_tag_test.rb +0 -31
- data/test/integration/tags/standard_tag_test.rb +0 -296
- data/test/integration/tags/statements_test.rb +0 -111
- data/test/integration/tags/table_row_test.rb +0 -64
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -323
- data/test/integration/trim_mode_test.rb +0 -525
- data/test/integration/variable_test.rb +0 -92
- data/test/test_helper.rb +0 -117
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -158
- data/test/unit/context_unit_test.rb +0 -483
- data/test/unit/file_system_unit_test.rb +0 -35
- data/test/unit/i18n_unit_test.rb +0 -37
- data/test/unit/lexer_unit_test.rb +0 -51
- data/test/unit/parser_unit_test.rb +0 -82
- data/test/unit/regexp_unit_test.rb +0 -44
- data/test/unit/strainer_unit_test.rb +0 -148
- data/test/unit/tag_unit_test.rb +0 -21
- data/test/unit/tags/case_tag_unit_test.rb +0 -10
- data/test/unit/tags/for_tag_unit_test.rb +0 -13
- data/test/unit/tags/if_tag_unit_test.rb +0 -8
- data/test/unit/template_unit_test.rb +0 -78
- data/test/unit/tokenizer_unit_test.rb +0 -55
- data/test/unit/variable_unit_test.rb +0 -162
data/lib/liquid/tags/comment.rb
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
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 comment
|
|
8
|
+
# @liquid_summary
|
|
9
|
+
# Prevents an expression from being rendered or output.
|
|
10
|
+
# @liquid_description
|
|
11
|
+
# Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
|
|
12
|
+
# @liquid_syntax
|
|
13
|
+
# {% comment %}
|
|
14
|
+
# content
|
|
15
|
+
# {% endcomment %}
|
|
16
|
+
# @liquid_syntax_keyword content The content of the comment.
|
|
2
17
|
class Comment < Block
|
|
3
|
-
def
|
|
4
|
-
|
|
18
|
+
def render_to_output_buffer(_context, output)
|
|
19
|
+
output
|
|
5
20
|
end
|
|
6
21
|
|
|
7
22
|
def unknown_tag(_tag, _markup, _tokens)
|
|
@@ -10,7 +25,64 @@ module Liquid
|
|
|
10
25
|
def blank?
|
|
11
26
|
true
|
|
12
27
|
end
|
|
13
|
-
end
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def parse_body(body, tokenizer)
|
|
32
|
+
if parse_context.depth >= MAX_DEPTH
|
|
33
|
+
raise StackLevelError, "Nesting too deep"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
parse_context.depth += 1
|
|
37
|
+
comment_tag_depth = 1
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
# Consume tokens without creating child nodes.
|
|
41
|
+
# The children tag doesn't require to be a valid Liquid except the comment and raw tag.
|
|
42
|
+
# The child comment and raw tag must be closed.
|
|
43
|
+
while (token = tokenizer.send(:shift))
|
|
44
|
+
tag_name = if tokenizer.for_liquid_tag
|
|
45
|
+
next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)
|
|
46
|
+
|
|
47
|
+
tag_name_match = BlockBody::LiquidTagToken.match(token)
|
|
48
|
+
|
|
49
|
+
next if tag_name_match.nil?
|
|
50
|
+
|
|
51
|
+
tag_name_match[1]
|
|
52
|
+
else
|
|
53
|
+
token =~ BlockBody::FullToken
|
|
54
|
+
Regexp.last_match(2)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
case tag_name
|
|
58
|
+
when "raw"
|
|
59
|
+
parse_raw_tag_body(tokenizer)
|
|
60
|
+
when "comment"
|
|
61
|
+
comment_tag_depth += 1
|
|
62
|
+
when "endcomment"
|
|
63
|
+
comment_tag_depth -= 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if comment_tag_depth.zero?
|
|
67
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
|
|
68
|
+
return false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
raise_tag_never_closed(block_name)
|
|
73
|
+
ensure
|
|
74
|
+
parse_context.depth -= 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def parse_raw_tag_body(tokenizer)
|
|
81
|
+
while (token = tokenizer.send(:shift))
|
|
82
|
+
return if token =~ BlockBody::FullTokenPossiblyInvalid && "endraw" == Regexp.last_match(2)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
raise_tag_never_closed("raw")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
16
88
|
end
|
data/lib/liquid/tags/continue.rb
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
4
|
+
# @liquid_public_docs
|
|
5
|
+
# @liquid_type tag
|
|
6
|
+
# @liquid_category iteration
|
|
7
|
+
# @liquid_name continue
|
|
8
|
+
# @liquid_summary
|
|
9
|
+
# Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
|
|
10
|
+
# @liquid_syntax
|
|
11
|
+
# {% continue %}
|
|
11
12
|
class Continue < Tag
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
INTERRUPT = ContinueInterrupt.new.freeze
|
|
14
|
+
|
|
15
|
+
def render_to_output_buffer(context, output)
|
|
16
|
+
context.push_interrupt(INTERRUPT)
|
|
17
|
+
output
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
|
-
|
|
17
|
-
Template.register_tag('continue'.freeze, Continue)
|
|
18
20
|
end
|
data/lib/liquid/tags/cycle.rb
CHANGED
|
@@ -1,57 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# <div class="blue"> Item three </div>
|
|
11
|
-
# <div class="red"> Item four </div>
|
|
12
|
-
# <div class="green"> Item five</div>
|
|
4
|
+
# @liquid_public_docs
|
|
5
|
+
# @liquid_type tag
|
|
6
|
+
# @liquid_category iteration
|
|
7
|
+
# @liquid_name cycle
|
|
8
|
+
# @liquid_summary
|
|
9
|
+
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/docs/api/liquid/tags/for).
|
|
10
|
+
# @liquid_description
|
|
11
|
+
# The `cycle` tag must be used inside a `for` loop.
|
|
13
12
|
#
|
|
13
|
+
# > Tip:
|
|
14
|
+
# > Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.
|
|
15
|
+
# @liquid_syntax
|
|
16
|
+
# {% cycle string, string, ... %}
|
|
14
17
|
class Cycle < Tag
|
|
15
18
|
SimpleSyntax = /\A#{QuotedFragment}+/o
|
|
16
19
|
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
|
20
|
+
UNNAMED_CYCLE_PATTERN = /\w+:0x\h{8}/
|
|
21
|
+
|
|
22
|
+
attr_reader :variables
|
|
17
23
|
|
|
18
24
|
def initialize(tag_name, markup, options)
|
|
19
25
|
super
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@variables = variables_from_string(markup)
|
|
26
|
-
@name = @variables.to_s
|
|
27
|
-
else
|
|
28
|
-
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
|
|
29
|
-
end
|
|
26
|
+
parse_with_selected_parser(markup)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def named?
|
|
30
|
+
@is_named
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
def
|
|
33
|
-
context.registers[:cycle] ||=
|
|
33
|
+
def render_to_output_buffer(context, output)
|
|
34
|
+
context.registers[:cycle] ||= {}
|
|
34
35
|
|
|
35
|
-
context.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
key = context.evaluate(@name)
|
|
37
|
+
iteration = context.registers[:cycle][key].to_i
|
|
38
|
+
|
|
39
|
+
val = context.evaluate(@variables[iteration])
|
|
40
|
+
|
|
41
|
+
if val.is_a?(Array)
|
|
42
|
+
val = val.join
|
|
43
|
+
elsif !val.is_a?(String)
|
|
44
|
+
val = val.to_s
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
output << val
|
|
48
|
+
|
|
49
|
+
iteration += 1
|
|
50
|
+
iteration = 0 if iteration >= @variables.size
|
|
51
|
+
|
|
52
|
+
context.registers[:cycle][key] = iteration
|
|
53
|
+
output
|
|
44
54
|
end
|
|
45
55
|
|
|
46
56
|
private
|
|
47
57
|
|
|
58
|
+
# cycle [name:] expression(, expression)*
|
|
59
|
+
def rigid_parse(markup)
|
|
60
|
+
p = @parse_context.new_parser(markup)
|
|
61
|
+
|
|
62
|
+
@variables = []
|
|
63
|
+
|
|
64
|
+
raise SyntaxError, options[:locale].t("errors.syntax.cycle") if p.look(:end_of_string)
|
|
65
|
+
|
|
66
|
+
first_expression = safe_parse_expression(p)
|
|
67
|
+
if p.look(:colon)
|
|
68
|
+
# cycle name: expr1, expr2, ...
|
|
69
|
+
@name = first_expression
|
|
70
|
+
@is_named = true
|
|
71
|
+
p.consume(:colon)
|
|
72
|
+
# After the colon, parse the first variable (required for named cycles)
|
|
73
|
+
@variables << maybe_dup_lookup(safe_parse_expression(p))
|
|
74
|
+
else
|
|
75
|
+
# cycle expr1, expr2, ...
|
|
76
|
+
@variables << maybe_dup_lookup(first_expression)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Parse remaining comma-separated expressions
|
|
80
|
+
while p.consume?(:comma)
|
|
81
|
+
break if p.look(:end_of_string)
|
|
82
|
+
|
|
83
|
+
@variables << maybe_dup_lookup(safe_parse_expression(p))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
p.consume(:end_of_string)
|
|
87
|
+
|
|
88
|
+
unless @is_named
|
|
89
|
+
@name = @variables.to_s
|
|
90
|
+
@is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def strict_parse(markup)
|
|
95
|
+
lax_parse(markup)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def lax_parse(markup)
|
|
99
|
+
case markup
|
|
100
|
+
when NamedSyntax
|
|
101
|
+
@variables = variables_from_string(Regexp.last_match(2))
|
|
102
|
+
@name = parse_expression(Regexp.last_match(1))
|
|
103
|
+
@is_named = true
|
|
104
|
+
when SimpleSyntax
|
|
105
|
+
@variables = variables_from_string(markup)
|
|
106
|
+
@name = @variables.to_s
|
|
107
|
+
@is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)
|
|
108
|
+
else
|
|
109
|
+
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
48
113
|
def variables_from_string(markup)
|
|
49
114
|
markup.split(',').collect do |var|
|
|
50
115
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
|
51
|
-
|
|
116
|
+
next unless Regexp.last_match(1)
|
|
117
|
+
|
|
118
|
+
var = parse_expression(Regexp.last_match(1))
|
|
119
|
+
maybe_dup_lookup(var)
|
|
52
120
|
end.compact
|
|
53
121
|
end
|
|
54
|
-
end
|
|
55
122
|
|
|
56
|
-
|
|
123
|
+
# For backwards compatibility, whenever a lookup is used in an unnamed cycle,
|
|
124
|
+
# we make it so that the @variables.to_s produces different strings for cycles
|
|
125
|
+
# called with the same arguments (since @variables.to_s is used as the cycle counter key)
|
|
126
|
+
# This makes it so {% cycle a, b %} and {% cycle a, b %} have independent counters even if a and b share value.
|
|
127
|
+
# This is not true for literal values, {% cycle "a", "b" %} and {% cycle "a", "b" %} share the same counter.
|
|
128
|
+
# I was really scratching my head about this one, but migrating away from this would be more headache
|
|
129
|
+
# than it's worth. So we're keeping this quirk for now.
|
|
130
|
+
def maybe_dup_lookup(var)
|
|
131
|
+
var.is_a?(VariableLookup) ? var.dup : var
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
135
|
+
def children
|
|
136
|
+
Array(@node.variables)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
57
140
|
end
|
|
@@ -1,35 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
# decrement 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
|
-
# NOTE: decrement is a pre-decrement, --i,
|
|
6
|
-
# while increment is post: i++.
|
|
7
|
-
#
|
|
8
|
-
# (To achieve the survival, the application must keep the context)
|
|
9
|
-
#
|
|
10
|
-
# if the variable does not exist, it is created with value 0.
|
|
1
|
+
# frozen_string_literal: true
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
#
|
|
3
|
+
module Liquid
|
|
4
|
+
# @liquid_public_docs
|
|
5
|
+
# @liquid_type tag
|
|
6
|
+
# @liquid_category variable
|
|
7
|
+
# @liquid_name decrement
|
|
8
|
+
# @liquid_summary
|
|
9
|
+
# Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
|
|
15
10
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
11
|
+
# > Caution:
|
|
12
|
+
# > Predefined Liquid objects can be overridden by variables with the same name.
|
|
13
|
+
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
|
|
14
|
+
# @liquid_description
|
|
15
|
+
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
|
16
|
+
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
|
17
|
+
# [snippets](/themes/architecture/snippets) included in the file.
|
|
19
18
|
#
|
|
19
|
+
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
|
|
20
|
+
# and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
|
|
21
|
+
# variables.
|
|
22
|
+
# @liquid_syntax
|
|
23
|
+
# {% decrement variable_name %}
|
|
24
|
+
# @liquid_syntax_keyword variable_name The name of the variable being decremented.
|
|
20
25
|
class Decrement < Tag
|
|
26
|
+
attr_reader :variable_name
|
|
27
|
+
|
|
21
28
|
def initialize(tag_name, markup, options)
|
|
22
29
|
super
|
|
23
|
-
@
|
|
30
|
+
@variable_name = markup.strip
|
|
24
31
|
end
|
|
25
32
|
|
|
26
|
-
def
|
|
27
|
-
|
|
33
|
+
def render_to_output_buffer(context, output)
|
|
34
|
+
counter_environment = context.environments.first
|
|
35
|
+
value = counter_environment[@variable_name] || 0
|
|
28
36
|
value -= 1
|
|
29
|
-
|
|
30
|
-
value.to_s
|
|
37
|
+
counter_environment[@variable_name] = value
|
|
38
|
+
output << value.to_s
|
|
39
|
+
output
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
|
-
|
|
34
|
-
Template.register_tag('decrement'.freeze, Decrement)
|
|
35
42
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
# @liquid_public_docs
|
|
5
|
+
# @liquid_type tag
|
|
6
|
+
# @liquid_category syntax
|
|
7
|
+
# @liquid_name doc
|
|
8
|
+
# @liquid_summary
|
|
9
|
+
# Documents template elements with annotations.
|
|
10
|
+
# @liquid_description
|
|
11
|
+
# The `doc` tag allows developers to include documentation within Liquid
|
|
12
|
+
# templates. Any content inside `doc` tags is not rendered or outputted.
|
|
13
|
+
# Liquid code inside will be parsed but not executed. This facilitates
|
|
14
|
+
# tooling support for features like code completion, linting, and inline
|
|
15
|
+
# documentation.
|
|
16
|
+
#
|
|
17
|
+
# For detailed documentation syntax and examples, see the
|
|
18
|
+
# [`LiquidDoc` reference](/docs/storefronts/themes/tools/liquid-doc).
|
|
19
|
+
#
|
|
20
|
+
# @liquid_syntax
|
|
21
|
+
# {% doc %}
|
|
22
|
+
# Renders a message.
|
|
23
|
+
#
|
|
24
|
+
# @param {string} foo - A string value.
|
|
25
|
+
# @param {string} [bar] - An optional string value.
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# {% render 'message', foo: 'Hello', bar: 'World' %}
|
|
29
|
+
# {% enddoc %}
|
|
30
|
+
class Doc < Block
|
|
31
|
+
NO_UNEXPECTED_ARGS = /\A\s*\z/
|
|
32
|
+
|
|
33
|
+
def initialize(tag_name, markup, parse_context)
|
|
34
|
+
super
|
|
35
|
+
ensure_valid_markup(tag_name, markup, parse_context)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse(tokens)
|
|
39
|
+
@body = +""
|
|
40
|
+
|
|
41
|
+
while (token = tokens.shift)
|
|
42
|
+
tag_name = token =~ BlockBody::FullTokenPossiblyInvalid && Regexp.last_match(2)
|
|
43
|
+
|
|
44
|
+
raise_nested_doc_error if tag_name == @tag_name
|
|
45
|
+
|
|
46
|
+
if tag_name == block_delimiter
|
|
47
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
|
48
|
+
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
@body << token unless token.empty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
raise_tag_never_closed(block_name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def render_to_output_buffer(_context, output)
|
|
58
|
+
output
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def blank?
|
|
62
|
+
@body.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def nodelist
|
|
66
|
+
[@body]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def ensure_valid_markup(tag_name, markup, parse_context)
|
|
72
|
+
unless NO_UNEXPECTED_ARGS.match?(markup)
|
|
73
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.block_tag_unexpected_args", tag: tag_name)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def raise_nested_doc_error
|
|
78
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.doc_invalid_nested")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
# @liquid_public_docs
|
|
5
|
+
# @liquid_type tag
|
|
6
|
+
# @liquid_category syntax
|
|
7
|
+
# @liquid_name echo
|
|
8
|
+
# @liquid_summary
|
|
9
|
+
# Outputs an expression.
|
|
10
|
+
# @liquid_description
|
|
11
|
+
# Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
|
|
12
|
+
# bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).
|
|
13
|
+
#
|
|
14
|
+
# > Tip:
|
|
15
|
+
# > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
|
|
16
|
+
# @liquid_syntax
|
|
17
|
+
# {% liquid
|
|
18
|
+
# echo expression
|
|
19
|
+
# %}
|
|
20
|
+
# @liquid_syntax_keyword expression The expression to be output.
|
|
21
|
+
class Echo < Tag
|
|
22
|
+
attr_reader :variable
|
|
23
|
+
|
|
24
|
+
def initialize(tag_name, markup, parse_context)
|
|
25
|
+
super
|
|
26
|
+
@variable = Variable.new(markup, parse_context)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def render(context)
|
|
30
|
+
@variable.render_to_output_buffer(context, +'')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
34
|
+
def children
|
|
35
|
+
[@node.variable]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|