curlybars 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/curlybars.rb +8 -0
- data/lib/curlybars/generic.rb +36 -0
- data/lib/curlybars/method_whitelist.rb +22 -3
- data/lib/curlybars/node/each_else.rb +6 -2
- data/lib/curlybars/node/path.rb +52 -11
- data/lib/curlybars/node/sub_expression.rb +46 -0
- data/lib/curlybars/node/with_else.rb +6 -2
- data/lib/curlybars/parser.rb +28 -0
- data/lib/curlybars/version.rb +1 -1
- data/spec/curlybars/method_whitelist_spec.rb +8 -4
- data/spec/integration/node/each_else_spec.rb +208 -2
- data/spec/integration/node/with_spec.rb +66 -2
- data/spec/integration/processor/tilde_spec.rb +1 -1
- data/spec/integration/processors_spec.rb +4 -4
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba4b2799fad2886edfd0bc1c4873d8da9ed80d8885a1868f4b0fa5797fb9e49c
|
4
|
+
data.tar.gz: ee1bfc5d500f2ceee63675c5188d915227f507de7895c6ed2f08e89ddcae122e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4233c4cd5ed833892d17603776a6db2abae2061cf72dfebe1add84bb7464e99b563cacee499fbcd447f59dd54a1ea3bf3b4aa18010526249aba2ed39a2310b96
|
7
|
+
data.tar.gz: f78754a1adc25de66302116338dd2abf9b4e9f8c2ae3439d1f74beda9065bb634c8fae64c19f65767be4d7389f1362559c0537f36bb438969e151bb93a47612f
|
data/lib/curlybars.rb
CHANGED
@@ -77,6 +77,13 @@ module Curlybars
|
|
77
77
|
visitor.accept(tree)
|
78
78
|
end
|
79
79
|
|
80
|
+
def global_helpers_dependency_tree
|
81
|
+
@global_helpers_dependency_tree ||= begin
|
82
|
+
classes = Curlybars.configuration.global_helpers_provider_classes
|
83
|
+
classes.map(&:dependency_tree).inject({}, :merge)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
80
87
|
def cache
|
81
88
|
@cache ||= ActiveSupport::Cache::MemoryStore.new
|
82
89
|
end
|
@@ -117,6 +124,7 @@ require 'curlybars/configuration'
|
|
117
124
|
require 'curlybars/rendering_support'
|
118
125
|
require 'curlybars/parser'
|
119
126
|
require 'curlybars/position'
|
127
|
+
require 'curlybars/generic'
|
120
128
|
require 'curlybars/lexer'
|
121
129
|
require 'curlybars/processor/token_factory'
|
122
130
|
require 'curlybars/processor/tilde'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'curlybars/method_whitelist'
|
2
|
+
|
3
|
+
module Curlybars
|
4
|
+
# A base class that can be used to signify that a helper's return type is a sort a generic.
|
5
|
+
#
|
6
|
+
# Examples
|
7
|
+
#
|
8
|
+
# class GlobalHelperProvider
|
9
|
+
# extend Curlybars::MethodWhitelist
|
10
|
+
#
|
11
|
+
# allow_methods slice: [:helper, [Curlybars::Generic]],
|
12
|
+
# translate: [:helper, Curlybars::Generic]
|
13
|
+
#
|
14
|
+
# def slice(collection, start, length, _)
|
15
|
+
# collection[start, length]
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def translate(object, locale)
|
19
|
+
# object.translate(locale)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# {{#each (slice articles, 0, 5)}}
|
24
|
+
# Title: {{title}}
|
25
|
+
# Body: {{body}}
|
26
|
+
# {{/each}}
|
27
|
+
#
|
28
|
+
# {{#with (translate article "en-us")}}
|
29
|
+
# Title: {{title}}
|
30
|
+
# Body: {{body}}
|
31
|
+
# {{/with}}
|
32
|
+
#
|
33
|
+
class Generic
|
34
|
+
extend Curlybars::MethodWhitelist
|
35
|
+
end
|
36
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module Curlybars
|
2
2
|
module MethodWhitelist
|
3
|
-
# rubocop:disable Style/SoleNestedConditional
|
4
3
|
def allow_methods(*methods_without_type, **methods_with_type, &contextual_block)
|
5
4
|
methods_with_type_validator = lambda do |methods_to_validate|
|
6
5
|
methods_to_validate.each do |(method_name, type)|
|
7
6
|
if type.is_a?(Array)
|
7
|
+
next if generic_or_collection_helper?(type)
|
8
|
+
|
8
9
|
if type.size != 1 || !type.first.respond_to?(:dependency_tree)
|
9
10
|
raise "Invalid allowed method syntax for `#{method_name}`. Collections must be of one presenter class"
|
10
11
|
end
|
@@ -81,7 +82,15 @@ module Curlybars
|
|
81
82
|
memo[method_name] = if type.respond_to?(:dependency_tree)
|
82
83
|
type.dependency_tree(context)
|
83
84
|
elsif type.is_a?(Array)
|
84
|
-
|
85
|
+
if type.first == :helper
|
86
|
+
if type.last.is_a?(Array)
|
87
|
+
[:helper, [type.last.first.dependency_tree(context)]]
|
88
|
+
else
|
89
|
+
[:helper, type.last.dependency_tree(context)]
|
90
|
+
end
|
91
|
+
else
|
92
|
+
[type.first.dependency_tree(context)]
|
93
|
+
end
|
85
94
|
else
|
86
95
|
type
|
87
96
|
end
|
@@ -92,11 +101,21 @@ module Curlybars
|
|
92
101
|
allowed_methods.include?(method)
|
93
102
|
end
|
94
103
|
end
|
95
|
-
# rubocop:enable Style/SoleNestedConditional
|
96
104
|
|
97
105
|
def self.extended(base)
|
98
106
|
# define a default of no method allowed
|
99
107
|
base.allow_methods
|
100
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def generic_or_collection_helper?(type)
|
113
|
+
return false unless type.size == 2
|
114
|
+
return false unless type.first == :helper
|
115
|
+
return true if type.last.respond_to?(:dependency_tree)
|
116
|
+
return false unless type.last.is_a?(Array) && type.last.size == 1
|
117
|
+
|
118
|
+
type.last.first.respond_to?(:dependency_tree)
|
119
|
+
end
|
101
120
|
end
|
102
121
|
end
|
@@ -11,7 +11,7 @@ module Curlybars
|
|
11
11
|
position = rendering.position(#{position.line_number}, #{position.line_offset})
|
12
12
|
template_cache_key = '#{each_template.cache_key}'
|
13
13
|
|
14
|
-
collection = rendering.coerce_to_hash!(collection, #{
|
14
|
+
collection = rendering.coerce_to_hash!(collection, #{collection_path.path.inspect}, position)
|
15
15
|
collection.each.with_index.map do |key_and_presenter, index|
|
16
16
|
rendering.check_timeout!
|
17
17
|
rendering.optional_presenter_cache(key_and_presenter[1], template_cache_key, buffer) do |buffer|
|
@@ -37,7 +37,7 @@ module Curlybars
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def validate(branches)
|
40
|
-
resolved = path.resolve_and_check!(branches, check_type: :
|
40
|
+
resolved = path.resolve_and_check!(branches, check_type: :collectionlike)
|
41
41
|
sub_tree = resolved.first
|
42
42
|
|
43
43
|
each_template_errors = begin
|
@@ -57,6 +57,10 @@ module Curlybars
|
|
57
57
|
path_error
|
58
58
|
end
|
59
59
|
|
60
|
+
def collection_path
|
61
|
+
path.subexpression? ? path.helper : path
|
62
|
+
end
|
63
|
+
|
60
64
|
def cache_key
|
61
65
|
[
|
62
66
|
path,
|
data/lib/curlybars/node/path.rb
CHANGED
@@ -35,11 +35,18 @@ module Curlybars
|
|
35
35
|
resolve(branches).is_a?(Hash)
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
value
|
38
|
+
def presenter_value?(value)
|
39
|
+
value.is_a?(Hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
def collection_value?(value)
|
40
43
|
value.is_a?(Array) && value.first.is_a?(Hash)
|
41
44
|
end
|
42
45
|
|
46
|
+
def presenter_collection?(branches)
|
47
|
+
collection_value?(resolve(branches))
|
48
|
+
end
|
49
|
+
|
43
50
|
def leaf?(branches)
|
44
51
|
value = resolve(branches)
|
45
52
|
value.nil?
|
@@ -53,9 +60,41 @@ module Curlybars
|
|
53
60
|
resolve(branches) == :helper
|
54
61
|
end
|
55
62
|
|
63
|
+
def generic_helper?(branches)
|
64
|
+
value = resolve(branches)
|
65
|
+
value.is_a?(Array) &&
|
66
|
+
value.first == :helper &&
|
67
|
+
presenter_value?(value.last)
|
68
|
+
end
|
69
|
+
|
70
|
+
def generic_collection_helper?(branches)
|
71
|
+
value = resolve(branches)
|
72
|
+
value.is_a?(Array) &&
|
73
|
+
value.first == :helper &&
|
74
|
+
collection_value?(value.last)
|
75
|
+
end
|
76
|
+
|
77
|
+
def collectionlike?(branches)
|
78
|
+
presenter_collection?(branches) || generic_collection_helper?(branches)
|
79
|
+
end
|
80
|
+
|
81
|
+
def presenterlike?(branches)
|
82
|
+
presenter?(branches) || generic_helper?(branches)
|
83
|
+
end
|
84
|
+
|
85
|
+
def subexpression?
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
56
89
|
def resolve(branches)
|
57
90
|
@value ||= begin
|
58
|
-
|
91
|
+
if Curlybars.global_helpers_dependency_tree.key?(path.to_sym)
|
92
|
+
dep_node = Curlybars.global_helpers_dependency_tree[path.to_sym]
|
93
|
+
|
94
|
+
return :helper if dep_node.nil?
|
95
|
+
|
96
|
+
return [:helper, dep_node]
|
97
|
+
end
|
59
98
|
|
60
99
|
path_split_by_slashes = path.split('/')
|
61
100
|
backward_steps_on_branches = path_split_by_slashes.count - 1
|
@@ -97,14 +136,6 @@ module Curlybars
|
|
97
136
|
|
98
137
|
private
|
99
138
|
|
100
|
-
# TODO: extract me away
|
101
|
-
def global_helpers_dependency_tree
|
102
|
-
@global_helpers_dependency_tree ||= begin
|
103
|
-
classes = Curlybars.configuration.global_helpers_provider_classes
|
104
|
-
classes.map(&:dependency_tree).inject({}, :merge)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
139
|
def check_type_of(branches, check_type)
|
109
140
|
case check_type
|
110
141
|
when :presenter
|
@@ -112,6 +143,16 @@ module Curlybars
|
|
112
143
|
|
113
144
|
message = "`#{path}` must resolve to a presenter"
|
114
145
|
raise Curlybars::Error::Validate.new('not_a_presenter', message, position)
|
146
|
+
when :presenterlike
|
147
|
+
return if presenterlike?(branches)
|
148
|
+
|
149
|
+
message = "`#{path}` must resolve to a presenter"
|
150
|
+
raise Curlybars::Error::Validate.new('not_presenterlike', message, position)
|
151
|
+
when :collectionlike
|
152
|
+
return if collectionlike?(branches)
|
153
|
+
|
154
|
+
message = "`#{path}` must resolve to a collection of presenters"
|
155
|
+
raise Curlybars::Error::Validate.new('not_collectionlike', message, position)
|
115
156
|
when :presenter_collection
|
116
157
|
return if presenter_collection?(branches)
|
117
158
|
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module Curlybars
|
2
2
|
module Node
|
3
3
|
SubExpression = Struct.new(:helper, :arguments, :options, :position) do
|
4
|
+
def subexpression?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
4
8
|
def compile
|
5
9
|
compiled_arguments = arguments.map do |argument|
|
6
10
|
"arguments.push(rendering.cached_call(#{argument.compile}))"
|
@@ -50,6 +54,48 @@ module Curlybars
|
|
50
54
|
]
|
51
55
|
end
|
52
56
|
|
57
|
+
def resolve_and_check!(branches, check_type: :collectionlike)
|
58
|
+
node = if arguments.first.is_a?(Curlybars::Node::SubExpression)
|
59
|
+
arguments.first
|
60
|
+
else
|
61
|
+
helper
|
62
|
+
end
|
63
|
+
|
64
|
+
type = node.resolve_and_check!(branches, check_type: check_type)
|
65
|
+
|
66
|
+
if helper?(type)
|
67
|
+
if generic_helper?(type)
|
68
|
+
is_collection = type.last.is_a?(Array)
|
69
|
+
return infer_generic_helper_type!(branches, is_collection: is_collection)
|
70
|
+
end
|
71
|
+
|
72
|
+
return type.last
|
73
|
+
end
|
74
|
+
|
75
|
+
type
|
76
|
+
end
|
77
|
+
|
78
|
+
def generic_helper?(type)
|
79
|
+
return false unless type.is_a?(Array)
|
80
|
+
return false unless type.size == 2
|
81
|
+
return false unless type.first == :helper
|
82
|
+
|
83
|
+
type.last == [{}] || type.last == {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def helper?(type)
|
87
|
+
type.first == :helper
|
88
|
+
end
|
89
|
+
|
90
|
+
def infer_generic_helper_type!(branches, is_collection:)
|
91
|
+
if arguments.empty?
|
92
|
+
raise Curlybars::Error::Validate.new('missing_path', "'#{helper.path}' requires a collection as its first argument", helper.position)
|
93
|
+
end
|
94
|
+
|
95
|
+
check_type = is_collection ? :presenter_collection : :presenter
|
96
|
+
arguments.first.resolve_and_check!(branches, check_type: check_type)
|
97
|
+
end
|
98
|
+
|
53
99
|
def cache_key
|
54
100
|
[
|
55
101
|
helper,
|
@@ -9,7 +9,7 @@ module Curlybars
|
|
9
9
|
|
10
10
|
if rendering.to_bool(compiled_path)
|
11
11
|
position = rendering.position(#{position.line_number}, #{position.line_offset})
|
12
|
-
rendering.check_context_is_presenter(compiled_path, #{
|
12
|
+
rendering.check_context_is_presenter(compiled_path, #{presenter_path.path.inspect}, position)
|
13
13
|
|
14
14
|
contexts.push(compiled_path)
|
15
15
|
begin
|
@@ -24,7 +24,7 @@ module Curlybars
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def validate(branches)
|
27
|
-
sub_tree = path.resolve_and_check!(branches, check_type: :
|
27
|
+
sub_tree = path.resolve_and_check!(branches, check_type: :presenterlike)
|
28
28
|
with_template_errors = begin
|
29
29
|
branches.push(sub_tree)
|
30
30
|
with_template.validate(branches)
|
@@ -42,6 +42,10 @@ module Curlybars
|
|
42
42
|
path_error
|
43
43
|
end
|
44
44
|
|
45
|
+
def presenter_path
|
46
|
+
path.subexpression? ? path.helper : path
|
47
|
+
end
|
48
|
+
|
45
49
|
def cache_key
|
46
50
|
[
|
47
51
|
path,
|
data/lib/curlybars/parser.rb
CHANGED
@@ -112,6 +112,12 @@ module Curlybars
|
|
112
112
|
Node::EachElse.new(path, each_template || VOID, VOID, pos(0))
|
113
113
|
end
|
114
114
|
|
115
|
+
clause('START HASH EACH .subexpression END
|
116
|
+
.template?
|
117
|
+
START SLASH EACH END') do |subexpression, each_template|
|
118
|
+
Node::EachElse.new(subexpression, each_template || VOID, VOID, pos(0))
|
119
|
+
end
|
120
|
+
|
115
121
|
clause('START HASH EACH .path END
|
116
122
|
.template?
|
117
123
|
START ELSE END
|
@@ -120,12 +126,26 @@ module Curlybars
|
|
120
126
|
Node::EachElse.new(path, each_template || VOID, else_template || VOID, pos(0))
|
121
127
|
end
|
122
128
|
|
129
|
+
clause('START HASH EACH .subexpression END
|
130
|
+
.template?
|
131
|
+
START ELSE END
|
132
|
+
.template?
|
133
|
+
START SLASH EACH END') do |subexpression, each_template, else_template|
|
134
|
+
Node::EachElse.new(subexpression, each_template || VOID, else_template || VOID, pos(0))
|
135
|
+
end
|
136
|
+
|
123
137
|
clause('START HASH WITH .path END
|
124
138
|
.template?
|
125
139
|
START SLASH WITH END') do |path, with_template|
|
126
140
|
Node::WithElse.new(path, with_template || VOID, VOID, pos(0))
|
127
141
|
end
|
128
142
|
|
143
|
+
clause('START HASH WITH .subexpression END
|
144
|
+
.template?
|
145
|
+
START SLASH WITH END') do |subexpression, with_template|
|
146
|
+
Node::WithElse.new(subexpression, with_template || VOID, VOID, pos(0))
|
147
|
+
end
|
148
|
+
|
129
149
|
clause('START HASH WITH .path END
|
130
150
|
.template?
|
131
151
|
START ELSE END
|
@@ -134,6 +154,14 @@ module Curlybars
|
|
134
154
|
Node::WithElse.new(path, with_template || VOID, else_template || VOID, pos(0))
|
135
155
|
end
|
136
156
|
|
157
|
+
clause('START HASH WITH .subexpression END
|
158
|
+
.template?
|
159
|
+
START ELSE END
|
160
|
+
.template?
|
161
|
+
START SLASH WITH END') do |subexpression, with_template, else_template|
|
162
|
+
Node::WithElse.new(subexpression, with_template || VOID, else_template || VOID, pos(0))
|
163
|
+
end
|
164
|
+
|
137
165
|
clause('START GT .path END') do |path|
|
138
166
|
Node::Partial.new(path)
|
139
167
|
end
|
data/lib/curlybars/version.rb
CHANGED
@@ -36,16 +36,20 @@ describe Curlybars::MethodWhitelist do
|
|
36
36
|
|
37
37
|
describe ".allow_methods" do
|
38
38
|
before do
|
39
|
-
link_presenter = Class.new
|
40
|
-
article_presenter = Class.new
|
39
|
+
link_presenter = Class.new { extend Curlybars::MethodWhitelist }
|
40
|
+
article_presenter = Class.new { extend Curlybars::MethodWhitelist }
|
41
41
|
|
42
42
|
dummy_class.class_eval do
|
43
|
-
allow_methods :cook, link: link_presenter, article: article_presenter
|
43
|
+
allow_methods :cook, link: link_presenter, article: article_presenter,
|
44
|
+
translate_article: [:helper, article_presenter],
|
45
|
+
reverse_articles: [:helper, [article_presenter]],
|
46
|
+
translate: [:helper, Curlybars::Generic],
|
47
|
+
slice: [:helper, [Curlybars::Generic]]
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
47
51
|
it "sets the allowed methods" do
|
48
|
-
expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article])
|
52
|
+
expect(dummy_class.new.allowed_methods).to eq([:cook, :link, :article, :translate_article, :reverse_articles, :translate, :slice])
|
49
53
|
end
|
50
54
|
|
51
55
|
it "supports adding more methods for validation" do
|
@@ -1,9 +1,10 @@
|
|
1
1
|
describe "{{#each collection}}...{{else}}...{{/each}}" do
|
2
|
-
let(:global_helpers_providers) { [] }
|
2
|
+
let(:global_helpers_providers) { [IntegrationTest::GlobalHelperProvider.new] }
|
3
3
|
|
4
4
|
describe "#compile" do
|
5
5
|
let(:post) { double("post") }
|
6
|
-
let(:
|
6
|
+
let(:presenter_class) { IntegrationTest::Presenter }
|
7
|
+
let(:presenter) { presenter_class.new(double("view_context"), post: post) }
|
7
8
|
|
8
9
|
it "uses each_template when collection is not empty" do
|
9
10
|
allow(presenter).to receive(:allows_method?).with(:non_empty_collection).and_return(true)
|
@@ -123,6 +124,41 @@ describe "{{#each collection}}...{{else}}...{{/each}}" do
|
|
123
124
|
HTML
|
124
125
|
end
|
125
126
|
|
127
|
+
it "allows subexpressions" do
|
128
|
+
template = Curlybars.compile(<<-HBS)
|
129
|
+
{{#each (articles)}}left{{else}}right{{/each}}
|
130
|
+
HBS
|
131
|
+
|
132
|
+
expected = "left" * presenter.articles.size
|
133
|
+
expect(eval(template)).to resemble(expected)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "allows subexpressions with collection helpers" do
|
137
|
+
template = Curlybars.compile(<<-HBS)
|
138
|
+
{{#each (reverse_articles)}}
|
139
|
+
{{title}}
|
140
|
+
{{else}}
|
141
|
+
right
|
142
|
+
{{/each}}
|
143
|
+
HBS
|
144
|
+
|
145
|
+
expected = presenter.reverse_articles.inject("") { |res, a| res + a.title }
|
146
|
+
expect(eval(template)).to resemble(expected)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "allows subexpressions with generic collection helpers" do
|
150
|
+
template = Curlybars.compile(<<-HBS)
|
151
|
+
{{#each (refl articles)}}
|
152
|
+
{{title}}
|
153
|
+
{{else}}
|
154
|
+
right
|
155
|
+
{{/each}}
|
156
|
+
HBS
|
157
|
+
|
158
|
+
expected = presenter.articles.inject("") { |res, a| res + a.title }
|
159
|
+
expect(eval(template)).to resemble(expected)
|
160
|
+
end
|
161
|
+
|
126
162
|
it "raises an error if the context is not an array-like object" do
|
127
163
|
allow(IntegrationTest::Presenter).to receive(:allows_method?).with(:not_a_collection).and_return(true)
|
128
164
|
allow(presenter).to receive(:not_a_collection).and_return("string")
|
@@ -198,5 +234,175 @@ describe "{{#each collection}}...{{else}}...{{/each}}" do
|
|
198
234
|
|
199
235
|
expect(errors).not_to be_empty
|
200
236
|
end
|
237
|
+
|
238
|
+
describe "with subexpressions" do
|
239
|
+
before do
|
240
|
+
Curlybars.instance_variable_set(:@global_helpers_dependency_tree, nil)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "without errors when called with a presenter collection" do
|
244
|
+
dependency_tree = { a_presenter_collection: [{}] }
|
245
|
+
|
246
|
+
source = <<-HBS
|
247
|
+
{{#each (a_presenter_collection)}} {{else}} {{/each}}
|
248
|
+
HBS
|
249
|
+
|
250
|
+
errors = Curlybars.validate(dependency_tree, source)
|
251
|
+
|
252
|
+
expect(errors).to be_empty
|
253
|
+
end
|
254
|
+
|
255
|
+
it "without errors when called with a collection helper" do
|
256
|
+
dependency_tree = { a_collection_helper: [:helper, [{ url: nil }]] }
|
257
|
+
|
258
|
+
source = <<-HBS
|
259
|
+
{{#each (a_collection_helper)}}
|
260
|
+
{{url}}
|
261
|
+
{{else}}
|
262
|
+
right
|
263
|
+
{{/each}}
|
264
|
+
HBS
|
265
|
+
|
266
|
+
errors = Curlybars.validate(dependency_tree, source)
|
267
|
+
|
268
|
+
expect(errors).to be_empty
|
269
|
+
end
|
270
|
+
|
271
|
+
context "with a generic collection helper" do
|
272
|
+
it "without errors when arguments are valid" do
|
273
|
+
dependency_tree = {
|
274
|
+
refl: [:helper, [{}]],
|
275
|
+
articles: [{ url: nil }]
|
276
|
+
}
|
277
|
+
|
278
|
+
source = <<-HBS
|
279
|
+
{{#each (refl articles)}}
|
280
|
+
{{url}}
|
281
|
+
{{else}}
|
282
|
+
right
|
283
|
+
{{/each}}
|
284
|
+
HBS
|
285
|
+
|
286
|
+
errors = Curlybars.validate(dependency_tree, source)
|
287
|
+
|
288
|
+
expect(errors).to be_empty
|
289
|
+
end
|
290
|
+
|
291
|
+
it "with errors when the first argument is not a collection" do
|
292
|
+
dependency_tree = {
|
293
|
+
refl: [:helper, [{}]],
|
294
|
+
articles: { url: nil }
|
295
|
+
}
|
296
|
+
|
297
|
+
source = <<-HBS
|
298
|
+
{{#each (refl articles)}}
|
299
|
+
{{url}}
|
300
|
+
{{else}}
|
301
|
+
right
|
302
|
+
{{/each}}
|
303
|
+
HBS
|
304
|
+
|
305
|
+
errors = Curlybars.validate(dependency_tree, source)
|
306
|
+
|
307
|
+
expect(errors).not_to be_empty
|
308
|
+
end
|
309
|
+
|
310
|
+
it "with errors when the first argument is not defined" do
|
311
|
+
dependency_tree = {
|
312
|
+
refl: [:helper, [{}]]
|
313
|
+
}
|
314
|
+
|
315
|
+
source = <<-HBS
|
316
|
+
{{#each (refl articles)}}
|
317
|
+
{{url}}
|
318
|
+
{{else}}
|
319
|
+
right
|
320
|
+
{{/each}}
|
321
|
+
HBS
|
322
|
+
|
323
|
+
errors = Curlybars.validate(dependency_tree, source)
|
324
|
+
|
325
|
+
expect(errors).not_to be_empty
|
326
|
+
end
|
327
|
+
|
328
|
+
it "with errors when no argument is given" do
|
329
|
+
dependency_tree = {
|
330
|
+
refl: [:helper, [{}]]
|
331
|
+
}
|
332
|
+
|
333
|
+
source = <<-HBS
|
334
|
+
{{#each (refl)}}
|
335
|
+
{{url}}
|
336
|
+
{{else}}
|
337
|
+
right
|
338
|
+
{{/each}}
|
339
|
+
HBS
|
340
|
+
|
341
|
+
errors = Curlybars.validate(dependency_tree, source)
|
342
|
+
|
343
|
+
expect(errors).not_to be_empty
|
344
|
+
end
|
345
|
+
|
346
|
+
describe "as a global helper" do
|
347
|
+
let(:global_helpers_provider_classes) { [IntegrationTest::GlobalHelperProvider] }
|
348
|
+
|
349
|
+
before do
|
350
|
+
allow(Curlybars.configuration).to receive(:global_helpers_provider_classes).and_return(global_helpers_provider_classes)
|
351
|
+
end
|
352
|
+
|
353
|
+
it "without errors when the first argument is a collection" do
|
354
|
+
dependency_tree = {
|
355
|
+
articles: [{ url: nil }]
|
356
|
+
}
|
357
|
+
|
358
|
+
source = <<-HBS
|
359
|
+
{{#each (slice articles 0 4)}}
|
360
|
+
{{url}}
|
361
|
+
{{else}}
|
362
|
+
right
|
363
|
+
{{/each}}
|
364
|
+
HBS
|
365
|
+
|
366
|
+
errors = Curlybars.validate(dependency_tree, source)
|
367
|
+
|
368
|
+
expect(errors).to be_empty
|
369
|
+
end
|
370
|
+
|
371
|
+
it "with errors when the first argument is not a collection" do
|
372
|
+
dependency_tree = {
|
373
|
+
articles: { url: nil }
|
374
|
+
}
|
375
|
+
|
376
|
+
source = <<-HBS
|
377
|
+
{{#each (slice articles 0 4)}}
|
378
|
+
{{url}}
|
379
|
+
{{else}}
|
380
|
+
right
|
381
|
+
{{/each}}
|
382
|
+
HBS
|
383
|
+
|
384
|
+
errors = Curlybars.validate(dependency_tree, source)
|
385
|
+
|
386
|
+
expect(errors).not_to be_empty
|
387
|
+
end
|
388
|
+
|
389
|
+
it "with errors when no argument is given" do
|
390
|
+
dependency_tree = {}
|
391
|
+
|
392
|
+
source = <<-HBS
|
393
|
+
{{#each (slice)}}
|
394
|
+
{{url}}
|
395
|
+
{{else}}
|
396
|
+
right
|
397
|
+
{{/each}}
|
398
|
+
HBS
|
399
|
+
|
400
|
+
errors = Curlybars.validate(dependency_tree, source)
|
401
|
+
|
402
|
+
expect(errors).not_to be_empty
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
201
407
|
end
|
202
408
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
describe "{{#with presenter}}...{{/with}}" do
|
2
|
-
let(:global_helpers_providers) { [] }
|
2
|
+
let(:global_helpers_providers) { [IntegrationTest::GlobalHelperProvider.new] }
|
3
3
|
|
4
4
|
describe "#compile" do
|
5
5
|
let(:post) { double("post") }
|
@@ -31,6 +31,18 @@ describe "{{#with presenter}}...{{/with}}" do
|
|
31
31
|
HTML
|
32
32
|
end
|
33
33
|
|
34
|
+
it "allows subexpressions" do
|
35
|
+
template = Curlybars.compile(<<-HBS)
|
36
|
+
{{#with (translate user "sk-SK")}}
|
37
|
+
{{avatar.url}}
|
38
|
+
{{/with}}
|
39
|
+
HBS
|
40
|
+
|
41
|
+
expect(eval(template)).to resemble(<<-HTML)
|
42
|
+
http://example.com/foo.png?locale=sk-SK
|
43
|
+
HTML
|
44
|
+
end
|
45
|
+
|
34
46
|
it "allows empty with_template" do
|
35
47
|
template = Curlybars.compile(<<-HBS)
|
36
48
|
{{#with user}}{{/with}}
|
@@ -39,7 +51,7 @@ describe "{{#with presenter}}...{{/with}}" do
|
|
39
51
|
expect(eval(template)).to resemble("")
|
40
52
|
end
|
41
53
|
|
42
|
-
it "renders the else
|
54
|
+
it "renders the else template if the context is nil" do
|
43
55
|
template = Curlybars.compile(<<-HBS)
|
44
56
|
{{#with return_nil}}
|
45
57
|
text
|
@@ -87,6 +99,34 @@ describe "{{#with presenter}}...{{/with}}" do
|
|
87
99
|
expect(errors).to be_empty
|
88
100
|
end
|
89
101
|
|
102
|
+
it "without errors when a presenter helper is used" do
|
103
|
+
dependency_tree = { translate_article: [:helper, { title: nil }] }
|
104
|
+
|
105
|
+
source = <<-HBS
|
106
|
+
{{#with (translate_article 12345 "en-US")}}
|
107
|
+
{{title}}
|
108
|
+
{{/with}}
|
109
|
+
HBS
|
110
|
+
|
111
|
+
errors = Curlybars.validate(dependency_tree, source)
|
112
|
+
|
113
|
+
expect(errors).to be_empty
|
114
|
+
end
|
115
|
+
|
116
|
+
it "without errors when a generic presenter helper is used" do
|
117
|
+
dependency_tree = { article: { title: nil }, translate: [:helper, {}] }
|
118
|
+
|
119
|
+
source = <<-HBS
|
120
|
+
{{#with (translate article "en-US")}}
|
121
|
+
{{title}}
|
122
|
+
{{/with}}
|
123
|
+
HBS
|
124
|
+
|
125
|
+
errors = Curlybars.validate(dependency_tree, source)
|
126
|
+
|
127
|
+
expect(errors).to be_empty
|
128
|
+
end
|
129
|
+
|
90
130
|
it "with errors due to a leaf" do
|
91
131
|
dependency_tree = { not_a_presenter: nil }
|
92
132
|
|
@@ -110,5 +150,29 @@ describe "{{#with presenter}}...{{/with}}" do
|
|
110
150
|
|
111
151
|
expect(errors).not_to be_empty
|
112
152
|
end
|
153
|
+
|
154
|
+
it "with errors due collection helpers" do
|
155
|
+
dependency_tree = { reverse_articles: [:helper, [{ title: nil }]] }
|
156
|
+
|
157
|
+
source = <<-HBS
|
158
|
+
{{#with (reverse_articles)}}{{/with}}
|
159
|
+
HBS
|
160
|
+
|
161
|
+
errors = Curlybars.validate(dependency_tree, source)
|
162
|
+
|
163
|
+
expect(errors).not_to be_empty
|
164
|
+
end
|
165
|
+
|
166
|
+
it "with errors due generic collection helpers" do
|
167
|
+
dependency_tree = { articles: [{ title: nil }], slice: [:helper, [{}]] }
|
168
|
+
|
169
|
+
source = <<-HBS
|
170
|
+
{{#with (slice articles 0 4)}}{{/with}}
|
171
|
+
HBS
|
172
|
+
|
173
|
+
errors = Curlybars.validate(dependency_tree, source)
|
174
|
+
|
175
|
+
expect(errors).not_to be_empty
|
176
|
+
end
|
113
177
|
end
|
114
178
|
end
|
@@ -30,7 +30,7 @@ describe "tilde operator" do
|
|
30
30
|
it "runs even when 'run_processors' flag is set to false" do
|
31
31
|
allow(Curlybars::Processor::Tilde).to receive(:process!)
|
32
32
|
|
33
|
-
Curlybars.validate(presenter, "source", run_processors: false)
|
33
|
+
Curlybars.validate(presenter.dependency_tree, "source", run_processors: false)
|
34
34
|
|
35
35
|
expect(Curlybars::Processor::Tilde).to have_received(:process!)
|
36
36
|
end
|
@@ -9,19 +9,19 @@ describe "processors" do
|
|
9
9
|
|
10
10
|
describe "validation" do
|
11
11
|
it "are run by default" do
|
12
|
-
Curlybars.validate(presenter, "source")
|
12
|
+
Curlybars.validate(presenter.dependency_tree, "source")
|
13
13
|
|
14
14
|
expect(processor).to have_received(:process!)
|
15
15
|
end
|
16
16
|
|
17
|
-
it "are
|
18
|
-
Curlybars.validate(presenter, "source", run_processors: true)
|
17
|
+
it "are run when run_processors is true" do
|
18
|
+
Curlybars.validate(presenter.dependency_tree, "source", run_processors: true)
|
19
19
|
|
20
20
|
expect(processor).to have_received(:process!)
|
21
21
|
end
|
22
22
|
|
23
23
|
it "are not run when run_processors is false" do
|
24
|
-
Curlybars.validate(presenter, "source", run_processors: false)
|
24
|
+
Curlybars.validate(presenter.dependency_tree, "source", run_processors: false)
|
25
25
|
|
26
26
|
expect(processor).not_to have_received(:process!)
|
27
27
|
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.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Libo Cannici
|
@@ -11,10 +11,11 @@ authors:
|
|
11
11
|
- Luís Almeida
|
12
12
|
- Andreas Garnæs
|
13
13
|
- Augusto Silva
|
14
|
+
- Attila Večerek
|
14
15
|
autorequire:
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
|
-
date: 2021-
|
18
|
+
date: 2021-02-17 00:00:00.000000000 Z
|
18
19
|
dependencies:
|
19
20
|
- !ruby/object:Gem::Dependency
|
20
21
|
name: actionpack
|
@@ -234,6 +235,7 @@ files:
|
|
234
235
|
- lib/curlybars/error/presenter/not_found.rb
|
235
236
|
- lib/curlybars/error/render.rb
|
236
237
|
- lib/curlybars/error/validate.rb
|
238
|
+
- lib/curlybars/generic.rb
|
237
239
|
- lib/curlybars/lexer.rb
|
238
240
|
- lib/curlybars/method_whitelist.rb
|
239
241
|
- lib/curlybars/node/block_helper_else.rb
|