locomotivecms-solid 0.2.2
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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +7 -0
- data/lib/locomotivecms-solid.rb +2 -0
- data/lib/solid.rb +48 -0
- data/lib/solid/arguments.rb +26 -0
- data/lib/solid/block.rb +13 -0
- data/lib/solid/conditional_block.rb +35 -0
- data/lib/solid/context_error.rb +2 -0
- data/lib/solid/default_security_rules.rb +24 -0
- data/lib/solid/element.rb +51 -0
- data/lib/solid/engine.rb +4 -0
- data/lib/solid/extensions.rb +17 -0
- data/lib/solid/iterable.rb +18 -0
- data/lib/solid/liquid_extensions.rb +87 -0
- data/lib/solid/liquid_extensions/assign_tag.rb +21 -0
- data/lib/solid/liquid_extensions/for_tag.rb +102 -0
- data/lib/solid/liquid_extensions/if_tag.rb +44 -0
- data/lib/solid/liquid_extensions/unless_tag.rb +13 -0
- data/lib/solid/liquid_extensions/variable.rb +34 -0
- data/lib/solid/method_whitelist.rb +56 -0
- data/lib/solid/model_drop.rb +119 -0
- data/lib/solid/parser.rb +108 -0
- data/lib/solid/parser/ripper.rb +220 -0
- data/lib/solid/parser/ruby_parser.rb +88 -0
- data/lib/solid/tag.rb +11 -0
- data/lib/solid/template.rb +24 -0
- data/lib/solid/version.rb +3 -0
- data/locomotivecms-solid.gemspec +26 -0
- data/spec/solid/arguments_spec.rb +314 -0
- data/spec/solid/block_spec.rb +39 -0
- data/spec/solid/conditional_block_spec.rb +39 -0
- data/spec/solid/default_security_rules_spec.rb +180 -0
- data/spec/solid/element_examples.rb +67 -0
- data/spec/solid/liquid_extensions/assign_tag_spec.rb +27 -0
- data/spec/solid/liquid_extensions/for_tag_spec.rb +48 -0
- data/spec/solid/liquid_extensions/if_tag_spec.rb +64 -0
- data/spec/solid/liquid_extensions/unless_tag_spec.rb +54 -0
- data/spec/solid/liquid_extensions/variable_spec.rb +25 -0
- data/spec/solid/model_drop_spec.rb +26 -0
- data/spec/solid/parser/ripper_spec.rb +14 -0
- data/spec/solid/parser/ruby_parser_spec.rb +7 -0
- data/spec/solid/tag_spec.rb +26 -0
- data/spec/solid/template_spec.rb +37 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/class_highjacker_examples.rb +33 -0
- data/spec/support/method_whitelist_matchers.rb +17 -0
- data/spec/support/parser_examples.rb +261 -0
- data/spec/support/tag_highjacker_examples.rb +33 -0
- metadata +204 -0
data/lib/solid/parser.rb
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
class Solid::Parser
|
|
2
|
+
|
|
3
|
+
BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'parser')
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'ripper'
|
|
7
|
+
autoload :Ripper, File.join(BASE_PATH, 'ripper')
|
|
8
|
+
rescue LoadError
|
|
9
|
+
end
|
|
10
|
+
begin
|
|
11
|
+
require 'ruby_parser'
|
|
12
|
+
autoload :RubyParser, File.join(BASE_PATH, 'ruby_parser')
|
|
13
|
+
rescue LoadError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class ContextVariable < Struct.new(:name)
|
|
17
|
+
def evaluate(context)
|
|
18
|
+
Solid.to_liquid(context[name], context)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Literal < Struct.new(:value)
|
|
23
|
+
def evaluate(context)
|
|
24
|
+
Solid.to_liquid(value, context)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class LiteralArray < Literal
|
|
29
|
+
def evaluate(context)
|
|
30
|
+
value.map{ |v| v.evaluate(context) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class LiteralRange < Struct.new(:start_value, :end_value, :exclusive)
|
|
35
|
+
def evaluate(context)
|
|
36
|
+
Range.new(start_value.evaluate(context), end_value.evaluate(context), exclusive)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class LiteralHash < Literal
|
|
41
|
+
def evaluate(context)
|
|
42
|
+
Hash[value.map{ |k, v| [k.evaluate(context), v.evaluate(context)] }]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class MethodCall < Struct.new(:receiver, :name, :arguments)
|
|
47
|
+
include Solid::MethodWhitelist
|
|
48
|
+
BUILTIN_HANDLERS = {
|
|
49
|
+
:'&&' => ->(left, right) { left && right },
|
|
50
|
+
:'||' => ->(left, right) { left || right },
|
|
51
|
+
:'and' => ->(left, right) { left and right },
|
|
52
|
+
:'or' => ->(left, right) { left or right }
|
|
53
|
+
}
|
|
54
|
+
BUILTIN_HANDLERS.keys.each do |operator|
|
|
55
|
+
BUILTIN_HANDLERS[operator.to_s] = BUILTIN_HANDLERS[operator]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def evaluate(context)
|
|
59
|
+
Solid.to_liquid(pluck(receiver.evaluate(context), name, *arguments.map {|arg| arg.evaluate(context) }), context)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
protected
|
|
63
|
+
|
|
64
|
+
def pluck(object, method, *args)
|
|
65
|
+
if BUILTIN_HANDLERS.has_key?(method)
|
|
66
|
+
BUILTIN_HANDLERS[method].call(object, *args)
|
|
67
|
+
elsif safely_respond_to?(object, method)
|
|
68
|
+
object.public_send(method, *args)
|
|
69
|
+
elsif object.respond_to?(:[]) && args.empty?
|
|
70
|
+
object[method]
|
|
71
|
+
elsif object.respond_to?(:before_method)
|
|
72
|
+
object.before_method(method, *args)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
KEYWORDS = {
|
|
79
|
+
'true' => Literal.new(true),
|
|
80
|
+
'false' => Literal.new(false),
|
|
81
|
+
'nil' => Literal.new(nil),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class << self
|
|
85
|
+
|
|
86
|
+
attr_writer :parser
|
|
87
|
+
|
|
88
|
+
def parser
|
|
89
|
+
@parser || begin
|
|
90
|
+
if defined?(Solid::Parser::RubyParser)
|
|
91
|
+
Solid::Parser::RubyParser
|
|
92
|
+
elsif defined?(Solid::Parser::Ripper)
|
|
93
|
+
Solid::Parser::Ripper
|
|
94
|
+
else
|
|
95
|
+
raise "You need to run MRI (to have Ripper), "\
|
|
96
|
+
"or have 'ruby_parser' in $LOAD_PATH "\
|
|
97
|
+
"or set #{self}.parser yourself"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse(string)
|
|
103
|
+
parser.parse(string)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
class Solid::Parser::Ripper < Solid::Parser
|
|
2
|
+
|
|
3
|
+
def self.parse(string)
|
|
4
|
+
new(string).parse
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def initialize(string)
|
|
8
|
+
@string = string
|
|
9
|
+
@sexp = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse
|
|
13
|
+
@sexp = ::Ripper.sexp(@string)
|
|
14
|
+
dive_in or raise 'Ripper changed?'
|
|
15
|
+
parse_one(@sexp)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Looks for a structure like
|
|
19
|
+
# [:program, [[:array, [#stuff#]]]] or
|
|
20
|
+
# [:program, [[:array, nil]]]
|
|
21
|
+
def dive_in
|
|
22
|
+
@sexp = @sexp[1]
|
|
23
|
+
@sexp = @sexp.first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse_one(argument)
|
|
27
|
+
type = argument.shift
|
|
28
|
+
handler = "handle_#{type.to_s.sub('@', '')}"
|
|
29
|
+
raise Solid::SyntaxError, "unknown Ripper type: #{type.inspect}" unless respond_to?(handler)
|
|
30
|
+
public_send handler, *argument
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# # spam
|
|
34
|
+
# [:@ident, "spam", [1, 33]] or
|
|
35
|
+
# # true
|
|
36
|
+
# [:@kw, "true", [1, 23]]
|
|
37
|
+
def handle_var_ref(var_ref)
|
|
38
|
+
parse_one(var_ref)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# # foo: 42
|
|
42
|
+
# [[:assoc_new, [:@label, "foo:", [1, 1]], [:@int, "42", [1, 5]]]]
|
|
43
|
+
def handle_bare_assoc_hash(assoc_hash)
|
|
44
|
+
LiteralHash.new assoc_hash.map {|(_, *key_value)| key_value.map(&method(:parse_one)) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# # {foo: 42}
|
|
48
|
+
# [:assoclist_from_args, [[:assoc_new, [:@label, "foo:", [1, 2]], [:@int, "42", [1, 7]]]]]
|
|
49
|
+
def handle_hash(hash)
|
|
50
|
+
return LiteralHash.new({}) unless hash
|
|
51
|
+
handle_bare_assoc_hash(hash.last)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# # myvar.length
|
|
55
|
+
#
|
|
56
|
+
# [:var_ref, [:@ident, "myvar", [1, 1]]]
|
|
57
|
+
# :"."
|
|
58
|
+
# [:@ident, "length", [1, 7]]
|
|
59
|
+
def handle_call(receiver_sexp, method_call, method_sexp)
|
|
60
|
+
receiver = parse_one(receiver_sexp)
|
|
61
|
+
method = method_sexp[1]
|
|
62
|
+
MethodCall.new receiver, method, []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# # myvar
|
|
66
|
+
#
|
|
67
|
+
# since 1.9.3
|
|
68
|
+
# [:vcall, [:@ident, "myvar", [1, 0]]]
|
|
69
|
+
def handle_vcall(expression)
|
|
70
|
+
parse_one(expression)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# # myvar.split(',', 2)
|
|
74
|
+
#
|
|
75
|
+
# [:call, [:var_ref, [:@ident, "myvar", [1, 1]]], :".", [:@ident, "split", [1, 7]]]
|
|
76
|
+
# [:arg_paren, [:args_add_block, [
|
|
77
|
+
# [:string_literal, [:string_content, [:@tstring_content, ",", [1, 14]]]],
|
|
78
|
+
# [:@int, "2", [1, 18]]
|
|
79
|
+
# ], false]]
|
|
80
|
+
def handle_method_add_arg(call_sexp, args_sexp)
|
|
81
|
+
method_call = parse_one(call_sexp)
|
|
82
|
+
method_call.arguments = method_call_args(args_sexp)
|
|
83
|
+
method_call
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# # args list: (',', 2)
|
|
87
|
+
# [:arg_paren, [:args_add_block, [
|
|
88
|
+
# [:string_literal, [:string_content, [:@tstring_content, ",", [1, 14]]]],
|
|
89
|
+
# [:@int, "2", [1, 18]]
|
|
90
|
+
# ], false]]
|
|
91
|
+
#
|
|
92
|
+
# 1 args list: ()
|
|
93
|
+
# [:arg_paren, nil]
|
|
94
|
+
def method_call_args(args_sexp)
|
|
95
|
+
return [] if args_sexp[1].nil?
|
|
96
|
+
args_sexp = args_sexp.last[1]
|
|
97
|
+
args_sexp.map(&method(:parse_one))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# # !true
|
|
101
|
+
# [:!, [:var_ref, [:@kw, "true", [1, 1]]]]
|
|
102
|
+
def handle_unary(operator, operand)
|
|
103
|
+
MethodCall.new(parse_one(operand), operator, [])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# # 1 + 2
|
|
107
|
+
# [:@int, "1", [1, 0]], :*, [:@int, "2", [1, 4]]
|
|
108
|
+
def handle_binary(left_operand, operator, right_operand)
|
|
109
|
+
receiver = parse_one(left_operand)
|
|
110
|
+
MethodCall.new(receiver, operator, [parse_one(right_operand)])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# # [1]
|
|
114
|
+
# [[:@int, "1", [1, 1]]
|
|
115
|
+
def handle_array(array)
|
|
116
|
+
LiteralArray.new((array || []).map(&method(:parse_one)))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# # (1)
|
|
120
|
+
# [[:@int, "42", [1, 2]]]
|
|
121
|
+
def handle_paren(content)
|
|
122
|
+
parse_one(content.first)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# # 1..10
|
|
126
|
+
# [[:@int, "1", [1, 0]], [:@int, "10", [1, 4]]]
|
|
127
|
+
def handle_dot2(start_value, end_value)
|
|
128
|
+
LiteralRange.new(parse_one(start_value), parse_one(end_value), false)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# # 1...10
|
|
132
|
+
# [[:@int, "1", [1, 0]], [:@int, "10", [1, 4]]]
|
|
133
|
+
def handle_dot3(start_value, end_value)
|
|
134
|
+
LiteralRange.new(parse_one(start_value), parse_one(end_value), true)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# # 'mystring'
|
|
138
|
+
# [:string_content, [:@tstring_content, "mystring", [1, 14]]]
|
|
139
|
+
# TODO: handle string interpolation
|
|
140
|
+
def handle_string_literal(string_content)
|
|
141
|
+
Literal.new(parse_one(string_content))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def handle_string_content(*parts)
|
|
145
|
+
parts.map(&method(:parse_one)).join
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# [:@tstring_content, "mystring", [1, 14]]
|
|
149
|
+
def handle_tstring_content(string_content, lineno_column)
|
|
150
|
+
string_content
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def handle_dyna_symbol(string_content)
|
|
154
|
+
Literal.new(string_content.first[1])
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
REGEXP_FLAGS = {
|
|
158
|
+
'i' => Regexp::IGNORECASE,
|
|
159
|
+
'x' => Regexp::EXTENDED,
|
|
160
|
+
'm' => Regexp::MULTILINE,
|
|
161
|
+
'n' => Regexp::NOENCODING
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
def instanciate_regexp(content, flags)
|
|
165
|
+
mode = 0
|
|
166
|
+
flags.each_char do |flag|
|
|
167
|
+
mode |= REGEXP_FLAGS[flag] || 0
|
|
168
|
+
end
|
|
169
|
+
Regexp.new(content, mode)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# # /bb|[^b]{2}/
|
|
173
|
+
# [[[:@tstring_content, "bb|[^b]{2}", [1, 1]]], [:@regexp_end, "/", [1, 4]]]
|
|
174
|
+
|
|
175
|
+
# # /bb|[^b]{2}/ix
|
|
176
|
+
# [[[:@tstring_content, "bb|[^b]{2}", [1, 1]]], [:@regexp_end, "/ix", [1, 4]]]
|
|
177
|
+
|
|
178
|
+
# TODO: handle regexp interpolation
|
|
179
|
+
def handle_regexp_literal(regexp_content, regexp_end)
|
|
180
|
+
Literal.new instanciate_regexp(regexp_content.first[1], regexp_end[1][1..-1])
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# # true
|
|
184
|
+
# "true", [1, 33]
|
|
185
|
+
def handle_kw(keyword, lineno_column)
|
|
186
|
+
raise Solid::SyntaxError, 'unknown Ripper sexp' unless KEYWORDS.has_key? keyword
|
|
187
|
+
KEYWORDS[keyword]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# # spam
|
|
191
|
+
# "spam", [1, 23]
|
|
192
|
+
def handle_ident(identifier, lineno_column)
|
|
193
|
+
ContextVariable.new identifier
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# # Spam
|
|
197
|
+
# "Spam", [1, 23]
|
|
198
|
+
def handle_const(constant, lineno_column)
|
|
199
|
+
ContextVariable.new constant
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# # 42
|
|
203
|
+
# "42", [1, 2]
|
|
204
|
+
def handle_int(int, lineno_column)
|
|
205
|
+
Literal.new int.to_i
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# # 4.2
|
|
209
|
+
# "4.2", [1, 2]
|
|
210
|
+
def handle_float(float, lineno_column)
|
|
211
|
+
Literal.new float.to_f
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# # foo:
|
|
215
|
+
# "foo:", [1, 2]
|
|
216
|
+
def handle_label(label, lineno_column)
|
|
217
|
+
Literal.new label[0..-2].to_sym
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require 'ruby_parser'
|
|
2
|
+
|
|
3
|
+
class Solid::Parser::RubyParser < Solid::Parser
|
|
4
|
+
|
|
5
|
+
def self.parse(string)
|
|
6
|
+
new(string).parse
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(expression)
|
|
10
|
+
@expression = expression
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse
|
|
14
|
+
begin
|
|
15
|
+
@sexp = ::RubyParser.new.parse(@expression)
|
|
16
|
+
rescue ::RubyParser::SyntaxError => exc
|
|
17
|
+
raise Solid::SyntaxError.new(exc.message)
|
|
18
|
+
end
|
|
19
|
+
parse_one(@sexp)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def parse_one(expression)
|
|
23
|
+
type = expression.shift
|
|
24
|
+
handler = "handle_#{type}"
|
|
25
|
+
raise Solid::SyntaxError, "unknown expression type: #{type.inspect}" unless respond_to?(handler)
|
|
26
|
+
public_send handler, *expression
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def handle_lit(literal)
|
|
30
|
+
case literal
|
|
31
|
+
when Range # see https://github.com/seattlerb/ruby_parser/issues/134
|
|
32
|
+
LiteralRange.new(Literal.new(literal.first), Literal.new(literal.last), literal.exclude_end?)
|
|
33
|
+
else
|
|
34
|
+
Literal.new(literal)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def handle_str(literal)
|
|
39
|
+
Literal.new(literal)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def handle_array(*array_values)
|
|
43
|
+
LiteralArray.new(array_values.map(&method(:parse_one)))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def handle_hash(*hash_keys_and_values)
|
|
47
|
+
LiteralHash.new(Hash[*hash_keys_and_values.map(&method(:parse_one))])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def handle_dot2(start_value, end_value)
|
|
51
|
+
LiteralRange.new(parse_one(start_value), parse_one(end_value), false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def handle_dot3(start_value, end_value)
|
|
55
|
+
LiteralRange.new(parse_one(start_value), parse_one(end_value), true)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def handle_true
|
|
59
|
+
KEYWORDS['true']
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def handle_false
|
|
63
|
+
KEYWORDS['false']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_nil
|
|
67
|
+
KEYWORDS['nil']
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_const(const_name)
|
|
71
|
+
ContextVariable.new(const_name.to_s)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def handle_call(receiver, method_name, *arguments)
|
|
75
|
+
return ContextVariable.new(method_name.to_s) if receiver.nil?
|
|
76
|
+
|
|
77
|
+
MethodCall.new(parse_one(receiver), method_name.to_s, arguments.map(&method(:parse_one)))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def handle_and(left_expression, right_expression)
|
|
81
|
+
handle_call(left_expression, :'&&', right_expression)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def handle_or(left_expression, right_expression)
|
|
85
|
+
handle_call(left_expression, :'||', right_expression)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
data/lib/solid/tag.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
class Solid::Template < Liquid::Template
|
|
4
|
+
extend Forwardable
|
|
5
|
+
include Solid::Iterable
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
def parse(source)
|
|
10
|
+
template = Solid::Template.new
|
|
11
|
+
template.parse(source)
|
|
12
|
+
template
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def_delegators :root, :nodelist
|
|
18
|
+
|
|
19
|
+
# Avoid issues with ActiveSupport::Cache which freeze all objects passed to it like an ass
|
|
20
|
+
# And anyway once frozen Liquid::Templates are unable to render anything
|
|
21
|
+
def freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|