curlybars 1.1.4 → 1.4.0
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/lib/curlybars.rb +19 -6
- data/lib/curlybars/method_whitelist.rb +47 -17
- data/lib/curlybars/node/if_else.rb +1 -1
- data/lib/curlybars/node/item.rb +3 -3
- data/lib/curlybars/node/path.rb +2 -2
- data/lib/curlybars/node/unless_else.rb +1 -1
- data/lib/curlybars/rendering_support.rb +1 -1
- data/lib/curlybars/version.rb +1 -1
- data/lib/curlybars/visitor.rb +100 -0
- data/spec/curlybars/method_whitelist_spec.rb +134 -7
- data/spec/integration/node/helper_spec.rb +12 -0
- data/spec/integration/node/if_else_spec.rb +31 -0
- data/spec/integration/node/if_spec.rb +2 -2
- data/spec/integration/node/path_spec.rb +14 -2
- data/spec/integration/node/unless_else_spec.rb +2 -2
- data/spec/integration/visitor_spec.rb +146 -0
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92b6cb3082b3fc52f509b72511b93937529f57170d4f8751f989e17727418889
|
4
|
+
data.tar.gz: cbe9843e0852ccdb2bd9aabcb4c77e4856de40e60bbc01f6dc10d17e3987754a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a3cb40974bd6568a73b5640452403da9b40e63da6bc152fadd4ea375961cb8c5de21d561f094f5b181359c791ddf4237a0a58022ae2bd3b38d012748ae9eb6b
|
7
|
+
data.tar.gz: 4bd84f5a24ee7fcb3f59fac17844d65638f496f22c263fc1e94c47aac65c84e848718f1695a20421ade814c9e7e98f17b0e10d58b14920fccb33d92987606e6b
|
data/lib/curlybars.rb
CHANGED
@@ -28,12 +28,7 @@ module Curlybars
|
|
28
28
|
cache_key = ["Curlybars.compile", identifier, Digest::SHA256.hexdigest(source)]
|
29
29
|
|
30
30
|
cache.fetch(cache_key) do
|
31
|
-
|
32
|
-
transformed_source = transformers.inject(source) do |memo, transformer|
|
33
|
-
transformer.transform(memo, identifier)
|
34
|
-
end
|
35
|
-
|
36
|
-
ast(transformed_source, identifier, run_processors: true).compile
|
31
|
+
ast(transformed_source(source), identifier, run_processors: true).compile
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
@@ -72,6 +67,16 @@ module Curlybars
|
|
72
67
|
errors.empty?
|
73
68
|
end
|
74
69
|
|
70
|
+
# Visit nodes in the AST.
|
71
|
+
#
|
72
|
+
# visitor - An instance of a subclass of `Curlybars::Visitor`.
|
73
|
+
# source - The source HBS String used to generate an AST.
|
74
|
+
# identifier - The the file name of the template being checked (defaults to `nil`).
|
75
|
+
def visit(visitor, source, identifier = nil)
|
76
|
+
tree = ast(transformed_source(source), identifier, run_processors: true)
|
77
|
+
visitor.accept(tree)
|
78
|
+
end
|
79
|
+
|
75
80
|
def cache
|
76
81
|
@cache ||= ActiveSupport::Cache::MemoryStore.new
|
77
82
|
end
|
@@ -80,6 +85,13 @@ module Curlybars
|
|
80
85
|
|
81
86
|
private
|
82
87
|
|
88
|
+
def transformed_source(source)
|
89
|
+
transformers = Curlybars.configuration.compiler_transformers
|
90
|
+
transformers.inject(source) do |memo, transformer|
|
91
|
+
transformer.transform(memo, identifier)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
83
95
|
def ast(source, identifier, run_processors:)
|
84
96
|
tokens = Curlybars::Lexer.lex(source, identifier)
|
85
97
|
|
@@ -118,3 +130,4 @@ require 'curlybars/template_handler'
|
|
118
130
|
require 'curlybars/railtie' if defined?(Rails)
|
119
131
|
require 'curlybars/presenter'
|
120
132
|
require 'curlybars/method_whitelist'
|
133
|
+
require 'curlybars/visitor'
|
@@ -1,27 +1,57 @@
|
|
1
1
|
module Curlybars
|
2
2
|
module MethodWhitelist
|
3
|
-
def allow_methods(*
|
4
|
-
|
5
|
-
|
6
|
-
if type.
|
7
|
-
|
3
|
+
def allow_methods(*methods_without_type, **methods_with_type, &contextual_block)
|
4
|
+
methods_with_type_validator = lambda do |methods_to_validate|
|
5
|
+
methods_to_validate.each do |(method_name, type)|
|
6
|
+
if type.is_a?(Array)
|
7
|
+
if type.size != 1 || !type.first.respond_to?(:dependency_tree)
|
8
|
+
raise "Invalid allowed method syntax for `#{method_name}`. Collections must be of one presenter class"
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
14
|
+
methods_with_type_validator.call(methods_with_type)
|
15
|
+
|
12
16
|
define_method(:allowed_methods) do
|
13
|
-
|
14
|
-
|
17
|
+
@method_whitelist_allowed_methods ||= begin
|
18
|
+
methods_list = methods_without_type + methods_with_type.keys
|
19
|
+
|
20
|
+
# Adds methods to the list of allowed methods
|
21
|
+
method_adder = lambda do |*more_methods, **more_methods_with_type|
|
22
|
+
methods_with_type_validator.call(more_methods_with_type)
|
23
|
+
|
24
|
+
methods_list += more_methods
|
25
|
+
methods_list += more_methods_with_type.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
contextual_block&.call(self, method_adder)
|
29
|
+
|
30
|
+
defined?(super) ? super() + methods_list : methods_list
|
31
|
+
end
|
15
32
|
end
|
16
33
|
|
17
|
-
define_singleton_method(:methods_schema) do
|
18
|
-
|
34
|
+
define_singleton_method(:methods_schema) do |context = nil|
|
35
|
+
all_methods_without_type = methods_without_type
|
36
|
+
all_methods_with_type = methods_with_type
|
37
|
+
|
38
|
+
# Adds methods to the schema
|
39
|
+
schema_adder = lambda do |*more_methods_without_type, **more_methods_with_type|
|
40
|
+
methods_with_type_validator.call(more_methods_with_type)
|
41
|
+
|
42
|
+
all_methods_without_type += more_methods_without_type
|
43
|
+
all_methods_with_type = all_methods_with_type.merge(more_methods_with_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
contextual_block&.call(context, schema_adder)
|
47
|
+
|
48
|
+
schema = all_methods_without_type.each_with_object({}) do |method, memo|
|
19
49
|
memo[method] = nil
|
20
50
|
end
|
21
51
|
|
22
|
-
methods_with_type_resolved =
|
52
|
+
methods_with_type_resolved = all_methods_with_type.each_with_object({}) do |(method_name, type), memo|
|
23
53
|
memo[method_name] = if type.respond_to?(:call)
|
24
|
-
type.call(
|
54
|
+
type.call(context)
|
25
55
|
else
|
26
56
|
type
|
27
57
|
end
|
@@ -30,26 +60,26 @@ module Curlybars
|
|
30
60
|
schema.merge!(methods_with_type_resolved)
|
31
61
|
|
32
62
|
# Inheritance
|
33
|
-
schema.merge!(super(
|
63
|
+
schema.merge!(super(context)) if defined?(super)
|
34
64
|
|
35
65
|
# Included modules
|
36
66
|
included_modules.each do |mod|
|
37
67
|
next unless mod.respond_to?(:methods_schema)
|
38
|
-
schema.merge!(mod.methods_schema(
|
68
|
+
schema.merge!(mod.methods_schema(context))
|
39
69
|
end
|
40
70
|
|
41
71
|
schema
|
42
72
|
end
|
43
73
|
|
44
|
-
define_singleton_method(:dependency_tree) do
|
45
|
-
methods_schema(
|
74
|
+
define_singleton_method(:dependency_tree) do |context = nil|
|
75
|
+
methods_schema(context).each_with_object({}) do |method_with_type, memo|
|
46
76
|
method_name = method_with_type.first
|
47
77
|
type = method_with_type.last
|
48
78
|
|
49
79
|
memo[method_name] = if type.respond_to?(:dependency_tree)
|
50
|
-
type.dependency_tree(
|
80
|
+
type.dependency_tree(context)
|
51
81
|
elsif type.is_a?(Array)
|
52
|
-
[type.first.dependency_tree(
|
82
|
+
[type.first.dependency_tree(context)]
|
53
83
|
else
|
54
84
|
type
|
55
85
|
end
|
data/lib/curlybars/node/item.rb
CHANGED
data/lib/curlybars/node/path.rb
CHANGED
@@ -60,10 +60,10 @@ module Curlybars
|
|
60
60
|
path_split_by_slashes = path.split('/')
|
61
61
|
backward_steps_on_branches = path_split_by_slashes.count - 1
|
62
62
|
base_tree_position = branches.length - backward_steps_on_branches
|
63
|
+
base_tree_index = base_tree_position - 1
|
63
64
|
|
64
|
-
|
65
|
+
raise Curlybars::Error::Validate.new('unallowed_path', "'#{path}' goes out of scope", position) if base_tree_index < 0
|
65
66
|
|
66
|
-
base_tree_index = base_tree_position - 1
|
67
67
|
base_tree = branches[base_tree_index]
|
68
68
|
|
69
69
|
dotted_path_side = path_split_by_slashes.last
|
@@ -101,7 +101,7 @@ module Curlybars
|
|
101
101
|
|
102
102
|
def cached_call(meth)
|
103
103
|
return cached_calls[meth] if cached_calls.key? meth
|
104
|
-
instrument(meth) { cached_calls[meth] = meth.call }
|
104
|
+
instrument(meth) { cached_calls[meth] = meth.call(*arguments_for_signature(meth, [], {})) }
|
105
105
|
end
|
106
106
|
|
107
107
|
def call(helper, helper_path, helper_position, arguments, options, &block)
|
data/lib/curlybars/version.rb
CHANGED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
module Curlybars
|
4
|
+
class Visitor
|
5
|
+
attr_accessor :context
|
6
|
+
|
7
|
+
def initialize(context)
|
8
|
+
@context = context
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept(node)
|
12
|
+
visit(node)
|
13
|
+
context
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def visit(node)
|
19
|
+
class_name = node.class.name.to_s
|
20
|
+
return unless class_name.start_with?('Curlybars::Node')
|
21
|
+
|
22
|
+
method_name = class_name.demodulize.underscore
|
23
|
+
send("visit_#{method_name}", node)
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_block_helper_else(node)
|
27
|
+
node.arguments.each { |arg| visit(arg) }
|
28
|
+
node.options.each { |opt| visit(opt) }
|
29
|
+
visit(node.helper)
|
30
|
+
visit(node.helper_template)
|
31
|
+
visit(node.else_template)
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_boolean(_node)
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_each_else(node)
|
38
|
+
visit(node.path)
|
39
|
+
visit(node.each_template)
|
40
|
+
visit(node.else_template)
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_if_else(node)
|
44
|
+
visit(node.expression)
|
45
|
+
visit(node.if_template)
|
46
|
+
visit(node.else_template)
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_item(node)
|
50
|
+
visit(node.item)
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_literal(_node)
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_option(node)
|
57
|
+
visit(node.expression)
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_output(node)
|
61
|
+
visit(node.value)
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_partial(node)
|
65
|
+
visit(node.path)
|
66
|
+
end
|
67
|
+
|
68
|
+
def visit_path(_node)
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit_root(node)
|
72
|
+
visit(node.template)
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_string(_node)
|
76
|
+
end
|
77
|
+
|
78
|
+
def visit_template(node)
|
79
|
+
node.items.each { |item| visit(item) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_text(_node)
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_unless_else(node)
|
86
|
+
visit(node.expression)
|
87
|
+
visit(node.unless_template)
|
88
|
+
visit(node.else_template)
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit_variable(_node)
|
92
|
+
end
|
93
|
+
|
94
|
+
def visit_with_else(node)
|
95
|
+
visit(node.path)
|
96
|
+
visit(node.with_template)
|
97
|
+
visit(node.else_template)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,5 +1,32 @@
|
|
1
1
|
describe Curlybars::MethodWhitelist do
|
2
|
-
let(:dummy_class)
|
2
|
+
let(:dummy_class) do
|
3
|
+
Class.new do
|
4
|
+
extend Curlybars::MethodWhitelist
|
5
|
+
|
6
|
+
# A method available in the context
|
7
|
+
def foo?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def qux?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:validation_context_class) do
|
18
|
+
Class.new do
|
19
|
+
attr_accessor :invocation_count
|
20
|
+
|
21
|
+
def foo?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def qux?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
3
30
|
|
4
31
|
describe "#allowed_methods" do
|
5
32
|
it "returns an empty array as default" do
|
@@ -21,6 +48,25 @@ describe Curlybars::MethodWhitelist do
|
|
21
48
|
expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article])
|
22
49
|
end
|
23
50
|
|
51
|
+
it "supports adding more methods for validation" do
|
52
|
+
dummy_class.class_eval do
|
53
|
+
allow_methods do |context, allow_method|
|
54
|
+
if context.foo?
|
55
|
+
allow_method.call(:bar)
|
56
|
+
end
|
57
|
+
|
58
|
+
if context.qux?
|
59
|
+
allow_method.call(:quux)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
aggregate_failures "test both allowed_methods and allows_method?" do
|
65
|
+
expect(dummy_class.new.allowed_methods).to eq([:bar])
|
66
|
+
expect(dummy_class.new.allows_method?(:bar)).to eq(true)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
24
70
|
it "raises when collection is not of presenters" do
|
25
71
|
expect do
|
26
72
|
dummy_class.class_eval { allow_methods :cook, links: ["foobar"] }
|
@@ -79,6 +125,81 @@ describe Curlybars::MethodWhitelist do
|
|
79
125
|
wave: nil
|
80
126
|
)
|
81
127
|
end
|
128
|
+
|
129
|
+
context "with context dependent methods" do
|
130
|
+
let(:base_presenter) do
|
131
|
+
stub_const("LinkPresenter", Class.new)
|
132
|
+
|
133
|
+
Class.new do
|
134
|
+
extend Curlybars::MethodWhitelist
|
135
|
+
attr_accessor :invocation_count
|
136
|
+
|
137
|
+
allow_methods :cook, link: LinkPresenter do |context, allow_method|
|
138
|
+
if context.foo?
|
139
|
+
allow_method.call(:bar)
|
140
|
+
end
|
141
|
+
|
142
|
+
context.invocation_count ||= 0
|
143
|
+
context.invocation_count += 1
|
144
|
+
end
|
145
|
+
|
146
|
+
def foo?
|
147
|
+
true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
let(:helpers) do
|
153
|
+
Module.new do
|
154
|
+
extend Curlybars::MethodWhitelist
|
155
|
+
allow_methods :form do |context, allow_method|
|
156
|
+
if context.foo?
|
157
|
+
allow_method.call(foo_bar: :helper)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def foo?
|
162
|
+
true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
let(:post_presenter) do
|
168
|
+
Class.new(base_presenter) do
|
169
|
+
extend Curlybars::MethodWhitelist
|
170
|
+
include Helpers
|
171
|
+
allow_methods :wave
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
before do
|
176
|
+
stub_const("Helpers", helpers)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "allows context methods from inheritance and composition" do
|
180
|
+
expect(post_presenter.new.allowed_methods).to eq([:cook, :link, :bar, :form, :foo_bar, :wave])
|
181
|
+
end
|
182
|
+
|
183
|
+
it "only invokes the context block once" do
|
184
|
+
presenter = post_presenter.new
|
185
|
+
|
186
|
+
10.times { presenter.allowed_methods }
|
187
|
+
|
188
|
+
expect(presenter.invocation_count).to eq(1)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "returns a dependency_tree with inheritance and composition with context" do
|
192
|
+
expect(post_presenter.dependency_tree(validation_context_class.new)).
|
193
|
+
to eq(
|
194
|
+
cook: nil,
|
195
|
+
link: LinkPresenter,
|
196
|
+
form: nil,
|
197
|
+
wave: nil,
|
198
|
+
bar: nil,
|
199
|
+
foo_bar: :helper
|
200
|
+
)
|
201
|
+
end
|
202
|
+
end
|
82
203
|
end
|
83
204
|
|
84
205
|
describe ".methods_schema" do
|
@@ -110,16 +231,22 @@ describe Curlybars::MethodWhitelist do
|
|
110
231
|
expect(dummy_class.methods_schema).to eq(links: [LinkPresenter])
|
111
232
|
end
|
112
233
|
|
113
|
-
it "supports procs in schema" do
|
114
|
-
dummy_class.class_eval { allow_methods settings: -> {
|
234
|
+
it "supports procs with context in schema" do
|
235
|
+
dummy_class.class_eval { allow_methods settings: ->(context) { context.foo? ? Hash[:background_color, nil] : nil } }
|
115
236
|
|
116
|
-
expect(dummy_class.methods_schema).to eq(settings: {
|
237
|
+
expect(dummy_class.methods_schema(validation_context_class.new)).to eq(settings: { background_color: nil })
|
117
238
|
end
|
118
239
|
|
119
|
-
it "supports
|
120
|
-
dummy_class.class_eval
|
240
|
+
it "supports context methods" do
|
241
|
+
dummy_class.class_eval do
|
242
|
+
allow_methods do |context, allow_method|
|
243
|
+
if context.foo?
|
244
|
+
allow_method.call(:bar)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
121
248
|
|
122
|
-
expect(dummy_class.methods_schema(
|
249
|
+
expect(dummy_class.methods_schema(validation_context_class.new)).to eq(bar: nil)
|
123
250
|
end
|
124
251
|
end
|
125
252
|
|
@@ -15,6 +15,18 @@ describe "{{helper context key=value}}" do
|
|
15
15
|
HTML
|
16
16
|
end
|
17
17
|
|
18
|
+
it "calls a helper without arguments in an if statement" do
|
19
|
+
template = Curlybars.compile(<<-HBS)
|
20
|
+
{{#if print_args_and_options}}
|
21
|
+
{{print_args_and_options 'first' 'second'}}
|
22
|
+
{{/if}}
|
23
|
+
HBS
|
24
|
+
|
25
|
+
expect(eval(template)).to resemble(<<-HTML)
|
26
|
+
first, second, key=
|
27
|
+
HTML
|
28
|
+
end
|
29
|
+
|
18
30
|
it "passes two arguments and options" do
|
19
31
|
template = Curlybars.compile(<<-HBS)
|
20
32
|
{{print_args_and_options 'first' 'second' key='value'}}
|
@@ -137,5 +137,36 @@ describe "{{#if}}...{{else}}...{{/if}}" do
|
|
137
137
|
|
138
138
|
expect(errors).not_to be_empty
|
139
139
|
end
|
140
|
+
|
141
|
+
it "validates errors the nested else_template when out of context" do
|
142
|
+
dependency_tree = { condition: nil }
|
143
|
+
|
144
|
+
source = <<-HBS
|
145
|
+
{{#if ../condition}}
|
146
|
+
{{else}}
|
147
|
+
{{unallowed_ELSE_method}}
|
148
|
+
{{/if}}
|
149
|
+
HBS
|
150
|
+
|
151
|
+
errors = Curlybars.validate(dependency_tree, source)
|
152
|
+
|
153
|
+
expect(errors.count).to eq(2)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "gives all possible errors found in validation" do
|
157
|
+
dependency_tree = { condition: nil }
|
158
|
+
|
159
|
+
source = <<-HBS
|
160
|
+
{{#if ../condition}}
|
161
|
+
{{unallowed_IF_method}}
|
162
|
+
{{else}}
|
163
|
+
{{unallowed_ELSE_method}}
|
164
|
+
{{/if}}
|
165
|
+
HBS
|
166
|
+
|
167
|
+
errors = Curlybars.validate(dependency_tree, source)
|
168
|
+
|
169
|
+
expect(errors.count).to eq(3)
|
170
|
+
end
|
140
171
|
end
|
141
172
|
end
|
@@ -140,7 +140,7 @@ describe "{{#if}}...{{/if}}" do
|
|
140
140
|
expect(errors).not_to be_empty
|
141
141
|
end
|
142
142
|
|
143
|
-
it "validates
|
143
|
+
it "validates without errors the helper as condition" do
|
144
144
|
dependency_tree = { helper: :helper }
|
145
145
|
|
146
146
|
source = <<-HBS
|
@@ -149,7 +149,7 @@ describe "{{#if}}...{{/if}}" do
|
|
149
149
|
|
150
150
|
errors = Curlybars.validate(dependency_tree, source)
|
151
151
|
|
152
|
-
expect(errors).
|
152
|
+
expect(errors).to be_empty
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|
@@ -139,7 +139,7 @@ describe "{{path}}" do
|
|
139
139
|
expect(errors).to be_empty
|
140
140
|
end
|
141
141
|
|
142
|
-
it "
|
142
|
+
it "gives errors errors when it goes out of context" do
|
143
143
|
dependency_tree = {}
|
144
144
|
|
145
145
|
source = <<-HBS
|
@@ -148,7 +148,7 @@ describe "{{path}}" do
|
|
148
148
|
|
149
149
|
errors = Curlybars.validate(dependency_tree, source)
|
150
150
|
|
151
|
-
expect(errors).
|
151
|
+
expect(errors).not_to be_empty
|
152
152
|
end
|
153
153
|
|
154
154
|
it "without errors using `this`" do
|
@@ -257,6 +257,18 @@ describe "{{path}}" do
|
|
257
257
|
end
|
258
258
|
end
|
259
259
|
|
260
|
+
it "with errors when going outside of scope" do
|
261
|
+
dependency_tree = { ok: { ok: { ok: nil } } }
|
262
|
+
|
263
|
+
source = <<~HBS
|
264
|
+
{{../ok.ok.ok}}
|
265
|
+
HBS
|
266
|
+
|
267
|
+
errors = Curlybars.validate(dependency_tree, source)
|
268
|
+
|
269
|
+
expect(errors.first.message).to eq("'../ok.ok.ok' goes out of scope")
|
270
|
+
end
|
271
|
+
|
260
272
|
describe "raises exact location of unallowed steps" do
|
261
273
|
let(:dependency_tree) { { ok: { allowed: { ok: nil } } } }
|
262
274
|
|
@@ -96,7 +96,7 @@ describe "{{#unless}}...{{else}}...{{/unless}}" do
|
|
96
96
|
expect(errors).not_to be_empty
|
97
97
|
end
|
98
98
|
|
99
|
-
it "validates
|
99
|
+
it "validates without errors when using a helper in the condition" do
|
100
100
|
dependency_tree = { helper: :helper }
|
101
101
|
|
102
102
|
source = <<-HBS
|
@@ -105,7 +105,7 @@ describe "{{#unless}}...{{else}}...{{/unless}}" do
|
|
105
105
|
|
106
106
|
errors = Curlybars.validate(dependency_tree, source)
|
107
107
|
|
108
|
-
expect(errors).
|
108
|
+
expect(errors).to be_empty
|
109
109
|
end
|
110
110
|
|
111
111
|
it "validates with errors the nested unless_template" do
|
@@ -0,0 +1,146 @@
|
|
1
|
+
describe "visitor" do
|
2
|
+
let(:source) do
|
3
|
+
<<-HBS
|
4
|
+
{{#print_args_and_options 'first' 'second' key='value'}}
|
5
|
+
{{/print_args_and_options}}
|
6
|
+
|
7
|
+
{{#render_inverse}}
|
8
|
+
fn
|
9
|
+
{{else}}
|
10
|
+
inverse
|
11
|
+
{{@variable}}
|
12
|
+
{{/render_inverse}}
|
13
|
+
|
14
|
+
{{#each foo}}
|
15
|
+
top
|
16
|
+
{{#each bar}}
|
17
|
+
middle
|
18
|
+
{{#each baz}}
|
19
|
+
inner
|
20
|
+
{{else}}
|
21
|
+
inner inverse
|
22
|
+
{{/each}}
|
23
|
+
{{/each}}
|
24
|
+
{{/each}}
|
25
|
+
|
26
|
+
{{#if valid}}
|
27
|
+
if_template
|
28
|
+
{{#if bar}}
|
29
|
+
foo
|
30
|
+
{{else}}
|
31
|
+
qux
|
32
|
+
{{/if}}
|
33
|
+
{{/if}}
|
34
|
+
|
35
|
+
{{#if baz}}
|
36
|
+
qux
|
37
|
+
{{/if}}
|
38
|
+
|
39
|
+
{{> partial}}
|
40
|
+
|
41
|
+
{{user.avatar.url}}
|
42
|
+
{{#with this}}
|
43
|
+
{{user.avatar.url}}
|
44
|
+
{{/with}}
|
45
|
+
|
46
|
+
{{#unless things}}
|
47
|
+
hi
|
48
|
+
{{/unless}}
|
49
|
+
HBS
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ".visit" do
|
53
|
+
it "visits BlockHelperElse nodes" do
|
54
|
+
visitor = counting_visitor_for(Curlybars::Node::BlockHelperElse)
|
55
|
+
output = Curlybars.visit(visitor, source)
|
56
|
+
expect(output).to eq(4)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "visits EachElse nodes" do
|
60
|
+
visitor = counting_visitor_for(Curlybars::Node::EachElse)
|
61
|
+
output = Curlybars.visit(visitor, source)
|
62
|
+
expect(output).to eq(3)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "visits IfElse nodes" do
|
66
|
+
visitor = counting_visitor_for(Curlybars::Node::IfElse)
|
67
|
+
output = Curlybars.visit(visitor, source)
|
68
|
+
expect(output).to eq(3)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "visits Item nodes" do
|
72
|
+
visitor = counting_visitor_for(Curlybars::Node::Item)
|
73
|
+
output = Curlybars.visit(visitor, source)
|
74
|
+
expect(output).to eq(42)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "visits Literal nodes" do
|
78
|
+
visitor = counting_visitor_for(Curlybars::Node::Literal)
|
79
|
+
output = Curlybars.visit(visitor, source)
|
80
|
+
expect(output).to eq(3)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "visits Option nodes" do
|
84
|
+
visitor = counting_visitor_for(Curlybars::Node::Option)
|
85
|
+
output = Curlybars.visit(visitor, source)
|
86
|
+
expect(output).to eq(1)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "visits Partial nodes" do
|
90
|
+
visitor = counting_visitor_for(Curlybars::Node::Partial)
|
91
|
+
output = Curlybars.visit(visitor, source)
|
92
|
+
expect(output).to eq(1)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "visits Path nodes" do
|
96
|
+
visitor = counting_visitor_for(Curlybars::Node::Path)
|
97
|
+
output = Curlybars.visit(visitor, source)
|
98
|
+
expect(output).to eq(13)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "visits Root nodes" do
|
102
|
+
visitor = counting_visitor_for(Curlybars::Node::Root)
|
103
|
+
output = Curlybars.visit(visitor, source)
|
104
|
+
expect(output).to eq(1)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "visits Template nodes" do
|
108
|
+
visitor = counting_visitor_for(Curlybars::Node::Template)
|
109
|
+
output = Curlybars.visit(visitor, source)
|
110
|
+
expect(output).to eq(14)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "visits Text nodes" do
|
114
|
+
visitor = counting_visitor_for(Curlybars::Node::Text)
|
115
|
+
output = Curlybars.visit(visitor, source)
|
116
|
+
expect(output).to eq(28)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "visits UnlessElse nodes" do
|
120
|
+
visitor = counting_visitor_for(Curlybars::Node::UnlessElse)
|
121
|
+
output = Curlybars.visit(visitor, source)
|
122
|
+
expect(output).to eq(1)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "visits Variable nodes" do
|
126
|
+
visitor = counting_visitor_for(Curlybars::Node::Variable)
|
127
|
+
output = Curlybars.visit(visitor, source)
|
128
|
+
expect(output).to eq(1)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "visits WithElse nodes" do
|
132
|
+
visitor = counting_visitor_for(Curlybars::Node::WithElse)
|
133
|
+
output = Curlybars.visit(visitor, source)
|
134
|
+
expect(output).to eq(1)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def counting_visitor_for(klass)
|
139
|
+
Class.new(Curlybars::Visitor) do
|
140
|
+
define_method "visit_#{klass.name.demodulize.underscore}" do |node|
|
141
|
+
self.context += 1
|
142
|
+
super(node)
|
143
|
+
end
|
144
|
+
end.new(0)
|
145
|
+
end
|
146
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: curlybars
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Libo Cannici
|
@@ -10,10 +10,11 @@ authors:
|
|
10
10
|
- Mauro Codella
|
11
11
|
- Luís Almeida
|
12
12
|
- Andreas Garnæs
|
13
|
+
- Augusto Silva
|
13
14
|
autorequire:
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
|
-
date:
|
17
|
+
date: 2020-09-29 00:00:00.000000000 Z
|
17
18
|
dependencies:
|
18
19
|
- !ruby/object:Gem::Dependency
|
19
20
|
name: actionpack
|
@@ -190,7 +191,7 @@ dependencies:
|
|
190
191
|
description: |-
|
191
192
|
A view layer for your Rails apps that separates structure and logic, using Handlebars templates.
|
192
193
|
Strongly inspired by Curly Template gem by Daniel Schierbeck.
|
193
|
-
email:
|
194
|
+
email: vikings@zendesk.com
|
194
195
|
executables: []
|
195
196
|
extensions: []
|
196
197
|
extra_rdoc_files: []
|
@@ -234,6 +235,7 @@ files:
|
|
234
235
|
- lib/curlybars/safe_buffer.rb
|
235
236
|
- lib/curlybars/template_handler.rb
|
236
237
|
- lib/curlybars/version.rb
|
238
|
+
- lib/curlybars/visitor.rb
|
237
239
|
- spec/acceptance/application_layout_spec.rb
|
238
240
|
- spec/acceptance/collection_blocks_spec.rb
|
239
241
|
- spec/acceptance/global_helper_spec.rb
|
@@ -270,6 +272,7 @@ files:
|
|
270
272
|
- spec/integration/node/with_spec.rb
|
271
273
|
- spec/integration/processor/tilde_spec.rb
|
272
274
|
- spec/integration/processors_spec.rb
|
275
|
+
- spec/integration/visitor_spec.rb
|
273
276
|
homepage: https://github.com/zendesk/curlybars
|
274
277
|
licenses:
|
275
278
|
- Apache-2.0
|
@@ -290,8 +293,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
290
293
|
- !ruby/object:Gem::Version
|
291
294
|
version: '0'
|
292
295
|
requirements: []
|
293
|
-
|
294
|
-
rubygems_version: 2.7.3
|
296
|
+
rubygems_version: 3.0.3
|
295
297
|
signing_key:
|
296
298
|
specification_version: 4
|
297
299
|
summary: Create your views using Handlebars templates!
|
@@ -312,6 +314,7 @@ test_files:
|
|
312
314
|
- spec/integration/processor/tilde_spec.rb
|
313
315
|
- spec/integration/exception_spec.rb
|
314
316
|
- spec/integration/processors_spec.rb
|
317
|
+
- spec/integration/visitor_spec.rb
|
315
318
|
- spec/integration/node/escape_spec.rb
|
316
319
|
- spec/integration/node/unless_else_spec.rb
|
317
320
|
- spec/integration/node/output_spec.rb
|