flavour_saver 0.3.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 +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/Guardfile +12 -0
- data/LICENSE +22 -0
- data/README.md +339 -0
- data/Rakefile +2 -0
- data/flavour_saver.gemspec +28 -0
- data/lib/flavour_saver/helpers.rb +118 -0
- data/lib/flavour_saver/lexer.rb +127 -0
- data/lib/flavour_saver/nodes.rb +177 -0
- data/lib/flavour_saver/parser.rb +183 -0
- data/lib/flavour_saver/partial.rb +29 -0
- data/lib/flavour_saver/rails_partial.rb +10 -0
- data/lib/flavour_saver/runtime.rb +269 -0
- data/lib/flavour_saver/template.rb +19 -0
- data/lib/flavour_saver/version.rb +3 -0
- data/lib/flavour_saver.rb +78 -0
- data/spec/acceptance/backtrack_spec.rb +14 -0
- data/spec/acceptance/comment_spec.rb +12 -0
- data/spec/acceptance/custom_block_helper_spec.rb +35 -0
- data/spec/acceptance/custom_helper_spec.rb +15 -0
- data/spec/acceptance/ensure_no_rce_spec.rb +26 -0
- data/spec/acceptance/handlebars_qunit_spec.rb +911 -0
- data/spec/acceptance/if_else_spec.rb +17 -0
- data/spec/acceptance/multi_level_with_spec.rb +15 -0
- data/spec/acceptance/one_character_identifier_spec.rb +13 -0
- data/spec/acceptance/runtime_run_spec.rb +27 -0
- data/spec/acceptance/sections_spec.rb +25 -0
- data/spec/acceptance/segment_literals_spec.rb +26 -0
- data/spec/acceptance/simple_expression_spec.rb +13 -0
- data/spec/fixtures/backtrack.hbs +4 -0
- data/spec/fixtures/comment.hbs +1 -0
- data/spec/fixtures/custom_block_helper.hbs +3 -0
- data/spec/fixtures/custom_helper.hbs +1 -0
- data/spec/fixtures/if_else.hbs +5 -0
- data/spec/fixtures/multi_level_if.hbs +12 -0
- data/spec/fixtures/multi_level_with.hbs +11 -0
- data/spec/fixtures/one_character_identifier.hbs +1 -0
- data/spec/fixtures/sections.hbs +9 -0
- data/spec/fixtures/simple_expression.hbs +1 -0
- data/spec/lib/flavour_saver/lexer_spec.rb +187 -0
- data/spec/lib/flavour_saver/parser_spec.rb +277 -0
- data/spec/lib/flavour_saver/runtime_spec.rb +190 -0
- data/spec/lib/flavour_saver/template_spec.rb +5 -0
- metadata +243 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'rltk'
|
2
|
+
require 'rltk/ast'
|
3
|
+
require 'flavour_saver/nodes'
|
4
|
+
|
5
|
+
module FlavourSaver
|
6
|
+
class Parser < RLTK::Parser
|
7
|
+
|
8
|
+
class UnbalancedBlockError < StandardError; end
|
9
|
+
|
10
|
+
class Environment < RLTK::Parser::Environment
|
11
|
+
def push_block block
|
12
|
+
blocks.push(block.name)
|
13
|
+
block
|
14
|
+
end
|
15
|
+
|
16
|
+
def pop_block block
|
17
|
+
b = blocks.pop
|
18
|
+
raise UnbalancedBlockError, "Unable to find matching opening for {{/#{block.name}}}" if b != block.name
|
19
|
+
block
|
20
|
+
end
|
21
|
+
|
22
|
+
def blocks
|
23
|
+
@blocks ||= []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
left :DOT
|
28
|
+
right :EQ
|
29
|
+
|
30
|
+
production(:template) do
|
31
|
+
clause('template_items') { |i| TemplateNode.new(i) }
|
32
|
+
clause('') { TemplateNode.new([]) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# empty_list(:template_items, [:output, :expression], 'WHITE?')
|
36
|
+
production(:template_items) do
|
37
|
+
clause('template_item') { |i| [i] }
|
38
|
+
clause('template_items template_item') { |i0,i1| i0 << i1 }
|
39
|
+
end
|
40
|
+
|
41
|
+
production(:template_item) do
|
42
|
+
clause('output') { |e| e }
|
43
|
+
clause('expression') { |e| e }
|
44
|
+
end
|
45
|
+
|
46
|
+
production(:output) do
|
47
|
+
clause('OUT') { |o| OutputNode.new(o) }
|
48
|
+
end
|
49
|
+
|
50
|
+
production(:expression) do
|
51
|
+
clause('block_expression') { |e| e }
|
52
|
+
clause('expr') { |e| ExpressionNode.new(e) }
|
53
|
+
clause('expr_comment') { |e| CommentNode.new(e) }
|
54
|
+
clause('expr_safe') { |e| SafeExpressionNode.new(e) }
|
55
|
+
clause('partial') { |e| e }
|
56
|
+
end
|
57
|
+
|
58
|
+
production(:partial) do
|
59
|
+
clause('EXPRST WHITE? GT WHITE? IDENT WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
|
60
|
+
clause('EXPRST WHITE? GT WHITE? IDENT WHITE? call WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,e1,nil) }
|
61
|
+
clause('EXPRST WHITE? GT WHITE? IDENT WHITE? lit WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,[],e1) }
|
62
|
+
clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? EXPRE') { |_,_,_,_,e,_,_| PartialNode.new(e,[]) }
|
63
|
+
clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? call WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,e1,nil) }
|
64
|
+
clause('EXPRST WHITE? GT WHITE? LITERAL WHITE? lit WHITE? EXPRE') { |_,_,_,_,e0,_,e1,_,_| PartialNode.new(e0,[],e1) }
|
65
|
+
end
|
66
|
+
|
67
|
+
production(:block_expression) do
|
68
|
+
clause('expr_bl_start template expr_else template expr_bl_end') { |e0,e1,_,e3,e2| BlockExpressionNodeWithElse.new([e0], e1,e2,e3) }
|
69
|
+
clause('expr_bl_start template expr_bl_end') { |e0,e1,e2| BlockExpressionNode.new([e0],e1,e2) }
|
70
|
+
clause('expr_bl_inv_start template expr_else template expr_bl_end') { |e0,e1,_,e3,e2| BlockExpressionNodeWithElse.new([e0], e2,e2,e1) }
|
71
|
+
clause('expr_bl_inv_start template expr_bl_end') { |e0,e1,e2| BlockExpressionNodeWithElse.new([e0],TemplateNode.new([]),e2,e1) }
|
72
|
+
end
|
73
|
+
|
74
|
+
production(:expr_else) do
|
75
|
+
clause('EXPRST WHITE? ELSE WHITE? EXPRE') { |_,_,_,_,_| }
|
76
|
+
clause('EXPRST WHITE? HAT WHITE? EXPRE') { |_,_,_,_,_| }
|
77
|
+
end
|
78
|
+
|
79
|
+
production(:expr) do
|
80
|
+
clause('EXPRST expression_contents EXPRE') { |_,e,_| e }
|
81
|
+
end
|
82
|
+
|
83
|
+
production(:expr_comment) do
|
84
|
+
clause('EXPRST BANG COMMENT EXPRE') { |_,_,e,_| e }
|
85
|
+
end
|
86
|
+
|
87
|
+
production(:expr_safe) do
|
88
|
+
clause('TEXPRST expression_contents TEXPRE') { |_,e,_| e }
|
89
|
+
clause('EXPRST AMP expression_contents EXPRE') { |_,_,e,_| e }
|
90
|
+
end
|
91
|
+
|
92
|
+
production(:expr_bl_start) do
|
93
|
+
clause('EXPRST HASH WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| push_block CallNode.new(e,[]) }
|
94
|
+
clause('EXPRST HASH WHITE? IDENT WHITE arguments EXPRE') { |_,_,_,e,_,a,_| push_block CallNode.new(e,a) }
|
95
|
+
end
|
96
|
+
|
97
|
+
production(:expr_bl_inv_start) do
|
98
|
+
clause('EXPRST HAT WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| push_block CallNode.new(e,[]) }
|
99
|
+
clause('EXPRST HAT WHITE? IDENT WHITE arguments EXPRE') { |_,_,_,e,_,a,_| push_block CallNode.new(e,a) }
|
100
|
+
end
|
101
|
+
|
102
|
+
production(:expr_bl_end) do
|
103
|
+
clause('EXPRST FWSL WHITE? IDENT WHITE? EXPRE') { |_,_,_,e,_,_| pop_block CallNode.new(e,[]) }
|
104
|
+
end
|
105
|
+
|
106
|
+
production(:expression_contents) do
|
107
|
+
clause('WHITE? call WHITE?') { |_,e,_| e }
|
108
|
+
clause('WHITE? local WHITE?') { |_,e,_| [e] }
|
109
|
+
end
|
110
|
+
|
111
|
+
production(:call) do
|
112
|
+
clause('object_path') { |e| e }
|
113
|
+
clause('object_path WHITE arguments') { |e0,_,e1| e0.last.arguments = e1; e0 }
|
114
|
+
clause('DOT') { |_| [CallNode.new('this', [])] }
|
115
|
+
end
|
116
|
+
|
117
|
+
production(:local) do
|
118
|
+
clause('AT IDENT') { |_,e| LocalVarNode.new(e) }
|
119
|
+
end
|
120
|
+
|
121
|
+
production('arguments') do
|
122
|
+
clause('argument_list') { |e| e }
|
123
|
+
clause('argument_list hash') { |e0,e1| e0 + [e1] }
|
124
|
+
clause('hash') { |e| [e] }
|
125
|
+
end
|
126
|
+
|
127
|
+
nonempty_list(:argument_list, [:object_path,:lit], :WHITE)
|
128
|
+
|
129
|
+
production(:lit) do
|
130
|
+
clause('string') { |e| e }
|
131
|
+
clause('number') { |e| e }
|
132
|
+
clause('boolean') { |e| e }
|
133
|
+
end
|
134
|
+
|
135
|
+
production(:string) do
|
136
|
+
clause('STRING') { |e| StringNode.new(e) }
|
137
|
+
end
|
138
|
+
|
139
|
+
production(:number) do
|
140
|
+
clause('NUMBER') { |n| NumberNode.new(n) }
|
141
|
+
end
|
142
|
+
|
143
|
+
production(:boolean) do
|
144
|
+
clause('BOOL') { |b| b ? TrueNode.new(true) : FalseNode.new(false) }
|
145
|
+
end
|
146
|
+
|
147
|
+
production(:hash) do
|
148
|
+
clause('hash_item') { |e| e }
|
149
|
+
clause('hash WHITE hash_item') { |e0,_,e1| e0.merge(e1) }
|
150
|
+
end
|
151
|
+
|
152
|
+
production(:hash_item) do
|
153
|
+
clause('IDENT EQ string') { |e0,_,e1| { e0.to_sym => e1 } }
|
154
|
+
clause('IDENT EQ object_path') { |e0,_,e1| { e0.to_sym => e1 } }
|
155
|
+
end
|
156
|
+
|
157
|
+
production(:object_sep) do
|
158
|
+
clause('DOT') { |_| }
|
159
|
+
clause('FWSL') { |_| }
|
160
|
+
end
|
161
|
+
|
162
|
+
nonempty_list(:object_path, :object, :object_sep)
|
163
|
+
|
164
|
+
production(:object) do
|
165
|
+
clause('IDENT') { |e| CallNode.new(e, []) }
|
166
|
+
clause('LITERAL') { |e| LiteralCallNode.new(e, []) }
|
167
|
+
clause('parent_call') { |e| e }
|
168
|
+
end
|
169
|
+
|
170
|
+
production(:parent_call) do
|
171
|
+
clause('backtrack IDENT') { |i,e| ParentCallNode.new(e,[],i) }
|
172
|
+
clause('backtrack LITERAL') { |i,e| ParentCallNode.new(e,[],i) }
|
173
|
+
end
|
174
|
+
|
175
|
+
production(:backtrack) do
|
176
|
+
clause('DOT DOT FWSL') { |_,_,_| 1 }
|
177
|
+
clause('backtrack DOT DOT FWSL') { |i,_,_,_| i += 1 }
|
178
|
+
end
|
179
|
+
|
180
|
+
finalize
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FlavourSaver
|
2
|
+
UnknownPartialException = Class.new(StandardError)
|
3
|
+
|
4
|
+
class Partial
|
5
|
+
|
6
|
+
def self.register_partial(name, content=nil, &block)
|
7
|
+
if block.respond_to? :call
|
8
|
+
partials[name.to_s] = block
|
9
|
+
else
|
10
|
+
partials[name.to_s] = Parser.parse(Lexer.lex(content))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.reset_partials
|
15
|
+
@partials = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.partials
|
19
|
+
@partials ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.fetch(name)
|
23
|
+
p = partials[name.to_s]
|
24
|
+
raise UnknownPartialException, "I can't find the partial named #{name.inspect}" unless p
|
25
|
+
p
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module FlavourSaver
|
4
|
+
UnknownNodeTypeException = Class.new(StandardError)
|
5
|
+
UnknownContextException = Class.new(StandardError)
|
6
|
+
InappropriateUseOfElseException = Class.new(StandardError)
|
7
|
+
UndefinedPrivateVariableException = Class.new(StandardError)
|
8
|
+
UnknownHelperException = Class.new(RuntimeError)
|
9
|
+
class Runtime
|
10
|
+
|
11
|
+
attr_accessor :context, :parent, :ast
|
12
|
+
|
13
|
+
def self.run(ast, context, locals={}, helpers=[])
|
14
|
+
self.new(ast, context, locals, helpers).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(ast, context=nil, locals={}, helpers=[],parent=nil)
|
18
|
+
@ast = ast
|
19
|
+
@locals = locals
|
20
|
+
@helpers = helpers
|
21
|
+
@context = context
|
22
|
+
@parent = parent
|
23
|
+
@privates = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s(tmp_context = nil,privates={})
|
27
|
+
result = nil
|
28
|
+
if tmp_context
|
29
|
+
old_context = @context
|
30
|
+
@context = tmp_context
|
31
|
+
old_privates = @privates
|
32
|
+
@privates = @privates.dup.merge(privates) if privates.any?
|
33
|
+
result = evaluate_node(@ast)
|
34
|
+
@privates = old_privates
|
35
|
+
@context = old_context
|
36
|
+
else
|
37
|
+
result = evaluate_node(@ast)
|
38
|
+
end
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def private_variable_set(name,value)
|
43
|
+
@privates[name.to_s] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
def private_variable_get(name)
|
47
|
+
begin
|
48
|
+
@privates.fetch(name)
|
49
|
+
rescue KeyError => e
|
50
|
+
raise UndefinedPrivateVariableException, "private variable not found @#{name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def strip(tmp_context = nil)
|
55
|
+
self.to_s(tmp_context).gsub(/[\s\r\n]+/,' ').strip
|
56
|
+
end
|
57
|
+
|
58
|
+
def evaluate_node(node)
|
59
|
+
case node
|
60
|
+
when TemplateNode
|
61
|
+
node.items.map { |n| evaluate_node(n) }.join('')
|
62
|
+
when BlockExpressionNode
|
63
|
+
evaluate_block(node).to_s
|
64
|
+
when OutputNode
|
65
|
+
node.value
|
66
|
+
when NumberNode
|
67
|
+
if node.value =~ /\./
|
68
|
+
node.value.to_f
|
69
|
+
else
|
70
|
+
node.value.to_i
|
71
|
+
end
|
72
|
+
when ValueNode
|
73
|
+
node.value
|
74
|
+
when SafeExpressionNode
|
75
|
+
evaluate_expression(node).to_s
|
76
|
+
when ExpressionNode
|
77
|
+
escape(evaluate_expression(node).to_s)
|
78
|
+
when CallNode
|
79
|
+
evaluate_call(node)
|
80
|
+
when Hash
|
81
|
+
node.each do |key,value|
|
82
|
+
node[key] = evaluate_argument(value)
|
83
|
+
end
|
84
|
+
node
|
85
|
+
when CommentNode
|
86
|
+
''
|
87
|
+
when PartialNode
|
88
|
+
evaluate_partial(node)
|
89
|
+
else
|
90
|
+
raise UnknownNodeTypeException, "Don't know how to deal with a node of type #{node.class.to_s.inspect}."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def parent
|
95
|
+
raise UnknownContextException, "No parent context in which to evaluate the parentiness of the context" unless @parent
|
96
|
+
@parent
|
97
|
+
end
|
98
|
+
|
99
|
+
def parent?
|
100
|
+
!!@parent
|
101
|
+
end
|
102
|
+
|
103
|
+
def evaluate_partial(node)
|
104
|
+
_context = context
|
105
|
+
_context = evaluate_argument(node.context) if node.context
|
106
|
+
if defined?(::Rails)
|
107
|
+
context.send(:render, :partial => node.name, :object => _context)
|
108
|
+
else
|
109
|
+
partial = Partial.fetch(node.name)
|
110
|
+
if partial.respond_to? :call
|
111
|
+
partial.call(_context)
|
112
|
+
else
|
113
|
+
create_child_runtime(partial).to_s(_context)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def evaluate_call(call, context=context, &block)
|
119
|
+
context = Helpers.decorate_with(context,@helpers,@locals) unless context.is_a? Helpers::Decorator
|
120
|
+
case call
|
121
|
+
when ParentCallNode
|
122
|
+
depth = call.depth
|
123
|
+
(2..depth).inject(parent) { |p| p.parent }.evaluate_call(call.to_callnode,&block)
|
124
|
+
when LiteralCallNode
|
125
|
+
result = context.send(:[], call.name)
|
126
|
+
result = result.call(*call.arguments.map { |a| evaluate_argument(a) },&block) if result.respond_to? :call
|
127
|
+
result
|
128
|
+
when LocalVarNode
|
129
|
+
result = private_variable_get(call.name)
|
130
|
+
else
|
131
|
+
raise UnknownHelperException, "Template context doesn't respond to method #{call.name.inspect}." unless context.respond_to? call.name
|
132
|
+
context.public_send(call.name, *call.arguments.map { |a| evaluate_argument(a) }, &block)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def evaluate_argument(arg)
|
137
|
+
if arg.is_a? Array
|
138
|
+
evaluate_object_path(arg)
|
139
|
+
else
|
140
|
+
evaluate_node(arg)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def evaluate_object_path(path, &block)
|
145
|
+
path.inject(context) do |context,call|
|
146
|
+
context = evaluate_call(call, context, &block)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def evaluate_expression(node, &block)
|
151
|
+
evaluate_object_path(node.method)
|
152
|
+
end
|
153
|
+
|
154
|
+
def evaluate_block(node,block_context=@context)
|
155
|
+
call = node.method.first
|
156
|
+
content_runtime = create_child_runtime(node.contents)
|
157
|
+
alternate_runtime = create_child_runtime(node.alternate) if node.respond_to? :alternate
|
158
|
+
block_runtime = BlockRuntime.new(block_context,content_runtime,alternate_runtime)
|
159
|
+
|
160
|
+
result = evaluate_call(call, block_context) { block_runtime }
|
161
|
+
|
162
|
+
# If the helper fails to call it's provided block then all
|
163
|
+
# sorts of wacky default behaviour kicks in. I don't like it,
|
164
|
+
# but that's the spec.
|
165
|
+
if !block_runtime.rendered?
|
166
|
+
|
167
|
+
# If the result is collectiony then act as an implicit
|
168
|
+
# "each"
|
169
|
+
if result && result.respond_to?(:each)
|
170
|
+
if result.respond_to?(:size) && (result.size > 0)
|
171
|
+
r = []
|
172
|
+
# Not using #each_with_index because the object might
|
173
|
+
# not actually be an Enumerable
|
174
|
+
count = 0
|
175
|
+
result.each do |e|
|
176
|
+
r << block_runtime.contents(e, {'index' => count})
|
177
|
+
count += 1
|
178
|
+
end
|
179
|
+
result = r.join('')
|
180
|
+
else
|
181
|
+
result = block_runtime.inverse
|
182
|
+
end
|
183
|
+
|
184
|
+
# Otherwise it behaves as an implicit "if"
|
185
|
+
elsif result
|
186
|
+
result = block_runtime.contents result
|
187
|
+
else
|
188
|
+
if block_runtime.has_inverse?
|
189
|
+
result = block_runtime.inverse
|
190
|
+
else
|
191
|
+
result = ''
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
result
|
196
|
+
end
|
197
|
+
|
198
|
+
def create_child_runtime(body=[])
|
199
|
+
node = body.is_a?(TemplateNode) ? body : TemplateNode.new(body)
|
200
|
+
Runtime.new(node,nil,@locals,@helpers,self)
|
201
|
+
end
|
202
|
+
|
203
|
+
def inspect
|
204
|
+
"#<FlavourSaver::Runtime contents=#{@ast.inspect}>"
|
205
|
+
end
|
206
|
+
|
207
|
+
class BlockRuntime
|
208
|
+
def initialize(block_context,content_runtime,alternate_runtime=nil)
|
209
|
+
@block_context = block_context
|
210
|
+
@content_runtime = content_runtime
|
211
|
+
@alternate_runtime = alternate_runtime
|
212
|
+
@render_count = 0
|
213
|
+
end
|
214
|
+
|
215
|
+
def contents(context=@block_context,locals={})
|
216
|
+
@render_count += 1
|
217
|
+
@content_runtime.to_s(context,locals) if @content_runtime
|
218
|
+
end
|
219
|
+
|
220
|
+
def inverse(context=@block_context)
|
221
|
+
@render_count += 1
|
222
|
+
@alternate_runtime.to_s(context) if @alternate_runtime
|
223
|
+
end
|
224
|
+
|
225
|
+
def has_inverse?
|
226
|
+
!!@alternate_runtime
|
227
|
+
end
|
228
|
+
|
229
|
+
def rendered?
|
230
|
+
@render_count > 0 ? @render_count : false
|
231
|
+
end
|
232
|
+
|
233
|
+
def rendered!
|
234
|
+
@render_count += 1
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def escape(output)
|
241
|
+
if output.respond_to?(:html_safe) && output.html_safe?
|
242
|
+
# If the string is already marked as html_safe then don't
|
243
|
+
# escape it any further.
|
244
|
+
output
|
245
|
+
|
246
|
+
else
|
247
|
+
output = CGI.escapeHTML(output)
|
248
|
+
|
249
|
+
# We can't just use CGI.escapeHTML because Handlebars does extra
|
250
|
+
# escaping for its JavaScript environment. Thems the breaks.
|
251
|
+
output = output.gsub(/(['"`])/) do |match|
|
252
|
+
case match
|
253
|
+
when "'"
|
254
|
+
"'"
|
255
|
+
when '"'
|
256
|
+
"""
|
257
|
+
when '`'
|
258
|
+
"`"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Mark it as already escaped if we're in Rails
|
263
|
+
output.html_safe if output.respond_to? :html_safe
|
264
|
+
|
265
|
+
output
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|