curlybars 1.6.0 → 1.7.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 +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
|