liquid-render-tag 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +23 -0
- data/Rakefile +2 -0
- data/lib/liquid-render-tag.rb +20 -0
- data/lib/liquid-render-tag/block_body.rb +219 -0
- data/lib/liquid-render-tag/context.rb +261 -0
- data/lib/liquid-render-tag/partial_cache.rb +24 -0
- data/lib/liquid-render-tag/register.rb +6 -0
- data/lib/liquid-render-tag/registers/disabled_tags.rb +32 -0
- data/lib/liquid-render-tag/static_registers.rb +36 -0
- data/lib/liquid-render-tag/strainer_factory.rb +36 -0
- data/lib/liquid-render-tag/strainer_template.rb +53 -0
- data/lib/liquid-render-tag/tag.rb +73 -0
- data/lib/liquid-render-tag/tags/render.rb +84 -0
- data/lib/liquid-render-tag/template.rb +274 -0
- data/lib/liquid-render-tag/template_factory.rb +9 -0
- data/lib/liquid-render-tag/tokenizer.rb +39 -0
- data/lib/liquid-render-tag/variable.rb +170 -0
- data/lib/liquid-render-tag/version.rb +3 -0
- data/liquid-render-tag.gemspec +27 -0
- metadata +108 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class PartialCache
|
5
|
+
def self.load(template_name, context:, parse_context:)
|
6
|
+
cached_partials = (context.registers[:cached_partials] ||= {})
|
7
|
+
cached = cached_partials[template_name]
|
8
|
+
return cached if cached
|
9
|
+
|
10
|
+
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
|
11
|
+
source = file_system.read_template_file(template_name)
|
12
|
+
|
13
|
+
parse_context.partial = true
|
14
|
+
|
15
|
+
template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new)
|
16
|
+
template = template_factory.for(template_name)
|
17
|
+
|
18
|
+
partial = template.parse(source, parse_context)
|
19
|
+
cached_partials[template_name] = partial
|
20
|
+
ensure
|
21
|
+
parse_context.partial = false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Liquid
|
3
|
+
class DisabledTags < Register
|
4
|
+
def initialize
|
5
|
+
@disabled_tags = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def disabled?(tag)
|
9
|
+
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def disable(tags)
|
13
|
+
tags.each(&method(:increment))
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
tags.each(&method(:decrement))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def increment(tag)
|
22
|
+
@disabled_tags[tag] ||= 0
|
23
|
+
@disabled_tags[tag] += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def decrement(tag)
|
27
|
+
@disabled_tags[tag] -= 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Template.add_register(:disabled_tags, DisabledTags.new)
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class StaticRegisters
|
5
|
+
attr_reader :static, :registers
|
6
|
+
|
7
|
+
def initialize(registers = {})
|
8
|
+
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
|
9
|
+
@registers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(key, value)
|
13
|
+
@registers[key] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
if @registers.key?(key)
|
18
|
+
@registers[key]
|
19
|
+
else
|
20
|
+
@static[key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
@registers.delete(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fetch(key, default = nil)
|
29
|
+
key?(key) ? self[key] : default
|
30
|
+
end
|
31
|
+
|
32
|
+
def key?(key)
|
33
|
+
@registers.key?(key) || @static.key?(key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# StrainerFactory is the factory for the filters system.
|
5
|
+
module StrainerFactory
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def add_global_filter(filter)
|
9
|
+
strainer_class_cache.clear
|
10
|
+
global_filters << filter
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(context, filters = [])
|
14
|
+
strainer_from_cache(filters).new(context)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def global_filters
|
20
|
+
@global_filters ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def strainer_from_cache(filters)
|
24
|
+
strainer_class_cache[filters] ||= begin
|
25
|
+
klass = Class.new(StrainerTemplate)
|
26
|
+
global_filters.each { |f| klass.add_filter(f) }
|
27
|
+
filters.each { |f| klass.add_filter(f) }
|
28
|
+
klass
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def strainer_class_cache
|
33
|
+
@strainer_class_cache ||= {}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Liquid
|
6
|
+
# StrainerTemplate is the computed class for the filters system.
|
7
|
+
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
|
8
|
+
#
|
9
|
+
# The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
|
10
|
+
# Context#add_filters or Template.register_filter
|
11
|
+
class StrainerTemplate
|
12
|
+
def initialize(context)
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def add_filter(filter)
|
18
|
+
return if include?(filter)
|
19
|
+
|
20
|
+
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
21
|
+
if invokable_non_public_methods.any?
|
22
|
+
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
include(filter)
|
26
|
+
|
27
|
+
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
28
|
+
end
|
29
|
+
|
30
|
+
def invokable?(method)
|
31
|
+
filter_methods.include?(method.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def filter_methods
|
37
|
+
@filter_methods ||= Set.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def invoke(method, *args)
|
42
|
+
if self.class.invokable?(method)
|
43
|
+
send(method, *args)
|
44
|
+
elsif @context.strict_filters
|
45
|
+
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
46
|
+
else
|
47
|
+
args.first
|
48
|
+
end
|
49
|
+
rescue ::ArgumentError => e
|
50
|
+
raise Liquid::ArgumentError, e.message, e.backtrace
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Tag
|
5
|
+
attr_reader :nodelist, :tag_name, :line_number, :parse_context
|
6
|
+
alias_method :options, :parse_context
|
7
|
+
include ParserSwitching
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def parse(tag_name, markup, tokenizer, parse_context)
|
11
|
+
tag = new(tag_name, markup, parse_context)
|
12
|
+
tag.parse(tokenizer)
|
13
|
+
tag
|
14
|
+
end
|
15
|
+
|
16
|
+
def disable_tags(*tags)
|
17
|
+
disabled_tags.push(*tags)
|
18
|
+
end
|
19
|
+
|
20
|
+
private :new
|
21
|
+
|
22
|
+
def disabled_tags
|
23
|
+
@disabled_tags ||= []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(tag_name, markup, parse_context)
|
28
|
+
@tag_name = tag_name
|
29
|
+
@markup = markup
|
30
|
+
@parse_context = parse_context
|
31
|
+
@line_number = parse_context.line_number
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse(_tokens)
|
35
|
+
end
|
36
|
+
|
37
|
+
def raw
|
38
|
+
"#{@tag_name} #{@markup}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def name
|
42
|
+
self.class.name.downcase
|
43
|
+
end
|
44
|
+
|
45
|
+
def render(_context)
|
46
|
+
''
|
47
|
+
end
|
48
|
+
|
49
|
+
def disabled?(context)
|
50
|
+
context.registers[:disabled_tags].disabled?(tag_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def disabled_error_message
|
54
|
+
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# For backwards compatibility with custom tags. In a future release, the semantics
|
58
|
+
# of the `render_to_output_buffer` method will become the default and the `render`
|
59
|
+
# method will be removed.
|
60
|
+
def render_to_output_buffer(context, output)
|
61
|
+
output << render(context)
|
62
|
+
output
|
63
|
+
end
|
64
|
+
|
65
|
+
def blank?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def disabled_tags
|
70
|
+
self.class.disabled_tags
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Render < Tag
|
5
|
+
FOR = 'for'
|
6
|
+
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
7
|
+
|
8
|
+
disable_tags "include"
|
9
|
+
|
10
|
+
attr_reader :template_name_expr, :attributes
|
11
|
+
|
12
|
+
def initialize(tag_name, markup, options)
|
13
|
+
super
|
14
|
+
|
15
|
+
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
|
16
|
+
|
17
|
+
template_name = Regexp.last_match(1)
|
18
|
+
with_or_for = Regexp.last_match(3)
|
19
|
+
variable_name = Regexp.last_match(4)
|
20
|
+
|
21
|
+
@alias_name = Regexp.last_match(6)
|
22
|
+
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
23
|
+
@template_name_expr = Expression.parse(template_name)
|
24
|
+
@for = (with_or_for == FOR)
|
25
|
+
|
26
|
+
@attributes = {}
|
27
|
+
markup.scan(TagAttributes) do |key, value|
|
28
|
+
@attributes[key] = Expression.parse(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_to_output_buffer(context, output)
|
33
|
+
render_tag(context, output)
|
34
|
+
end
|
35
|
+
|
36
|
+
def render_tag(context, output)
|
37
|
+
# Though we evaluate this here we will only ever parse it as a string literal.
|
38
|
+
template_name = context.evaluate(@template_name_expr)
|
39
|
+
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
|
40
|
+
|
41
|
+
partial = PartialCache.load(
|
42
|
+
template_name,
|
43
|
+
context: context,
|
44
|
+
parse_context: parse_context
|
45
|
+
)
|
46
|
+
|
47
|
+
context_variable_name = @alias_name || template_name.split('/').last
|
48
|
+
|
49
|
+
render_partial_func = ->(var, forloop) {
|
50
|
+
inner_context = context.new_isolated_subcontext
|
51
|
+
inner_context.template_name = template_name
|
52
|
+
inner_context.partial = true
|
53
|
+
inner_context['forloop'] = forloop if forloop
|
54
|
+
|
55
|
+
@attributes.each do |key, value|
|
56
|
+
inner_context[key] = context.evaluate(value)
|
57
|
+
end
|
58
|
+
inner_context[context_variable_name] = var unless var.nil?
|
59
|
+
partial.render_to_output_buffer(inner_context, output)
|
60
|
+
forloop&.send(:increment!)
|
61
|
+
}
|
62
|
+
|
63
|
+
variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
|
64
|
+
if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
|
65
|
+
forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
|
66
|
+
variable.each { |var| render_partial_func.call(var, forloop) }
|
67
|
+
else
|
68
|
+
render_partial_func.call(variable, nil)
|
69
|
+
end
|
70
|
+
|
71
|
+
output
|
72
|
+
end
|
73
|
+
|
74
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
75
|
+
def children
|
76
|
+
[
|
77
|
+
@node.template_name_expr,
|
78
|
+
] + @node.attributes.values
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Template.register_tag('render', Render)
|
84
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# Templates are central to liquid.
|
5
|
+
# Interpretating templates is a two step process. First you compile the
|
6
|
+
# source code you got. During compile time some extensive error checking is performed.
|
7
|
+
# your code should expect to get some SyntaxErrors.
|
8
|
+
#
|
9
|
+
# After you have a compiled template you can then <tt>render</tt> it.
|
10
|
+
# You can use a compiled template over and over again and keep it cached.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# template = Liquid::Template.parse(source)
|
15
|
+
# template.render('user_name' => 'bob')
|
16
|
+
#
|
17
|
+
class Template
|
18
|
+
attr_accessor :root
|
19
|
+
attr_reader :resource_limits, :warnings
|
20
|
+
|
21
|
+
class TagRegistry
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@tags = {}
|
26
|
+
@cache = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](tag_name)
|
30
|
+
return nil unless @tags.key?(tag_name)
|
31
|
+
return @cache[tag_name] if Liquid.cache_classes
|
32
|
+
|
33
|
+
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(tag_name, klass)
|
37
|
+
@tags[tag_name] = klass.name
|
38
|
+
@cache[tag_name] = klass
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(tag_name)
|
42
|
+
@tags.delete(tag_name)
|
43
|
+
@cache.delete(tag_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def each(&block)
|
47
|
+
@tags.each(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def lookup_class(name)
|
53
|
+
Object.const_get(name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :profiler
|
58
|
+
|
59
|
+
class << self
|
60
|
+
# Sets how strict the parser should be.
|
61
|
+
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
62
|
+
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
63
|
+
# :strict will enforce correct syntax.
|
64
|
+
attr_accessor :error_mode
|
65
|
+
Template.error_mode = :lax
|
66
|
+
|
67
|
+
attr_reader :taint_mode
|
68
|
+
|
69
|
+
# Sets how strict the taint checker should be.
|
70
|
+
# :lax is the default, and ignores the taint flag completely
|
71
|
+
# :warn adds a warning, but does not interrupt the rendering
|
72
|
+
# :error raises an error when tainted output is used
|
73
|
+
# @deprecated Since it is being deprecated in ruby itself.
|
74
|
+
def taint_mode=(mode)
|
75
|
+
taint_supported = Object.new.taint.tainted?
|
76
|
+
if mode != :lax && !taint_supported
|
77
|
+
raise NotImplementedError, "#{RUBY_ENGINE} #{RUBY_VERSION} doesn't support taint checking"
|
78
|
+
end
|
79
|
+
@taint_mode = mode
|
80
|
+
end
|
81
|
+
|
82
|
+
Template.taint_mode = :lax
|
83
|
+
|
84
|
+
attr_accessor :default_exception_renderer
|
85
|
+
Template.default_exception_renderer = lambda do |exception|
|
86
|
+
exception
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_accessor :file_system
|
90
|
+
Template.file_system = BlankFileSystem.new
|
91
|
+
|
92
|
+
attr_accessor :tags
|
93
|
+
Template.tags = TagRegistry.new
|
94
|
+
private :tags=
|
95
|
+
|
96
|
+
def register_tag(name, klass)
|
97
|
+
tags[name.to_s] = klass
|
98
|
+
end
|
99
|
+
|
100
|
+
attr_accessor :registers
|
101
|
+
Template.registers = {}
|
102
|
+
private :registers=
|
103
|
+
|
104
|
+
def add_register(name, klass)
|
105
|
+
registers[name.to_sym] = klass
|
106
|
+
end
|
107
|
+
|
108
|
+
# Pass a module with filter methods which should be available
|
109
|
+
# to all liquid views. Good for registering the standard library
|
110
|
+
def register_filter(mod)
|
111
|
+
StrainerFactory.add_global_filter(mod)
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_accessor :default_resource_limits
|
115
|
+
Template.default_resource_limits = {}
|
116
|
+
private :default_resource_limits=
|
117
|
+
|
118
|
+
# creates a new <tt>Template</tt> object from liquid source code
|
119
|
+
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
120
|
+
# See Liquid::Profiler for more information
|
121
|
+
def parse(source, options = {})
|
122
|
+
new.parse(source, options)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize
|
127
|
+
@rethrow_errors = false
|
128
|
+
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Parse source code.
|
132
|
+
# Returns self for easy chaining
|
133
|
+
def parse(source, options = {})
|
134
|
+
@options = options
|
135
|
+
@profiling = options[:profile]
|
136
|
+
@line_numbers = options[:line_numbers] || @profiling
|
137
|
+
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
138
|
+
@root = Document.parse(tokenize(source), parse_context)
|
139
|
+
@warnings = parse_context.warnings
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
def registers
|
144
|
+
@registers ||= {}
|
145
|
+
end
|
146
|
+
|
147
|
+
def assigns
|
148
|
+
@assigns ||= {}
|
149
|
+
end
|
150
|
+
|
151
|
+
def instance_assigns
|
152
|
+
@instance_assigns ||= {}
|
153
|
+
end
|
154
|
+
|
155
|
+
def errors
|
156
|
+
@errors ||= []
|
157
|
+
end
|
158
|
+
|
159
|
+
# Render takes a hash with local variables.
|
160
|
+
#
|
161
|
+
# if you use the same filters over and over again consider registering them globally
|
162
|
+
# with <tt>Template.register_filter</tt>
|
163
|
+
#
|
164
|
+
# if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
|
165
|
+
# will be available via <tt>Template#profiler</tt>
|
166
|
+
#
|
167
|
+
# Following options can be passed:
|
168
|
+
#
|
169
|
+
# * <tt>filters</tt> : array with local filters
|
170
|
+
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
171
|
+
# filters and tags and might be useful to integrate liquid more with its host application
|
172
|
+
#
|
173
|
+
def render(*args)
|
174
|
+
return '' if @root.nil?
|
175
|
+
|
176
|
+
context = case args.first
|
177
|
+
when Liquid::Context
|
178
|
+
c = args.shift
|
179
|
+
|
180
|
+
if @rethrow_errors
|
181
|
+
c.exception_renderer = ->(_e) { raise }
|
182
|
+
end
|
183
|
+
|
184
|
+
c
|
185
|
+
when Liquid::Drop
|
186
|
+
drop = args.shift
|
187
|
+
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
188
|
+
when Hash
|
189
|
+
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
190
|
+
when nil
|
191
|
+
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
192
|
+
else
|
193
|
+
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
194
|
+
end
|
195
|
+
|
196
|
+
output = nil
|
197
|
+
|
198
|
+
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
199
|
+
|
200
|
+
case args.last
|
201
|
+
when Hash
|
202
|
+
options = args.pop
|
203
|
+
output = options[:output] if options[:output]
|
204
|
+
|
205
|
+
options[:registers]&.each do |key, register|
|
206
|
+
context_register[key] = register
|
207
|
+
end
|
208
|
+
|
209
|
+
apply_options_to_context(context, options)
|
210
|
+
when Module, Array
|
211
|
+
context.add_filters(args.pop)
|
212
|
+
end
|
213
|
+
|
214
|
+
Template.registers.each do |key, register|
|
215
|
+
context_register[key] = register
|
216
|
+
end
|
217
|
+
|
218
|
+
# Retrying a render resets resource usage
|
219
|
+
context.resource_limits.reset
|
220
|
+
|
221
|
+
begin
|
222
|
+
# render the nodelist.
|
223
|
+
# for performance reasons we get an array back here. join will make a string out of it.
|
224
|
+
with_profiling(context) do
|
225
|
+
@root.render_to_output_buffer(context, output || +'')
|
226
|
+
end
|
227
|
+
rescue Liquid::MemoryError => e
|
228
|
+
context.handle_error(e)
|
229
|
+
ensure
|
230
|
+
@errors = context.errors
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def render!(*args)
|
235
|
+
@rethrow_errors = true
|
236
|
+
render(*args)
|
237
|
+
end
|
238
|
+
|
239
|
+
def render_to_output_buffer(context, output)
|
240
|
+
render(context, output: output)
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def tokenize(source)
|
246
|
+
Tokenizer.new(source, @line_numbers)
|
247
|
+
end
|
248
|
+
|
249
|
+
def with_profiling(context)
|
250
|
+
if @profiling && !context.partial
|
251
|
+
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
252
|
+
|
253
|
+
@profiler = Profiler.new(context.template_name)
|
254
|
+
@profiler.start
|
255
|
+
|
256
|
+
begin
|
257
|
+
yield
|
258
|
+
ensure
|
259
|
+
@profiler.stop
|
260
|
+
end
|
261
|
+
else
|
262
|
+
yield
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def apply_options_to_context(context, options)
|
267
|
+
context.add_filters(options[:filters]) if options[:filters]
|
268
|
+
context.global_filter = options[:global_filter] if options[:global_filter]
|
269
|
+
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
270
|
+
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
271
|
+
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|