mvz-ruby-handlebars 0.0.8 → 0.0.9
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/CHANGELOG.md +10 -0
- data/README.md +59 -17
- data/lib/ruby-handlebars.rb +21 -38
- data/lib/ruby-handlebars/context.rb +30 -4
- data/lib/ruby-handlebars/escapers/dummy_escaper.rb +9 -0
- data/lib/ruby-handlebars/escapers/html_escaper.rb +9 -0
- data/lib/ruby-handlebars/helper.rb +23 -19
- data/lib/ruby-handlebars/helpers/default_helper.rb +28 -0
- data/lib/ruby-handlebars/helpers/each_helper.rb +31 -0
- data/lib/ruby-handlebars/helpers/helper_missing_helper.rb +18 -0
- data/lib/ruby-handlebars/helpers/if_helper.rb +23 -0
- data/lib/ruby-handlebars/helpers/register_default_helpers.rb +15 -0
- data/lib/ruby-handlebars/helpers/unless_helper.rb +21 -0
- data/lib/ruby-handlebars/parser.rb +53 -7
- data/lib/ruby-handlebars/tree.rb +99 -11
- data/lib/ruby-handlebars/version.rb +1 -1
- data/spec/handlebars_spec.rb +143 -193
- data/spec/parser_spec.rb +162 -21
- data/spec/ruby-handlebars/context_spec.rb +145 -0
- data/spec/ruby-handlebars/helpers/each_helper_spec.rb +331 -0
- data/spec/ruby-handlebars/helpers/helper_missing_helper_spec.rb +45 -0
- data/spec/ruby-handlebars/helpers/if_helper_spec.rb +90 -0
- data/spec/ruby-handlebars/helpers/register_default_helpers_spec.rb +34 -0
- data/spec/ruby-handlebars/helpers/shared.rb +55 -0
- data/spec/ruby-handlebars/helpers/unless_helper_spec.rb +65 -0
- data/spec/spec_helper.rb +5 -0
- metadata +36 -6
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'default_helper'
|
2
|
+
|
3
|
+
module Handlebars
|
4
|
+
module Helpers
|
5
|
+
class IfHelper < DefaultHelper
|
6
|
+
def self.registry_name
|
7
|
+
'if'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.apply(context, condition, block, else_block)
|
11
|
+
condition = !condition.empty? if condition.respond_to?(:empty?)
|
12
|
+
|
13
|
+
if condition
|
14
|
+
block.fn(context)
|
15
|
+
elsif else_block
|
16
|
+
else_block.fn(context)
|
17
|
+
else
|
18
|
+
""
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'each_helper'
|
2
|
+
require_relative 'helper_missing_helper'
|
3
|
+
require_relative 'if_helper'
|
4
|
+
require_relative 'unless_helper'
|
5
|
+
|
6
|
+
module Handlebars
|
7
|
+
module Helpers
|
8
|
+
def self.register_default_helpers(hbs)
|
9
|
+
EachHelper.register(hbs)
|
10
|
+
HelperMissingHelper.register(hbs)
|
11
|
+
IfHelper.register(hbs)
|
12
|
+
UnlessHelper.register(hbs)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'default_helper'
|
2
|
+
|
3
|
+
module Handlebars
|
4
|
+
module Helpers
|
5
|
+
class UnlessHelper < DefaultHelper
|
6
|
+
def self.registry_name
|
7
|
+
'unless'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.apply(context, condition, block, else_block)
|
11
|
+
condition = !condition.empty? if condition.respond_to?(:empty?)
|
12
|
+
|
13
|
+
unless condition
|
14
|
+
block.fn(context)
|
15
|
+
else
|
16
|
+
else_block ? else_block.fn(context) : ""
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -10,12 +10,21 @@ module Handlebars
|
|
10
10
|
rule(:slash) { str('/')}
|
11
11
|
rule(:ocurly) { str('{')}
|
12
12
|
rule(:ccurly) { str('}')}
|
13
|
+
rule(:pipe) { str('|')}
|
14
|
+
rule(:eq) { str('=')}
|
15
|
+
|
13
16
|
|
14
17
|
rule(:docurly) { ocurly >> ocurly }
|
15
18
|
rule(:dccurly) { ccurly >> ccurly }
|
19
|
+
rule(:tocurly) { ocurly >> ocurly >> ocurly }
|
20
|
+
rule(:tccurly) { ccurly >> ccurly >> ccurly }
|
21
|
+
|
22
|
+
rule(:else_kw) { str('else') }
|
23
|
+
rule(:as_kw) { str('as') }
|
16
24
|
|
17
|
-
rule(:identifier) { match['a-zA-Z0-9_\?'].repeat(1) }
|
18
|
-
rule(:
|
25
|
+
rule(:identifier) { (else_kw >> space? >> dccurly).absent? >> match['@\-a-zA-Z0-9_\?'].repeat(1) }
|
26
|
+
rule(:directory) { (else_kw >> space? >> dccurly).absent? >> match['@\-a-zA-Z0-9_\/\?'].repeat(1) }
|
27
|
+
rule(:path) { identifier >> (dot >> (identifier | else_kw)).repeat }
|
19
28
|
|
20
29
|
rule(:nocurly) { match('[^{}]') }
|
21
30
|
rule(:eof) { any.absent? }
|
@@ -27,18 +36,49 @@ module Handlebars
|
|
27
36
|
ocurly >> eof # Opening curly that doesn't start a {{}} because it's the end
|
28
37
|
).repeat(1).as(:template_content) }
|
29
38
|
|
39
|
+
rule(:unsafe_replacement) { docurly >> space? >> path.as(:replaced_unsafe_item) >> space? >> dccurly }
|
40
|
+
rule(:safe_replacement) { tocurly >> space? >> path.as(:replaced_safe_item) >> space? >> tccurly }
|
41
|
+
|
30
42
|
rule(:sq_string) { match("'") >> match("[^']").repeat.maybe.as(:str_content) >> match("'") }
|
31
43
|
rule(:dq_string) { match('"') >> match('[^"]').repeat.maybe.as(:str_content) >> match('"') }
|
32
44
|
rule(:string) { sq_string | dq_string }
|
33
45
|
|
34
|
-
rule(:parameter) {
|
46
|
+
rule(:parameter) {
|
47
|
+
(as_kw >> space? >> pipe).absent? >>
|
48
|
+
(
|
49
|
+
(path | string).as(:parameter_name) |
|
50
|
+
(str('(') >> space? >> identifier.as(:safe_helper_name) >> (space? >> parameters.as(:parameters)).maybe >> space? >> str(')'))
|
51
|
+
)
|
52
|
+
}
|
35
53
|
rule(:parameters) { parameter >> (space >> parameter).repeat }
|
36
54
|
|
37
|
-
rule(:
|
38
|
-
rule(:
|
55
|
+
rule(:argument) { identifier.as(:key) >> space? >> eq >> space? >> parameter.as(:value) }
|
56
|
+
rule(:arguments) { argument >> (space >> argument).repeat }
|
57
|
+
|
58
|
+
rule(:unsafe_helper) { docurly >> space? >> identifier.as(:unsafe_helper_name) >> (space? >> parameters.as(:parameters)).maybe >> space? >> dccurly }
|
59
|
+
rule(:safe_helper) { tocurly >> space? >> identifier.as(:safe_helper_name) >> (space? >> parameters.as(:parameters)).maybe >> space? >> tccurly }
|
39
60
|
|
40
61
|
rule(:helper) { unsafe_helper | safe_helper }
|
41
62
|
|
63
|
+
rule(:as_block_helper) {
|
64
|
+
docurly >>
|
65
|
+
hash >>
|
66
|
+
identifier.capture(:helper_name).as(:helper_name) >>
|
67
|
+
space >> parameters.as(:parameters) >>
|
68
|
+
space >> as_kw >> space >> pipe >> space? >> parameters.as(:as_parameters) >> space? >> pipe >>
|
69
|
+
space? >>
|
70
|
+
dccurly >>
|
71
|
+
scope {
|
72
|
+
block
|
73
|
+
} >>
|
74
|
+
scope {
|
75
|
+
docurly >> space? >> else_kw >> space? >> dccurly >> scope { block_item.repeat.as(:else_block_items) }
|
76
|
+
}.maybe >>
|
77
|
+
dynamic { |src, scope|
|
78
|
+
docurly >> slash >> str(scope.captures[:helper_name]) >> dccurly
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
42
82
|
rule(:block_helper) {
|
43
83
|
docurly >>
|
44
84
|
hash >>
|
@@ -49,6 +89,9 @@ module Handlebars
|
|
49
89
|
scope {
|
50
90
|
block
|
51
91
|
} >>
|
92
|
+
scope {
|
93
|
+
docurly >> space? >> else_kw >> space? >> dccurly >> scope { block_item.repeat.as(:else_block_items) }
|
94
|
+
}.maybe >>
|
52
95
|
dynamic { |src, scope|
|
53
96
|
docurly >> slash >> str(scope.captures[:helper_name]) >> dccurly
|
54
97
|
}
|
@@ -58,12 +101,15 @@ module Handlebars
|
|
58
101
|
docurly >>
|
59
102
|
gt >>
|
60
103
|
space? >>
|
61
|
-
|
104
|
+
directory.as(:partial_name) >>
|
105
|
+
space? >>
|
106
|
+
arguments.as(:arguments).maybe >>
|
62
107
|
space? >>
|
63
108
|
dccurly
|
64
109
|
}
|
65
110
|
|
66
|
-
rule(:
|
111
|
+
rule(:block_item) { (template_content | unsafe_replacement | safe_replacement | helper | partial | block_helper | as_block_helper) }
|
112
|
+
rule(:block) { block_item.repeat.as(:block_items) }
|
67
113
|
|
68
114
|
root :block
|
69
115
|
end
|
data/lib/ruby-handlebars/tree.rb
CHANGED
@@ -14,6 +14,23 @@ module Handlebars
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
class Replacement < TreeItem.new(:item)
|
18
|
+
def _eval(context)
|
19
|
+
helper = context.get_helper(item.to_s)
|
20
|
+
if helper && helper.arity == 1
|
21
|
+
helper.apply(context)
|
22
|
+
else
|
23
|
+
context.get(item.to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class EscapedReplacement < Replacement
|
29
|
+
def _eval(context)
|
30
|
+
context.escaper.escape(super(context).to_s)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
17
34
|
class String < TreeItem.new(:content)
|
18
35
|
def _eval(context)
|
19
36
|
return content
|
@@ -30,17 +47,31 @@ module Handlebars
|
|
30
47
|
end
|
31
48
|
end
|
32
49
|
|
33
|
-
class Helper < TreeItem.new(:name, :parameters, :block)
|
50
|
+
class Helper < TreeItem.new(:name, :parameters, :block, :else_block)
|
34
51
|
def _eval(context)
|
35
|
-
|
36
|
-
|
52
|
+
helper = context.get_helper(name.to_s)
|
53
|
+
if helper.nil?
|
54
|
+
context.get_helper('helperMissing').apply(context, String.new(name.to_s))
|
37
55
|
else
|
38
|
-
|
56
|
+
helper.apply(context, parameters, block, else_block)
|
39
57
|
end
|
40
58
|
end
|
59
|
+
end
|
41
60
|
|
42
|
-
|
43
|
-
|
61
|
+
class AsHelper < TreeItem.new(:name, :parameters, :as_parameters, :block, :else_block)
|
62
|
+
def _eval(context)
|
63
|
+
helper = context.get_as_helper(name.to_s)
|
64
|
+
if helper.nil?
|
65
|
+
context.get_helper('helperMissing').apply(context, String.new(name.to_s))
|
66
|
+
else
|
67
|
+
helper.apply_as(context, parameters, as_parameters, block, else_block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class EscapedHelper < Helper
|
73
|
+
def _eval(context)
|
74
|
+
context.escaper.escape(super(context).to_s)
|
44
75
|
end
|
45
76
|
end
|
46
77
|
|
@@ -50,6 +81,15 @@ module Handlebars
|
|
50
81
|
end
|
51
82
|
end
|
52
83
|
|
84
|
+
class PartialWithArgs < TreeItem.new(:partial_name, :arguments)
|
85
|
+
def _eval(context)
|
86
|
+
[arguments].flatten.map(&:values).map do |vals|
|
87
|
+
context.add_item vals.first.to_s, vals.last._eval(context)
|
88
|
+
end
|
89
|
+
context.get_partial(partial_name.to_s).call_with_context(context)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
53
93
|
class Block < TreeItem.new(:items)
|
54
94
|
def _eval(context)
|
55
95
|
items.map {|item| item._eval(context)}.join()
|
@@ -59,22 +99,26 @@ module Handlebars
|
|
59
99
|
def add_item(i)
|
60
100
|
items << i
|
61
101
|
end
|
102
|
+
|
62
103
|
end
|
63
104
|
end
|
64
105
|
|
65
106
|
class Transform < Parslet::Transform
|
66
107
|
rule(template_content: simple(:content)) {Tree::TemplateContent.new(content)}
|
108
|
+
rule(replaced_unsafe_item: simple(:item)) {Tree::EscapedReplacement.new(item)}
|
109
|
+
rule(replaced_safe_item: simple(:item)) {Tree::Replacement.new(item)}
|
67
110
|
rule(str_content: simple(:content)) {Tree::String.new(content)}
|
68
111
|
rule(parameter_name: simple(:name)) {Tree::Parameter.new(name)}
|
69
112
|
|
70
113
|
rule(
|
71
|
-
|
114
|
+
unsafe_helper_name: simple(:name),
|
115
|
+
parameters: subtree(:parameters)
|
72
116
|
) {
|
73
|
-
Tree::
|
117
|
+
Tree::EscapedHelper.new(name, parameters)
|
74
118
|
}
|
75
119
|
|
76
120
|
rule(
|
77
|
-
|
121
|
+
safe_helper_name: simple(:name),
|
78
122
|
parameters: subtree(:parameters)
|
79
123
|
) {
|
80
124
|
Tree::Helper.new(name, parameters)
|
@@ -82,20 +126,64 @@ module Handlebars
|
|
82
126
|
|
83
127
|
rule(
|
84
128
|
helper_name: simple(:name),
|
85
|
-
block_items: subtree(:block_items)
|
129
|
+
block_items: subtree(:block_items),
|
86
130
|
) {
|
87
131
|
Tree::Helper.new(name, [], block_items)
|
88
132
|
}
|
89
133
|
|
134
|
+
rule(
|
135
|
+
helper_name: simple(:name),
|
136
|
+
block_items: subtree(:block_items),
|
137
|
+
else_block_items: subtree(:else_block_items)
|
138
|
+
) {
|
139
|
+
Tree::Helper.new(name, [], block_items, else_block_items)
|
140
|
+
}
|
141
|
+
|
90
142
|
rule(
|
91
143
|
helper_name: simple(:name),
|
92
144
|
parameters: subtree(:parameters),
|
93
|
-
block_items: subtree(:block_items)
|
145
|
+
block_items: subtree(:block_items),
|
94
146
|
) {
|
95
147
|
Tree::Helper.new(name, parameters, block_items)
|
96
148
|
}
|
97
149
|
|
150
|
+
rule(
|
151
|
+
helper_name: simple(:name),
|
152
|
+
parameters: subtree(:parameters),
|
153
|
+
block_items: subtree(:block_items),
|
154
|
+
else_block_items: subtree(:else_block_items)
|
155
|
+
) {
|
156
|
+
Tree::Helper.new(name, parameters, block_items, else_block_items)
|
157
|
+
}
|
158
|
+
|
159
|
+
rule(
|
160
|
+
helper_name: simple(:name),
|
161
|
+
parameters: subtree(:parameters),
|
162
|
+
as_parameters: subtree(:as_parameters),
|
163
|
+
block_items: subtree(:block_items),
|
164
|
+
) {
|
165
|
+
Tree::AsHelper.new(name, parameters, as_parameters, block_items)
|
166
|
+
}
|
167
|
+
|
168
|
+
rule(
|
169
|
+
helper_name: simple(:name),
|
170
|
+
parameters: subtree(:parameters),
|
171
|
+
as_parameters: subtree(:as_parameters),
|
172
|
+
block_items: subtree(:block_items),
|
173
|
+
else_block_items: subtree(:else_block_items)
|
174
|
+
) {
|
175
|
+
Tree::AsHelper.new(name, parameters, as_parameters, block_items, else_block_items)
|
176
|
+
}
|
177
|
+
|
178
|
+
rule(
|
179
|
+
partial_name: simple(:partial_name),
|
180
|
+
arguments: subtree(:arguments)
|
181
|
+
) {
|
182
|
+
Tree::PartialWithArgs.new(partial_name, arguments)
|
183
|
+
}
|
184
|
+
|
98
185
|
rule(partial_name: simple(:partial_name)) {Tree::Partial.new(partial_name)}
|
99
186
|
rule(block_items: subtree(:block_items)) {Tree::Block.new(block_items)}
|
187
|
+
rule(else_block_items: subtree(:else_block_items)) {Tree::Block.new(block_items)}
|
100
188
|
end
|
101
189
|
end
|
data/spec/handlebars_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
require_relative '../lib/ruby-handlebars'
|
3
|
+
require_relative '../lib/ruby-handlebars/escapers/dummy_escaper'
|
4
|
+
|
3
5
|
|
4
6
|
describe Handlebars::Handlebars do
|
5
7
|
let(:hbs) {Handlebars::Handlebars.new}
|
@@ -17,6 +19,18 @@ describe Handlebars::Handlebars do
|
|
17
19
|
expect(evaluate('Hello {{name}}', {name: 'world'})).to eq('Hello world')
|
18
20
|
end
|
19
21
|
|
22
|
+
it 'a double braces replacement with unsafe characters' do
|
23
|
+
expect(evaluate('Hello {{name}}', {name: '<"\'>&'})).to eq('Hello <"'>&')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'a double braces replacement with nil' do
|
27
|
+
expect(evaluate('Hello {{name}}', {name: nil})).to eq('Hello ')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'a triple braces replacement with unsafe characters' do
|
31
|
+
expect(evaluate('Hello {{{name}}}', {name: '<"\'>&'})).to eq('Hello <"\'>&')
|
32
|
+
end
|
33
|
+
|
20
34
|
it 'allows values specified by methods' do
|
21
35
|
expect(evaluate('Hello {{name}}', double(name: 'world'))).to eq('Hello world')
|
22
36
|
end
|
@@ -33,16 +47,55 @@ describe Handlebars::Handlebars do
|
|
33
47
|
expect(evaluate('My simple template: {{person.name}}', {person: {name: 'Another name'}})).to eq('My simple template: Another name')
|
34
48
|
end
|
35
49
|
|
50
|
+
it 'prefers a replacement even if its name matches a helper' do
|
51
|
+
expect(evaluate('Hello {{each}}', {each: 'world'})).to eq('Hello world')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'handles a parameter with a dash' do
|
55
|
+
expect(evaluate('Hello {{first-name}}', double("first-name": 'world'))).to eq('Hello world')
|
56
|
+
end
|
57
|
+
|
36
58
|
context 'partials' do
|
37
59
|
it 'simple' do
|
38
60
|
hbs.register_partial('plic', "Plic")
|
39
61
|
expect(evaluate("Hello {{> plic}}")).to eq("Hello Plic")
|
40
62
|
end
|
41
63
|
|
64
|
+
it 'using a name with a slash' do
|
65
|
+
hbs.register_partial('parent/plic', "Plic")
|
66
|
+
expect(evaluate("Hello {{> parent/plic}}")).to eq("Hello Plic")
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'using a name that begins with a slash' do
|
70
|
+
hbs.register_partial('/parent/plic', "Plic")
|
71
|
+
expect(evaluate("Hello {{> /parent/plic}}")).to eq("Hello Plic")
|
72
|
+
end
|
73
|
+
|
42
74
|
it 'using context' do
|
43
75
|
hbs.register_partial('brackets', "[{{name}}]")
|
44
76
|
expect(evaluate("Hello {{> brackets}}", {name: 'world'})).to eq("Hello [world]")
|
45
77
|
end
|
78
|
+
|
79
|
+
it 'with a string argument' do
|
80
|
+
hbs.register_partial('with_args', "[{{name}}]")
|
81
|
+
expect(evaluate("Hello {{> with_args name='jon'}}")).to eq("Hello [jon]")
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'with string arguments' do
|
85
|
+
hbs.register_partial('with_args', "[{{fname}} {{lname}}]")
|
86
|
+
expect(evaluate("Hello {{> with_args fname='jon' lname='doe'}}")).to eq("Hello [jon doe]")
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'with variables in arguments' do
|
90
|
+
hbs.register_partial('with_args', "[{{fname}} {{lname}}]")
|
91
|
+
expect(evaluate("Hello {{> with_args fname='jon' lname=last_name}}", {last_name: 'doe'})).to eq("Hello [jon doe]")
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'with a helper as an argument' do
|
95
|
+
hbs.register_helper('wrap_parens') {|context, value| "(#{value})"}
|
96
|
+
hbs.register_partial('with_args', "[{{fname}} {{lname}}]")
|
97
|
+
expect(evaluate("Hello {{> with_args fname='jon' lname=(wrap_parens 'doe')}}")).to eq("Hello [jon (doe)]")
|
98
|
+
end
|
46
99
|
end
|
47
100
|
|
48
101
|
context 'helpers' do
|
@@ -60,7 +113,22 @@ describe Handlebars::Handlebars do
|
|
60
113
|
it 'with multiple arguments, including strings' do
|
61
114
|
hbs.register_helper('add') {|context, left, op, right| "#{left} #{op} #{right}"}
|
62
115
|
|
63
|
-
expect(evaluate("{{add left '&' right}}", {left: 'Law', right: 'Order'})).to eq("Law & Order")
|
116
|
+
expect(evaluate("{{add left '&' right}}", {left: 'Law', right: 'Order'})).to eq("Law & Order")
|
117
|
+
expect(evaluate("{{{add left '&' right}}}", {left: 'Law', right: 'Order'})).to eq("Law & Order")
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'with an empty string argument' do
|
121
|
+
hbs.register_helper('noah') {|context, value| value.to_s.gsub(/a/, '')}
|
122
|
+
|
123
|
+
expect(evaluate("hey{{noah ''}}there", {})).to eq("heythere")
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'with helpers as arguments' do
|
127
|
+
hbs.register_helper('wrap_parens') {|context, value| "(#{value})"}
|
128
|
+
hbs.register_helper('wrap_dashes') {|context, value| "-#{value}-"}
|
129
|
+
|
130
|
+
expect(evaluate('{{wrap_dashes (wrap_parens "hello")}}', {})).to eq("-(hello)-")
|
131
|
+
expect(evaluate('{{wrap_dashes (wrap_parens world)}}', {world: "world"})).to eq("-(world)-")
|
64
132
|
end
|
65
133
|
|
66
134
|
it 'with an empty string argument' do
|
@@ -115,213 +183,95 @@ describe Handlebars::Handlebars do
|
|
115
183
|
data = {company: {people: ['a', 'b', 'c']}}
|
116
184
|
expect(evaluate("{{#each company.people}}{{{this}}}{{/each}}", data)).to eq('abc')
|
117
185
|
end
|
186
|
+
|
187
|
+
it 'a else keyword out of a helper will raise an error' do
|
188
|
+
expect { evaluate('My {{ else }} template') }.to raise_exception(Parslet::ParseFailed)
|
189
|
+
end
|
190
|
+
|
191
|
+
it '"else" can be part of a path' do
|
192
|
+
expect(evaluate('My {{ something.else }} template', { something: { else: 'awesome' }})).to eq('My awesome template')
|
193
|
+
end
|
118
194
|
end
|
119
195
|
|
120
|
-
context '
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
"{{/if}}"
|
127
|
-
].join("\n")
|
128
|
-
expect(evaluate(template, {condition: true})).to eq("\n Show something\n")
|
129
|
-
expect(evaluate(template, {condition: false})).to eq("")
|
196
|
+
context 'as_helpers' do
|
197
|
+
it 'can be used to have names parameters inside the block' do
|
198
|
+
hbs.register_as_helper('test_with') do |context, value, name, block|
|
199
|
+
context.with_temporary_context(name => value) do
|
200
|
+
block.fn(context)
|
201
|
+
end
|
130
202
|
end
|
131
203
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
]
|
140
|
-
expect(evaluate(template, {condition: true})).to eq("\n Show something\n")
|
141
|
-
expect(evaluate(template, {condition: false})).to eq("\n Do not show something\n")
|
142
|
-
end
|
204
|
+
expect(evaluate("{{#test_with name as |duck|}}Duck name is: {{duck}}{{/test_with}}", {name: "Dewey"})).to eq('Duck name is: Dewey')
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'can have multiple "as" parameters' do
|
208
|
+
hbs.register_as_helper('test_with') do |context, value1, value2, name1, name2, block|
|
209
|
+
mapping = {}
|
210
|
+
mapping[name1] = value1
|
211
|
+
mapping[name2] = value2
|
143
212
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
" {{#if second_condition}}",
|
148
|
-
" Case 1",
|
149
|
-
" {{else}}",
|
150
|
-
" Case 2",
|
151
|
-
" {{/if}}",
|
152
|
-
"{{else}}",
|
153
|
-
" {{#if second_condition}}",
|
154
|
-
" Case 3",
|
155
|
-
" {{else}}",
|
156
|
-
" Case 4",
|
157
|
-
" {{/if}}",
|
158
|
-
"{{/if}}"
|
159
|
-
].join("\n")
|
160
|
-
|
161
|
-
expect(evaluate(template, {first_condition: true, second_condition: true}).strip).to eq("Case 1")
|
162
|
-
expect(evaluate(template, {first_condition: true, second_condition: false}).strip).to eq("Case 2")
|
163
|
-
expect(evaluate(template, {first_condition: false, second_condition: true}).strip).to eq("Case 3")
|
164
|
-
expect(evaluate(template, {first_condition: false, second_condition: false}).strip).to eq("Case 4")
|
213
|
+
context.with_temporary_context(mapping) do
|
214
|
+
block.fn(context)
|
215
|
+
end
|
165
216
|
end
|
217
|
+
|
218
|
+
expect(evaluate("{{#test_with name1 name2 as |duck1 duck2|}}Duck names are {{duck1}} and {{duck2}}{{/test_with}}", {name1: "Huey", name2: "Dewey"})).to eq('Duck names are Huey and Dewey')
|
166
219
|
end
|
220
|
+
end
|
221
|
+
end
|
167
222
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
"<ul>",
|
181
|
-
" <li>Huey</li>",
|
182
|
-
" <li>Dewey</li>",
|
183
|
-
" <li>Louis</li>",
|
184
|
-
"</ul>"
|
185
|
-
].join("\n"))
|
186
|
-
|
187
|
-
data = {items: []}
|
188
|
-
expect(evaluate(template, data)).to eq([
|
189
|
-
"<ul>",
|
190
|
-
"</ul>"
|
191
|
-
].join("\n"))
|
192
|
-
end
|
223
|
+
context 'escaping characters' do
|
224
|
+
let(:escaper) { nil }
|
225
|
+
let(:name) { '<"\'>&' }
|
226
|
+
let(:replacement_escaped) { evaluate('Hello {{ name }}', {name: name}) }
|
227
|
+
let(:helper_replacement_escaped) {
|
228
|
+
hbs.register_helper('wrap_parens') {|context, value| "(#{value})"}
|
229
|
+
evaluate('Hello {{wrap_parens name}}', {name: name})
|
230
|
+
}
|
231
|
+
|
232
|
+
before do
|
233
|
+
hbs.set_escaper(escaper)
|
234
|
+
end
|
193
235
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
"{{/each}}</ul>"
|
199
|
-
].join("\n")
|
200
|
-
|
201
|
-
expect(evaluate(template, {})).to eq([
|
202
|
-
"<ul>",
|
203
|
-
"</ul>"
|
204
|
-
].join("\n"))
|
205
|
-
end
|
236
|
+
context 'default escaper' do
|
237
|
+
it 'escapes HTML characters in simple replacements' do
|
238
|
+
expect(replacement_escaped).to eq('Hello <"'>&')
|
239
|
+
end
|
206
240
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
"{{else}} <li>No stuff found....</li>",
|
212
|
-
"{{/each}}</ul>"
|
213
|
-
].join("\n")
|
214
|
-
|
215
|
-
expect(evaluate(template, {})).to eq([
|
216
|
-
"<ul>",
|
217
|
-
" <li>No stuff found....</li>",
|
218
|
-
"</ul>"
|
219
|
-
].join("\n"))
|
220
|
-
end
|
241
|
+
it 'escapes HTML characters in helpers' do
|
242
|
+
expect(helper_replacement_escaped).to eq('Hello (<"'>&)')
|
243
|
+
end
|
244
|
+
end
|
221
245
|
|
222
|
-
|
223
|
-
|
224
|
-
"<ul>",
|
225
|
-
"{{#each items}} <li>{{this.name}}</li>",
|
226
|
-
"{{/each}}</ul>"
|
227
|
-
].join("\n")
|
228
|
-
|
229
|
-
data = double(items: ducks)
|
230
|
-
expect(evaluate(template, data)).to eq([
|
231
|
-
"<ul>",
|
232
|
-
" <li>Huey</li>",
|
233
|
-
" <li>Dewey</li>",
|
234
|
-
" <li>Louis</li>",
|
235
|
-
"</ul>"
|
236
|
-
].join("\n"))
|
237
|
-
|
238
|
-
data = {items: []}
|
239
|
-
expect(evaluate(template, data)).to eq([
|
240
|
-
"<ul>",
|
241
|
-
"</ul>"
|
242
|
-
].join("\n"))
|
243
|
-
end
|
246
|
+
context 'DummyEscaper' do
|
247
|
+
let(:escaper) { Handlebars::Escapers::DummyEscaper }
|
244
248
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
data = {items: ducks}
|
254
|
-
expect(evaluate(template, data)).to eq([
|
255
|
-
"<ul>",
|
256
|
-
" <li>Huey</li>",
|
257
|
-
" <li>Dewey</li>",
|
258
|
-
" <li>Louis</li>",
|
259
|
-
"</ul>"
|
260
|
-
].join("\n"))
|
261
|
-
|
262
|
-
data = {items: []}
|
263
|
-
expect(evaluate(template, data)).to eq([
|
264
|
-
"<ul>",
|
265
|
-
" <li>No ducks to display</li>",
|
266
|
-
"</ul>"
|
267
|
-
].join("\n"))
|
268
|
-
end
|
249
|
+
it 'escapes nothing' do
|
250
|
+
expect(replacement_escaped).to eq('Hello <"\'>&')
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'escapes nothing in helpers' do
|
254
|
+
expect(helper_replacement_escaped).to eq('Hello (<"\'>&)')
|
255
|
+
end
|
256
|
+
end
|
269
257
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
email: 'huey@junior-woodchucks.example.com',
|
275
|
-
phones: ['1234', '5678'],
|
276
|
-
},
|
277
|
-
{
|
278
|
-
name: 'Dewey',
|
279
|
-
email: 'dewey@junior-woodchucks.example.com',
|
280
|
-
phones: ['4321'],
|
281
|
-
}
|
282
|
-
]}
|
283
|
-
|
284
|
-
template = [
|
285
|
-
"People:",
|
286
|
-
"<ul>",
|
287
|
-
" {{#each people}}",
|
288
|
-
" <li>",
|
289
|
-
" <ul>",
|
290
|
-
" <li>Name: {{this.name}}</li>",
|
291
|
-
" <li>Phones: {{#each this.phones}} {{this}} {{/each}}</li>",
|
292
|
-
" <li>email: {{this.email}}</li>",
|
293
|
-
" </ul>",
|
294
|
-
" </li>",
|
295
|
-
" {{else}}",
|
296
|
-
" <li>No one to display</li>",
|
297
|
-
" {{/each}}",
|
298
|
-
"</ul>"
|
299
|
-
].join("\n")
|
300
|
-
|
301
|
-
expect(evaluate(template, data)).to eq([
|
302
|
-
"People:",
|
303
|
-
"<ul>",
|
304
|
-
" ",
|
305
|
-
" <li>",
|
306
|
-
" <ul>",
|
307
|
-
" <li>Name: Huey</li>",
|
308
|
-
" <li>Phones: 1234 5678 </li>",
|
309
|
-
" <li>email: huey@junior-woodchucks.example.com</li>",
|
310
|
-
" </ul>",
|
311
|
-
" </li>",
|
312
|
-
" ",
|
313
|
-
" <li>",
|
314
|
-
" <ul>",
|
315
|
-
" <li>Name: Dewey</li>",
|
316
|
-
" <li>Phones: 4321 </li>",
|
317
|
-
" <li>email: dewey@junior-woodchucks.example.com</li>",
|
318
|
-
" </ul>",
|
319
|
-
" </li>",
|
320
|
-
" ",
|
321
|
-
"</ul>"
|
322
|
-
].join("\n"))
|
258
|
+
context 'custom escaper' do
|
259
|
+
class VowelEscaper
|
260
|
+
def self.escape(value)
|
261
|
+
value.gsub(/([aeiuo])/, '-\1')
|
323
262
|
end
|
324
263
|
end
|
264
|
+
|
265
|
+
let(:escaper) { VowelEscaper }
|
266
|
+
let(:name) { 'Her Serene Highness' }
|
267
|
+
|
268
|
+
it 'applies the escaping' do
|
269
|
+
expect(replacement_escaped).to eq('Hello H-er S-er-en-e H-ighn-ess')
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'applies the escaping in helpers' do
|
273
|
+
expect(helper_replacement_escaped).to eq('Hello (H-er S-er-en-e H-ighn-ess)')
|
274
|
+
end
|
325
275
|
end
|
326
276
|
end
|
327
277
|
end
|