twig_ruby 0.0.1 → 0.0.3
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 +4 -4
- data/README.md +116 -0
- data/lib/tasks/twig_parity.rake +278 -0
- data/lib/twig/auto_hash.rb +7 -1
- data/lib/twig/callable.rb +28 -1
- data/lib/twig/compiler.rb +35 -3
- data/lib/twig/environment.rb +198 -41
- data/lib/twig/error/base.rb +81 -16
- data/lib/twig/error/loader.rb +8 -0
- data/lib/twig/error/logic.rb +8 -0
- data/lib/twig/error/runtime.rb +8 -0
- data/lib/twig/expression_parser/base.rb +30 -0
- data/lib/twig/expression_parser/expression_parsers.rb +57 -0
- data/lib/twig/expression_parser/infix/arrow.rb +31 -0
- data/lib/twig/expression_parser/infix/binary.rb +34 -0
- data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
- data/lib/twig/expression_parser/infix/dot.rb +72 -0
- data/lib/twig/expression_parser/infix/filter.rb +43 -0
- data/lib/twig/expression_parser/infix/function.rb +67 -0
- data/lib/twig/expression_parser/infix/is.rb +53 -0
- data/lib/twig/expression_parser/infix/is_not.rb +19 -0
- data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
- data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
- data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
- data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
- data/lib/twig/expression_parser/prefix/literal.rb +244 -0
- data/lib/twig/expression_parser/prefix/unary.rb +29 -0
- data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
- data/lib/twig/extension/base.rb +26 -4
- data/lib/twig/extension/core.rb +1076 -48
- data/lib/twig/extension/debug.rb +25 -0
- data/lib/twig/extension/escaper.rb +73 -0
- data/lib/twig/extension/rails.rb +10 -57
- data/lib/twig/extension/string_loader.rb +19 -0
- data/lib/twig/extension_set.rb +117 -20
- data/lib/twig/file_extension_escaping_strategy.rb +35 -0
- data/lib/twig/lexer.rb +225 -81
- data/lib/twig/loader/array.rb +25 -8
- data/lib/twig/loader/chain.rb +93 -0
- data/lib/twig/loader/filesystem.rb +106 -7
- data/lib/twig/node/auto_escape.rb +18 -0
- data/lib/twig/node/base.rb +58 -2
- data/lib/twig/node/block.rb +2 -0
- data/lib/twig/node/block_reference.rb +5 -1
- data/lib/twig/node/body.rb +7 -0
- data/lib/twig/node/cache.rb +50 -0
- data/lib/twig/node/capture.rb +22 -0
- data/lib/twig/node/deprecated.rb +53 -0
- data/lib/twig/node/do.rb +19 -0
- data/lib/twig/node/embed.rb +43 -0
- data/lib/twig/node/expression/array.rb +29 -20
- data/lib/twig/node/expression/arrow_function.rb +55 -0
- data/lib/twig/node/expression/assign_name.rb +1 -1
- data/lib/twig/node/expression/binary/and.rb +17 -0
- data/lib/twig/node/expression/binary/base.rb +6 -4
- data/lib/twig/node/expression/binary/boolean.rb +24 -0
- data/lib/twig/node/expression/binary/concat.rb +20 -0
- data/lib/twig/node/expression/binary/elvis.rb +35 -0
- data/lib/twig/node/expression/binary/ends_with.rb +24 -0
- data/lib/twig/node/expression/binary/floor_div.rb +21 -0
- data/lib/twig/node/expression/binary/has_every.rb +20 -0
- data/lib/twig/node/expression/binary/has_some.rb +20 -0
- data/lib/twig/node/expression/binary/in.rb +20 -0
- data/lib/twig/node/expression/binary/matches.rb +24 -0
- data/lib/twig/node/expression/binary/not_in.rb +20 -0
- data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
- data/lib/twig/node/expression/binary/or.rb +15 -0
- data/lib/twig/node/expression/binary/starts_with.rb +24 -0
- data/lib/twig/node/expression/binary/xor.rb +17 -0
- data/lib/twig/node/expression/block_reference.rb +62 -0
- data/lib/twig/node/expression/call.rb +126 -6
- data/lib/twig/node/expression/constant.rb +3 -1
- data/lib/twig/node/expression/filter/default.rb +37 -0
- data/lib/twig/node/expression/filter/raw.rb +31 -0
- data/lib/twig/node/expression/filter.rb +2 -2
- data/lib/twig/node/expression/function.rb +37 -0
- data/lib/twig/node/expression/get_attribute.rb +51 -7
- data/lib/twig/node/expression/hash.rb +75 -0
- data/lib/twig/node/expression/helper_method.rb +6 -18
- data/lib/twig/node/expression/macro_reference.rb +43 -0
- data/lib/twig/node/expression/name.rb +42 -8
- data/lib/twig/node/expression/operator_escape.rb +13 -0
- data/lib/twig/node/expression/parent.rb +28 -0
- data/lib/twig/node/expression/support_defined_test.rb +23 -0
- data/lib/twig/node/expression/ternary.rb +7 -1
- data/lib/twig/node/expression/test/base.rb +26 -0
- data/lib/twig/node/expression/test/constant.rb +35 -0
- data/lib/twig/node/expression/test/defined.rb +33 -0
- data/lib/twig/node/expression/test/divisible_by.rb +23 -0
- data/lib/twig/node/expression/test/even.rb +21 -0
- data/lib/twig/node/expression/test/iterable.rb +21 -0
- data/lib/twig/node/expression/test/mapping.rb +21 -0
- data/lib/twig/node/expression/test/null.rb +21 -0
- data/lib/twig/node/expression/test/odd.rb +21 -0
- data/lib/twig/node/expression/test/same_as.rb +23 -0
- data/lib/twig/node/expression/test/sequence.rb +21 -0
- data/lib/twig/node/expression/unary/base.rb +3 -1
- data/lib/twig/node/expression/unary/not.rb +18 -0
- data/lib/twig/node/expression/unary/spread.rb +18 -0
- data/lib/twig/node/expression/unary/string_cast.rb +18 -0
- data/lib/twig/node/expression/variable/assign_template.rb +35 -0
- data/lib/twig/node/expression/variable/local.rb +35 -0
- data/lib/twig/node/expression/variable/template.rb +54 -0
- data/lib/twig/node/for.rb +38 -8
- data/lib/twig/node/for_loop.rb +0 -22
- data/lib/twig/node/if.rb +4 -1
- data/lib/twig/node/import.rb +32 -0
- data/lib/twig/node/include.rb +38 -8
- data/lib/twig/node/macro.rb +79 -0
- data/lib/twig/node/module.rb +278 -23
- data/lib/twig/node/output.rb +7 -0
- data/lib/twig/node/print.rb +4 -1
- data/lib/twig/node/set.rb +72 -0
- data/lib/twig/node/text.rb +4 -1
- data/lib/twig/node/with.rb +50 -0
- data/lib/twig/node/yield.rb +6 -1
- data/lib/twig/node_traverser.rb +50 -0
- data/lib/twig/node_visitor/base.rb +30 -0
- data/lib/twig/node_visitor/escaper.rb +165 -0
- data/lib/twig/node_visitor/safe_analysis.rb +127 -0
- data/lib/twig/node_visitor/spreader.rb +39 -0
- data/lib/twig/output_buffer.rb +14 -12
- data/lib/twig/parser.rb +281 -8
- data/lib/twig/rails/config.rb +33 -0
- data/lib/twig/rails/engine.rb +44 -0
- data/lib/twig/rails/renderer.rb +41 -0
- data/lib/twig/runtime/argument_spreader.rb +46 -0
- data/lib/twig/runtime/context.rb +154 -0
- data/lib/twig/runtime/enumerable_hash.rb +51 -0
- data/lib/twig/runtime/escaper.rb +155 -0
- data/lib/twig/runtime/loop_context.rb +81 -0
- data/lib/twig/runtime/loop_iterator.rb +60 -0
- data/lib/twig/runtime/spread.rb +21 -0
- data/lib/twig/runtime_loader/base.rb +12 -0
- data/lib/twig/runtime_loader/factory.rb +23 -0
- data/lib/twig/template.rb +267 -14
- data/lib/twig/template_wrapper.rb +42 -0
- data/lib/twig/token.rb +28 -2
- data/lib/twig/token_parser/apply.rb +48 -0
- data/lib/twig/token_parser/auto_escape.rb +45 -0
- data/lib/twig/token_parser/base.rb +26 -0
- data/lib/twig/token_parser/block.rb +4 -4
- data/lib/twig/token_parser/cache.rb +31 -0
- data/lib/twig/token_parser/deprecated.rb +40 -0
- data/lib/twig/token_parser/do.rb +19 -0
- data/lib/twig/token_parser/embed.rb +62 -0
- data/lib/twig/token_parser/extends.rb +4 -3
- data/lib/twig/token_parser/for.rb +14 -9
- data/lib/twig/token_parser/from.rb +57 -0
- data/lib/twig/token_parser/guard.rb +65 -0
- data/lib/twig/token_parser/if.rb +9 -9
- data/lib/twig/token_parser/import.rb +29 -0
- data/lib/twig/token_parser/include.rb +2 -2
- data/lib/twig/token_parser/macro.rb +109 -0
- data/lib/twig/token_parser/set.rb +76 -0
- data/lib/twig/token_parser/use.rb +54 -0
- data/lib/twig/token_parser/with.rb +36 -0
- data/lib/twig/token_parser/yield.rb +7 -7
- data/lib/twig/token_stream.rb +23 -3
- data/lib/twig/twig_filter.rb +20 -0
- data/lib/twig/twig_function.rb +37 -0
- data/lib/twig/twig_test.rb +31 -0
- data/lib/twig/util/callable_arguments_extractor.rb +227 -0
- data/lib/twig_ruby.rb +21 -2
- metadata +148 -6
- data/lib/twig/context.rb +0 -64
- data/lib/twig/expression_parser.rb +0 -517
- data/lib/twig/railtie.rb +0 -60
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module NodeVisitor
|
|
5
|
+
class Escaper < Base
|
|
6
|
+
attr_reader :escaping_strategy
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
@default_strategy = false
|
|
12
|
+
@status_stack = []
|
|
13
|
+
@blocks = {}
|
|
14
|
+
@safe_vars = []
|
|
15
|
+
@safe_analysis = SafeAnalysis.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def enter_node(node, env)
|
|
19
|
+
case node
|
|
20
|
+
when Node::Module
|
|
21
|
+
if env.extension?(Extension::Escaper)
|
|
22
|
+
default_strategy = env.extension(Extension::Escaper).default_strategy(node.template_name)
|
|
23
|
+
@default_strategy = default_strategy if default_strategy
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@blocks = {}
|
|
27
|
+
@safe_vars = []
|
|
28
|
+
when Node::AutoEscape
|
|
29
|
+
@status_stack << node.attributes[:value].then { |v| v.is_a?(String) ? v.to_sym : v }
|
|
30
|
+
when Node::Block
|
|
31
|
+
@status_stack << blocks.fetch(node.attributes[:name], need_escaping)
|
|
32
|
+
when Node::Import
|
|
33
|
+
@safe_vars << node.nodes[:var].nodes[:var].attributes[:name]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
node
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def leave_node(node, env)
|
|
40
|
+
if node.is_a?(Node::Module)
|
|
41
|
+
@default_strategy = false
|
|
42
|
+
@safe_vars = []
|
|
43
|
+
@blocks = {}
|
|
44
|
+
elsif node.is_a?(Node::Expression::Filter)
|
|
45
|
+
return pre_escape_filter_node(node, env)
|
|
46
|
+
elsif node.is_a?(Node::Print) && (type = need_escaping) != false
|
|
47
|
+
expression = node.nodes[:expr]
|
|
48
|
+
|
|
49
|
+
if expression.is_a?(Node::Expression::OperatorEscape)
|
|
50
|
+
escape_conditional(expression, env, type)
|
|
51
|
+
else
|
|
52
|
+
node.nodes[:expr] = escape_expression(expression, env, type)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
return node
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if node.is_a?(Node::AutoEscape) || node.is_a?(Node::Block)
|
|
59
|
+
@status_stack.pop
|
|
60
|
+
elsif node.is_a?(Node::BlockReference)
|
|
61
|
+
@blocks[node.attributes[:name]] = need_escaping
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
node
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# @param [Node::Expression::Base, Node::Expression::OperatorEscape] expression
|
|
70
|
+
# @param [Environment] env
|
|
71
|
+
# @param [Symbol] type
|
|
72
|
+
def escape_conditional(expression, env, type)
|
|
73
|
+
expression.operand_names_to_escape.each do |name|
|
|
74
|
+
operand = expression.nodes[name]
|
|
75
|
+
|
|
76
|
+
if operand.is_a?(Node::Expression::OperatorEscape)
|
|
77
|
+
escape_conditional(operand, env, type)
|
|
78
|
+
else
|
|
79
|
+
expression.nodes[name] = escape_expression(operand, env, type)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [SafeAnalysis]
|
|
85
|
+
attr_reader :safe_analysis
|
|
86
|
+
|
|
87
|
+
# @return [String, Boolean]
|
|
88
|
+
attr_reader :default_strategy
|
|
89
|
+
|
|
90
|
+
# @return [Hash{String => String, Boolean}]
|
|
91
|
+
attr_reader :blocks
|
|
92
|
+
|
|
93
|
+
# @return [Array]
|
|
94
|
+
attr_reader :status_stack
|
|
95
|
+
|
|
96
|
+
# @return [String, Boolean]
|
|
97
|
+
def need_escaping
|
|
98
|
+
unless status_stack.empty?
|
|
99
|
+
return status_stack.last
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
default_strategy || false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @param [Node::Expression::Base] expression
|
|
106
|
+
# @param [Environment] env
|
|
107
|
+
# @param [Symbol] type
|
|
108
|
+
def escape_expression(expression, env, type)
|
|
109
|
+
safe_for?(type, expression, env) ? expression : get_escaper_filter(env, type, expression)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @param [Node::Exression::Filter] filter
|
|
113
|
+
# @param [Environment] env
|
|
114
|
+
# @return [Node::Expression::Filter]
|
|
115
|
+
def pre_escape_filter_node(filter, env)
|
|
116
|
+
if (type = filter.attributes[:twig_callable].pre_escape).nil?
|
|
117
|
+
return filter
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
node = filter.nodes[:node]
|
|
121
|
+
|
|
122
|
+
if safe_for?(type, node, env)
|
|
123
|
+
return filter
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
filter.nodes[:node] = get_escaper_filter(env, type, node)
|
|
127
|
+
|
|
128
|
+
filter
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @param [Environment] env
|
|
132
|
+
# @param [Symbol] type
|
|
133
|
+
# @param [Node::Expression::Base] node
|
|
134
|
+
# @return [Node::Expression::Filter]
|
|
135
|
+
def get_escaper_filter(env, type, node)
|
|
136
|
+
line = node.lineno
|
|
137
|
+
filter = env.filter('escape')
|
|
138
|
+
args = Node::Nodes.new(
|
|
139
|
+
AutoHash.new.add(
|
|
140
|
+
Node::Expression::Constant.new(type, line),
|
|
141
|
+
Node::Expression::Constant.new(nil, line),
|
|
142
|
+
Node::Expression::Constant.new(true, line)
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
Node::Expression::Filter.new(node, filter, args, line)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def safe_for?(type, expression, env)
|
|
150
|
+
safe = safe_analysis.safe(expression)
|
|
151
|
+
|
|
152
|
+
if safe.empty?
|
|
153
|
+
@traverser ||= NodeTraverser.new(env, [safe_analysis])
|
|
154
|
+
|
|
155
|
+
safe_analysis.safe_vars = @safe_vars
|
|
156
|
+
@traverser.traverse(expression)
|
|
157
|
+
|
|
158
|
+
safe = safe_analysis.safe(expression)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
safe.include?(type) || safe.include?(:all)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module NodeVisitor
|
|
5
|
+
class SafeAnalysis < Base
|
|
6
|
+
SAFE_ALL = [
|
|
7
|
+
Node::Expression::Constant,
|
|
8
|
+
Node::Expression::BlockReference,
|
|
9
|
+
Node::Expression::Parent,
|
|
10
|
+
Node::Expression::MacroReference,
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
@data = {}
|
|
17
|
+
@safe_vars = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def enter_node(node, env)
|
|
21
|
+
node
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def leave_node(node, env)
|
|
25
|
+
if SAFE_ALL.any? { |klass| node.is_a?(klass) }
|
|
26
|
+
set_safe(node, [:all])
|
|
27
|
+
elsif node.is_a?(Node::Expression::OperatorEscape)
|
|
28
|
+
operands = node.operand_names_to_escape
|
|
29
|
+
|
|
30
|
+
if operands.length > 2
|
|
31
|
+
raise ArgumentError, "Operators with more than 2 operands are not supported yet, got #{operands.length}."
|
|
32
|
+
elsif operands.length == 2
|
|
33
|
+
safe = intersect_safe(safe(node.nodes[operands[0]]), safe(node.nodes[operands[1]]))
|
|
34
|
+
set_safe(node, safe)
|
|
35
|
+
end
|
|
36
|
+
elsif node.is_a?(Node::Expression::Filter)
|
|
37
|
+
# Filter expression is safe when the filter is safe
|
|
38
|
+
if node.attributes.key?(:twig_callable) && (filter = node.attributes[:twig_callable])
|
|
39
|
+
if (safe = filter.safe(node.nodes[:arguments])).empty?
|
|
40
|
+
safe = intersect_safe(safe(node.nodes[:node]), filter.preserves_safety)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
set_safe(node, safe)
|
|
44
|
+
end
|
|
45
|
+
elsif node.is_a?(Node::Expression::Function)
|
|
46
|
+
# Function expression is safe when the function is safe
|
|
47
|
+
if node.attributes.key?(:twig_callable) && (function = node.attributes[:twig_callable])
|
|
48
|
+
set_safe(node, function.safe(node.nodes[:arguments]))
|
|
49
|
+
else
|
|
50
|
+
set_safe(node, [])
|
|
51
|
+
end
|
|
52
|
+
elsif node.is_a?(Node::Expression::GetAttribute) && node.nodes[:node].is_a?(Node::Expression::Variable::Context)
|
|
53
|
+
name = node.nodes[:node].attributes[:name]
|
|
54
|
+
|
|
55
|
+
if safe_vars.include?(name)
|
|
56
|
+
set_safe(node, [:all])
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
node
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param [Node::Base] node
|
|
64
|
+
def safe(node)
|
|
65
|
+
hash = node.object_id
|
|
66
|
+
|
|
67
|
+
unless data.key?(hash)
|
|
68
|
+
return []
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
data[hash].each do |bucket|
|
|
72
|
+
next unless bucket[:key] == node
|
|
73
|
+
|
|
74
|
+
if bucket[:value].include?(:html_attr)
|
|
75
|
+
bucket[:value] << :html
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
return bucket[:value]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
[]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param [Array<String>] safe_vars
|
|
85
|
+
def safe_vars=(safe_vars)
|
|
86
|
+
@safe_vars = safe_vars.dup
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# @return [Hash{Integer => Array}]
|
|
92
|
+
attr_reader :data
|
|
93
|
+
|
|
94
|
+
# @return [Array<String>]
|
|
95
|
+
attr_reader :safe_vars
|
|
96
|
+
|
|
97
|
+
# @param [Node::Base] node
|
|
98
|
+
# @param [Array] safe
|
|
99
|
+
def set_safe(node, safe)
|
|
100
|
+
hash = node.object_id
|
|
101
|
+
found = data.fetch(hash, []).detect { |bucket| bucket[:key] == node }
|
|
102
|
+
|
|
103
|
+
if found
|
|
104
|
+
found[:value] = safe
|
|
105
|
+
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@data[hash] ||= []
|
|
110
|
+
@data[hash] << {
|
|
111
|
+
key: node,
|
|
112
|
+
value: safe,
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @param [Array<String>] a
|
|
117
|
+
# @param [Array<String>] b
|
|
118
|
+
def intersect_safe(a, b)
|
|
119
|
+
return [] if a.empty? || b.empty?
|
|
120
|
+
return b if a.include?(:all)
|
|
121
|
+
return a if b.include?(:all)
|
|
122
|
+
|
|
123
|
+
a & b
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module NodeVisitor
|
|
5
|
+
# This visitor checks the contents of the Spread operator to see if it's a hash or array.
|
|
6
|
+
# If it is, it converts it to a HashSpread or ArraySpread node.
|
|
7
|
+
#
|
|
8
|
+
# The cases where this is not, would be some kind of variable expression which we cannot determine
|
|
9
|
+
# at compile time, and still need to be sent through the ArgumentSpreader so that they can be spread
|
|
10
|
+
# properly into the destination callable.
|
|
11
|
+
class Spreader < Base
|
|
12
|
+
def enter_node(node, env)
|
|
13
|
+
node
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def leave_node(node, env)
|
|
17
|
+
unless node.is_a?(Node::Expression::Unary::Spread)
|
|
18
|
+
return node
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if node.nodes[:node].instance_of?(Node::Expression::Hash)
|
|
22
|
+
return Node::Expression::Unary::HashSpread.new(
|
|
23
|
+
node.nodes[:node],
|
|
24
|
+
node.lineno
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if node.nodes[:node].instance_of?(Node::Expression::Array)
|
|
29
|
+
return Node::Expression::Unary::ArraySpread.new(
|
|
30
|
+
node.nodes[:node],
|
|
31
|
+
node.lineno
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
node
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/twig/output_buffer.rb
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Twig
|
|
4
|
+
# Anything passed through the Twig OutputBuffer is already
|
|
5
|
+
# being checked for escaping issues, so we can mark everything
|
|
6
|
+
# html_safe here. When being used with Rails, we just pass everything
|
|
7
|
+
# through this buffer into the Rails safe buffer so we don't need a
|
|
8
|
+
# bunch of html_safe calls everywhere.
|
|
4
9
|
class OutputBuffer
|
|
5
|
-
def initialize
|
|
10
|
+
def initialize(decorated = nil)
|
|
11
|
+
@decorated = decorated
|
|
6
12
|
@buffer = +''
|
|
7
13
|
end
|
|
8
14
|
|
|
9
15
|
def append=(string)
|
|
10
|
-
|
|
11
|
-
string = string.to_s
|
|
12
|
-
|
|
13
|
-
@buffer << if string.html_safe?
|
|
14
|
-
string
|
|
15
|
-
else
|
|
16
|
-
CGI.escapeHTML(string)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
16
|
+
self.safe_append = string
|
|
19
17
|
end
|
|
20
18
|
|
|
21
19
|
def safe_append=(string)
|
|
22
|
-
@
|
|
20
|
+
if @decorated
|
|
21
|
+
@decorated.safe_append = string
|
|
22
|
+
else
|
|
23
|
+
@buffer << string.to_s.html_safe
|
|
24
|
+
end
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def to_s
|
|
26
|
-
@buffer
|
|
28
|
+
@decorated || @buffer
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
end
|