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.
- checksums.yaml +5 -5
- data/History.md +243 -58
- data/README.md +43 -4
- data/lib/liquid/block.rb +57 -123
- data/lib/liquid/block_body.rb +217 -85
- data/lib/liquid/condition.rb +92 -45
- data/lib/liquid/context.rb +154 -89
- data/lib/liquid/document.rb +57 -9
- data/lib/liquid/drop.rb +20 -17
- data/lib/liquid/errors.rb +27 -29
- data/lib/liquid/expression.rb +32 -20
- data/lib/liquid/extensions.rb +21 -7
- data/lib/liquid/file_system.rb +17 -15
- data/lib/liquid/forloop_drop.rb +92 -0
- data/lib/liquid/i18n.rb +10 -8
- data/lib/liquid/interrupts.rb +4 -3
- data/lib/liquid/lexer.rb +37 -26
- data/lib/liquid/locales/en.yml +13 -6
- data/lib/liquid/parse_context.rb +54 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +20 -6
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +72 -92
- data/lib/liquid/range_lookup.rb +28 -3
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +62 -0
- data/lib/liquid/standardfilters.rb +715 -132
- data/lib/liquid/strainer_factory.rb +41 -0
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +121 -0
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tag.rb +35 -12
- data/lib/liquid/tags/assign.rb +57 -18
- data/lib/liquid/tags/break.rb +15 -5
- data/lib/liquid/tags/capture.rb +24 -18
- data/lib/liquid/tags/case.rb +79 -30
- data/lib/liquid/tags/comment.rb +19 -4
- data/lib/liquid/tags/continue.rb +16 -12
- data/lib/liquid/tags/cycle.rb +47 -27
- data/lib/liquid/tags/decrement.rb +23 -24
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +155 -124
- data/lib/liquid/tags/if.rb +97 -63
- data/lib/liquid/tags/ifchanged.rb +11 -12
- data/lib/liquid/tags/include.rb +82 -73
- data/lib/liquid/tags/increment.rb +23 -17
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +50 -8
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +57 -41
- data/lib/liquid/tags/unless.rb +38 -20
- data/lib/liquid/template.rb +71 -103
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +39 -0
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +63 -9
- data/lib/liquid/variable.rb +74 -56
- data/lib/liquid/variable_lookup.rb +31 -15
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +27 -12
- metadata +30 -106
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/strainer.rb +0 -59
- data/lib/liquid/token.rb +0 -18
- 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/drop_test.rb +0 -271
- data/test/integration/error_handling_test.rb +0 -207
- data/test/integration/filter_test.rb +0 -138
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -124
- data/test/integration/parsing_quirks_test.rb +0 -116
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -64
- data/test/integration/standard_filter_test.rb +0 -396
- data/test/integration/tags/break_tag_test.rb +0 -16
- data/test/integration/tags/continue_tag_test.rb +0 -16
- data/test/integration/tags/for_tag_test.rb +0 -375
- data/test/integration/tags/if_else_tag_test.rb +0 -190
- data/test/integration/tags/include_tag_test.rb +0 -234
- data/test/integration/tags/increment_tag_test.rb +0 -24
- data/test/integration/tags/raw_tag_test.rb +0 -25
- data/test/integration/tags/standard_tag_test.rb +0 -297
- data/test/integration/tags/statements_test.rb +0 -113
- data/test/integration/tags/table_row_test.rb +0 -63
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -182
- data/test/integration/variable_test.rb +0 -82
- data/test/test_helper.rb +0 -89
- data/test/unit/block_unit_test.rb +0 -55
- data/test/unit/condition_unit_test.rb +0 -149
- data/test/unit/context_unit_test.rb +0 -492
- 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 -48
- data/test/unit/module_ex_unit_test.rb +0 -87
- 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 -69
- data/test/unit/tag_unit_test.rb +0 -16
- 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 -69
- data/test/unit/tokenizer_unit_test.rb +0 -38
- data/test/unit/variable_unit_test.rb +0 -145
- /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -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,72 +1,88 @@
|
|
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
|
|
29
|
+
attr_reader :variable_name, :collection_name, :attributes
|
30
|
+
|
5
31
|
def initialize(tag_name, markup, options)
|
6
32
|
super
|
7
33
|
if markup =~ Syntax
|
8
|
-
@variable_name
|
9
|
-
@collection_name =
|
10
|
-
@attributes
|
34
|
+
@variable_name = Regexp.last_match(1)
|
35
|
+
@collection_name = parse_expression(Regexp.last_match(2))
|
36
|
+
@attributes = {}
|
11
37
|
markup.scan(TagAttributes) do |key, value|
|
12
|
-
@attributes[key] = value
|
38
|
+
@attributes[key] = parse_expression(value)
|
13
39
|
end
|
14
40
|
else
|
15
|
-
raise SyntaxError
|
41
|
+
raise SyntaxError, options[:locale].t("errors.syntax.table_row")
|
16
42
|
end
|
17
43
|
end
|
18
44
|
|
19
|
-
def
|
20
|
-
collection = context
|
45
|
+
def render_to_output_buffer(context, output)
|
46
|
+
(collection = context.evaluate(@collection_name)) || (return '')
|
21
47
|
|
22
|
-
from = @attributes
|
23
|
-
to
|
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
|
24
50
|
|
25
51
|
collection = Utils.slice_collection(collection, from, to)
|
52
|
+
length = collection.length
|
26
53
|
|
27
|
-
|
28
|
-
|
29
|
-
cols = context[@attributes['cols'.freeze]].to_i
|
54
|
+
cols = context.evaluate(@attributes['cols']).to_i
|
30
55
|
|
31
|
-
|
32
|
-
col = 0
|
33
|
-
|
34
|
-
result = "<tr class=\"row1\">\n"
|
56
|
+
output << "<tr class=\"row1\">\n"
|
35
57
|
context.stack do
|
58
|
+
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
59
|
+
context['tablerowloop'] = tablerowloop
|
36
60
|
|
37
|
-
collection.
|
61
|
+
collection.each do |item|
|
38
62
|
context[@variable_name] = item
|
39
|
-
context['tablerowloop'.freeze] = {
|
40
|
-
'length'.freeze => length,
|
41
|
-
'index'.freeze => index + 1,
|
42
|
-
'index0'.freeze => index,
|
43
|
-
'col'.freeze => col + 1,
|
44
|
-
'col0'.freeze => col,
|
45
|
-
'rindex'.freeze => length - index,
|
46
|
-
'rindex0'.freeze => length - index - 1,
|
47
|
-
'first'.freeze => (index == 0),
|
48
|
-
'last'.freeze => (index == length - 1),
|
49
|
-
'col_first'.freeze => (col == 0),
|
50
|
-
'col_last'.freeze => (col == cols - 1)
|
51
|
-
}
|
52
|
-
|
53
63
|
|
54
|
-
|
64
|
+
output << "<td class=\"col#{tablerowloop.col}\">"
|
65
|
+
super
|
66
|
+
output << '</td>'
|
55
67
|
|
56
|
-
|
57
|
-
|
58
|
-
if col == cols and (index != length - 1)
|
59
|
-
col = 0
|
60
|
-
row += 1
|
61
|
-
result << "</tr>\n<tr class=\"row#{row}\">"
|
68
|
+
if tablerowloop.col_last && !tablerowloop.last
|
69
|
+
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
62
70
|
end
|
63
71
|
|
72
|
+
tablerowloop.send(:increment!)
|
64
73
|
end
|
65
74
|
end
|
66
|
-
|
67
|
-
|
75
|
+
|
76
|
+
output << "</tr>\n"
|
77
|
+
output
|
78
|
+
end
|
79
|
+
|
80
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
81
|
+
def children
|
82
|
+
super + @node.attributes.values + [@node.collection_name]
|
83
|
+
end
|
68
84
|
end
|
69
85
|
end
|
70
86
|
|
71
|
-
Template.register_tag('tablerow'
|
87
|
+
Template.register_tag('tablerow', TableRow)
|
72
88
|
end
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -1,31 +1,49 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'if'
|
2
4
|
|
3
5
|
module Liquid
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
|
10
|
-
|
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
|
+
)
|
11
28
|
|
12
|
-
|
13
|
-
first_block
|
14
|
-
|
15
|
-
return render_all(first_block.attachment, context)
|
16
|
-
end
|
29
|
+
unless result
|
30
|
+
return first_block.attachment.render_to_output_buffer(context, output)
|
31
|
+
end
|
17
32
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
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
|
+
)
|
24
38
|
|
25
|
-
|
39
|
+
if result
|
40
|
+
return block.attachment.render_to_output_buffer(context, output)
|
41
|
+
end
|
26
42
|
end
|
43
|
+
|
44
|
+
output
|
27
45
|
end
|
28
46
|
end
|
29
47
|
|
30
|
-
Template.register_tag('unless'
|
48
|
+
Template.register_tag('unless', Unless)
|
31
49
|
end
|
data/lib/liquid/template.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Liquid
|
3
4
|
# Templates are central to liquid.
|
4
5
|
# Interpretating templates is a two step process. First you compile the
|
5
6
|
# source code you got. During compile time some extensive error checking is performed.
|
@@ -14,21 +15,19 @@ module Liquid
|
|
14
15
|
# template.render('user_name' => 'bob')
|
15
16
|
#
|
16
17
|
class Template
|
17
|
-
|
18
|
-
|
19
|
-
}
|
20
|
-
|
21
|
-
attr_accessor :root, :resource_limits
|
22
|
-
@@file_system = BlankFileSystem.new
|
18
|
+
attr_accessor :root
|
19
|
+
attr_reader :resource_limits, :warnings
|
23
20
|
|
24
21
|
class TagRegistry
|
22
|
+
include Enumerable
|
23
|
+
|
25
24
|
def initialize
|
26
25
|
@tags = {}
|
27
26
|
@cache = {}
|
28
27
|
end
|
29
28
|
|
30
29
|
def [](tag_name)
|
31
|
-
return nil unless @tags.
|
30
|
+
return nil unless @tags.key?(tag_name)
|
32
31
|
return @cache[tag_name] if Liquid.cache_classes
|
33
32
|
|
34
33
|
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
@@ -44,10 +43,14 @@ module Liquid
|
|
44
43
|
@cache.delete(tag_name)
|
45
44
|
end
|
46
45
|
|
46
|
+
def each(&block)
|
47
|
+
@tags.each(&block)
|
48
|
+
end
|
49
|
+
|
47
50
|
private
|
48
51
|
|
49
52
|
def lookup_class(name)
|
50
|
-
|
53
|
+
Object.const_get(name)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
@@ -58,77 +61,57 @@ module Liquid
|
|
58
61
|
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
59
62
|
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
60
63
|
# :strict will enforce correct syntax.
|
61
|
-
|
64
|
+
attr_accessor :error_mode
|
65
|
+
Template.error_mode = :lax
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
# :error raises an error when tainted output is used
|
67
|
-
attr_writer :taint_mode
|
68
|
-
|
69
|
-
def file_system
|
70
|
-
@@file_system
|
67
|
+
attr_accessor :default_exception_renderer
|
68
|
+
Template.default_exception_renderer = lambda do |exception|
|
69
|
+
exception
|
71
70
|
end
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
attr_accessor :file_system
|
73
|
+
Template.file_system = BlankFileSystem.new
|
74
|
+
|
75
|
+
attr_accessor :tags
|
76
|
+
Template.tags = TagRegistry.new
|
77
|
+
private :tags=
|
76
78
|
|
77
79
|
def register_tag(name, klass)
|
78
80
|
tags[name.to_s] = klass
|
79
81
|
end
|
80
82
|
|
81
|
-
def tags
|
82
|
-
@tags ||= TagRegistry.new
|
83
|
-
end
|
84
|
-
|
85
|
-
def error_mode
|
86
|
-
@error_mode || :lax
|
87
|
-
end
|
88
|
-
|
89
|
-
def taint_mode
|
90
|
-
@taint_mode || :lax
|
91
|
-
end
|
92
|
-
|
93
83
|
# Pass a module with filter methods which should be available
|
94
84
|
# to all liquid views. Good for registering the standard library
|
95
85
|
def register_filter(mod)
|
96
|
-
|
86
|
+
StrainerFactory.add_global_filter(mod)
|
97
87
|
end
|
98
88
|
|
99
|
-
|
100
|
-
|
101
|
-
|
89
|
+
attr_accessor :default_resource_limits
|
90
|
+
Template.default_resource_limits = {}
|
91
|
+
private :default_resource_limits=
|
102
92
|
|
103
93
|
# creates a new <tt>Template</tt> object from liquid source code
|
104
94
|
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
105
95
|
# See Liquid::Profiler for more information
|
106
96
|
def parse(source, options = {})
|
107
|
-
|
108
|
-
template.parse(source, options)
|
97
|
+
new.parse(source, options)
|
109
98
|
end
|
110
99
|
end
|
111
100
|
|
112
101
|
def initialize
|
113
|
-
@
|
102
|
+
@rethrow_errors = false
|
103
|
+
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
|
114
104
|
end
|
115
105
|
|
116
106
|
# Parse source code.
|
117
107
|
# Returns self for easy chaining
|
118
108
|
def parse(source, options = {})
|
119
|
-
|
120
|
-
@
|
121
|
-
@
|
122
|
-
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
123
|
-
@warnings = nil
|
109
|
+
parse_context = configure_options(options)
|
110
|
+
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
|
111
|
+
@root = Document.parse(tokenizer, parse_context)
|
124
112
|
self
|
125
113
|
end
|
126
114
|
|
127
|
-
def warnings
|
128
|
-
return [] unless @root
|
129
|
-
@warnings ||= @root.warnings
|
130
|
-
end
|
131
|
-
|
132
115
|
def registers
|
133
116
|
@registers ||= {}
|
134
117
|
end
|
@@ -160,19 +143,19 @@ module Liquid
|
|
160
143
|
# filters and tags and might be useful to integrate liquid more with its host application
|
161
144
|
#
|
162
145
|
def render(*args)
|
163
|
-
return ''
|
146
|
+
return '' if @root.nil?
|
164
147
|
|
165
148
|
context = case args.first
|
166
149
|
when Liquid::Context
|
167
150
|
c = args.shift
|
168
151
|
|
169
152
|
if @rethrow_errors
|
170
|
-
c.
|
153
|
+
c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
171
154
|
end
|
172
155
|
|
173
156
|
c
|
174
157
|
when Liquid::Drop
|
175
|
-
drop
|
158
|
+
drop = args.shift
|
176
159
|
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
177
160
|
when Hash
|
178
161
|
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
@@ -182,34 +165,33 @@ module Liquid
|
|
182
165
|
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
183
166
|
end
|
184
167
|
|
168
|
+
output = nil
|
169
|
+
|
185
170
|
case args.last
|
186
171
|
when Hash
|
187
172
|
options = args.pop
|
173
|
+
output = options[:output] if options[:output]
|
174
|
+
static_registers = context.registers.static
|
188
175
|
|
189
|
-
|
190
|
-
|
176
|
+
options[:registers]&.each do |key, register|
|
177
|
+
static_registers[key] = register
|
191
178
|
end
|
192
179
|
|
193
|
-
|
194
|
-
|
195
|
-
end
|
196
|
-
|
197
|
-
if options[:exception_handler]
|
198
|
-
context.exception_handler = options[:exception_handler]
|
199
|
-
end
|
200
|
-
when Module
|
201
|
-
context.add_filters(args.pop)
|
202
|
-
when Array
|
180
|
+
apply_options_to_context(context, options)
|
181
|
+
when Module, Array
|
203
182
|
context.add_filters(args.pop)
|
204
183
|
end
|
205
184
|
|
185
|
+
# Retrying a render resets resource usage
|
186
|
+
context.resource_limits.reset
|
187
|
+
|
188
|
+
if @profiling && context.profiler.nil?
|
189
|
+
@profiler = context.profiler = Liquid::Profiler.new
|
190
|
+
end
|
191
|
+
|
206
192
|
begin
|
207
193
|
# render the nodelist.
|
208
|
-
|
209
|
-
result = with_profiling do
|
210
|
-
@root.render(context)
|
211
|
-
end
|
212
|
-
result.respond_to?(:join) ? result.join : result
|
194
|
+
@root.render_to_output_buffer(context, output || +'')
|
213
195
|
rescue Liquid::MemoryError => e
|
214
196
|
context.handle_error(e)
|
215
197
|
ensure
|
@@ -222,45 +204,31 @@ module Liquid
|
|
222
204
|
render(*args)
|
223
205
|
end
|
224
206
|
|
225
|
-
|
226
|
-
|
227
|
-
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
228
|
-
def tokenize(source)
|
229
|
-
source = source.source if source.respond_to?(:source)
|
230
|
-
return [] if source.to_s.empty?
|
231
|
-
|
232
|
-
tokens = calculate_line_numbers(source.split(TemplateParser))
|
233
|
-
|
234
|
-
# removes the rogue empty element at the beginning of the array
|
235
|
-
tokens.shift if tokens[0] and tokens[0].empty?
|
236
|
-
|
237
|
-
tokens
|
207
|
+
def render_to_output_buffer(context, output)
|
208
|
+
render(context, output: output)
|
238
209
|
end
|
239
210
|
|
240
|
-
|
241
|
-
return raw_tokens unless @line_numbers
|
211
|
+
private
|
242
212
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
current_line += token.count("\n")
|
247
|
-
end
|
213
|
+
def configure_options(options)
|
214
|
+
if (profiling = options[:profile])
|
215
|
+
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
248
216
|
end
|
249
|
-
end
|
250
217
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
218
|
+
@options = options
|
219
|
+
@profiling = profiling
|
220
|
+
@line_numbers = options[:line_numbers] || @profiling
|
221
|
+
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
222
|
+
@warnings = parse_context.warnings
|
223
|
+
parse_context
|
224
|
+
end
|
255
225
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
yield
|
263
|
-
end
|
226
|
+
def apply_options_to_context(context, options)
|
227
|
+
context.add_filters(options[:filters]) if options[:filters]
|
228
|
+
context.global_filter = options[:global_filter] if options[:global_filter]
|
229
|
+
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
230
|
+
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
231
|
+
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
264
232
|
end
|
265
233
|
end
|
266
234
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Tokenizer
|
5
|
+
attr_reader :line_number, :for_liquid_tag
|
6
|
+
|
7
|
+
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
8
|
+
@source = source.to_s.to_str
|
9
|
+
@line_number = line_number || (line_numbers ? 1 : nil)
|
10
|
+
@for_liquid_tag = for_liquid_tag
|
11
|
+
@tokens = tokenize
|
12
|
+
end
|
13
|
+
|
14
|
+
def shift
|
15
|
+
(token = @tokens.shift) || return
|
16
|
+
|
17
|
+
if @line_number
|
18
|
+
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
token
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def tokenize
|
27
|
+
return [] if @source.empty?
|
28
|
+
|
29
|
+
return @source.split("\n") if @for_liquid_tag
|
30
|
+
|
31
|
+
tokens = @source.split(TemplateParser)
|
32
|
+
|
33
|
+
# removes the rogue empty element at the beginning of the array
|
34
|
+
tokens.shift if tokens[0]&.empty?
|
35
|
+
|
36
|
+
tokens
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|