curlybars 1.2.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aaafd32b96d9cdf8e9af98ebe3636fc14d4df85761eea4ac804d4b623e1d186
4
- data.tar.gz: f177926ffc2cf7c9026a15f6ba7f43089cc9bab4da6e3318ab29a38003f4afdd
3
+ metadata.gz: 662148aedd0a8a6fdb84727cbe92b66fe66abf3fc11fa3e7720d70337fd9c1ff
4
+ data.tar.gz: 9b599e7c0232ee1442e6f9e410a7e19f10405ea0dbb150bc309121a18be6afef
5
5
  SHA512:
6
- metadata.gz: 25125dc3e0887ee7878a09f29e96c56f6d863d4aea21cb4c37e226d032cd9b5006c6a512837a9893b95188308d72de426673c8d8815c6d942bdad1ca60032403
7
- data.tar.gz: 2cc5791470872a77703979523e3de41a0db3491b8dd453628dc2c68ffe00dbef6374db91feb2da56ddae6edf75d5e6c21c5da3947048a3e320eb67da4d6a10c6
6
+ metadata.gz: 35b139b2d8c393a2a5a0f413edb687dfae425d315ef22b6cf9652a34a2367737dab922417c52494983f32e10b4f86dbc85ff62a12ac30cf178b7953af3dae3f1
7
+ data.tar.gz: 271f1f72a18ec374eadead76df6eda66e982ee5863d0be690903d581fbf6387cd83af6e9dbeed2b1351bfd2206c99a7b74ef4619fe2166c2b8a446033a164eac
@@ -32,6 +32,9 @@ module Curlybars
32
32
  r(/{{/) { push_state :curly; :START }
33
33
  r(/}}/, :curly) { pop_state; :END }
34
34
 
35
+ r(/\(/, :curly) { push_state :curly; :LPAREN }
36
+ r(/\)/, :curly) { pop_state; :RPAREN }
37
+
35
38
  r(/#/, :curly) { :HASH }
36
39
  r(/\//, :curly) { :SLASH }
37
40
  r(/>/, :curly) { :GT }
@@ -1,27 +1,57 @@
1
1
  module Curlybars
2
2
  module MethodWhitelist
3
- def allow_methods(*methods, **methods_with_type)
4
- methods_with_type.each do |(method_name, type)|
5
- if type.is_a?(Array)
6
- if type.size != 1 || !type.first.respond_to?(:dependency_tree)
7
- raise "Invalid allowed method syntax for `#{method_name}`. Collections must be of one presenter class"
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
- methods_list = methods + methods_with_type.keys
14
- defined?(super) ? super() + methods_list : methods_list
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 |*args|
18
- schema = methods.each_with_object({}) do |method, memo|
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 = methods_with_type.each_with_object({}) do |(method_name, type), memo|
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(*args)
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(*args)) if defined?(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(*args))
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 |*args|
45
- methods_schema(*args).each_with_object({}) do |method_with_type, memo|
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(*args)
80
+ type.dependency_tree(context)
51
81
  elsif type.is_a?(Array)
52
- [type.first.dependency_tree(*args)]
82
+ [type.first.dependency_tree(context)]
53
83
  else
54
84
  type
55
85
  end
@@ -15,7 +15,7 @@ module Curlybars
15
15
 
16
16
  def validate(branches)
17
17
  [
18
- expression.validate(branches, check_type: :not_helper),
18
+ expression.validate(branches),
19
19
  if_template.validate(branches),
20
20
  else_template.validate(branches)
21
21
  ]
@@ -15,9 +15,9 @@ module Curlybars
15
15
  end
16
16
 
17
17
  def validate(branches)
18
- catch(:skip_item_validation) do
19
- item.validate(branches)
20
- end
18
+ item.validate(branches)
19
+ rescue Curlybars::Error::Validate => path_error
20
+ path_error
21
21
  end
22
22
 
23
23
  def cache_key
@@ -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
- throw :skip_item_validation unless base_tree_position > 0
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
@@ -0,0 +1,62 @@
1
+ module Curlybars
2
+ module Node
3
+ SubExpression = Struct.new(:helper, :arguments, :options, :position) do
4
+ def compile
5
+ compiled_arguments = arguments.map do |argument|
6
+ "arguments.push(rendering.cached_call(#{argument.compile}))"
7
+ end.join("\n")
8
+
9
+ compiled_options = options.map do |option|
10
+ "options.merge!(#{option.compile})"
11
+ end.join("\n")
12
+
13
+ # NOTE: the following is a heredoc string, representing the ruby code fragment
14
+ # outputted by this node.
15
+ <<-RUBY
16
+ ::Module.new do
17
+ def self.exec(contexts, rendering)
18
+ rendering.check_timeout!
19
+
20
+ -> {
21
+ options = ::ActiveSupport::HashWithIndifferentAccess.new
22
+ #{compiled_options}
23
+
24
+ arguments = []
25
+ #{compiled_arguments}
26
+
27
+ helper = #{helper.compile}
28
+ helper_position = rendering.position(#{helper.position.line_number},
29
+ #{helper.position.line_offset})
30
+
31
+ options[:this] = contexts.last
32
+
33
+ rendering.call(helper, #{helper.path.inspect}, helper_position,
34
+ arguments, options)
35
+ }
36
+ end
37
+ end.exec(contexts, rendering)
38
+ RUBY
39
+ end
40
+
41
+ def validate_as_value(branches, check_type: :anything)
42
+ validate(branches, check_type: check_type)
43
+ end
44
+
45
+ def validate(branches, check_type: :anything)
46
+ [
47
+ helper.validate(branches, check_type: :helper),
48
+ arguments.map { |argument| argument.validate_as_value(branches) },
49
+ options.map { |option| option.validate(branches) }
50
+ ]
51
+ end
52
+
53
+ def cache_key
54
+ [
55
+ helper,
56
+ arguments,
57
+ options
58
+ ].flatten.map(&:cache_key).push(self.class.name).join("/")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -15,7 +15,7 @@ module Curlybars
15
15
 
16
16
  def validate(branches)
17
17
  [
18
- expression.validate(branches, check_type: :not_helper),
18
+ expression.validate(branches),
19
19
  unless_template.validate(branches),
20
20
  else_template.validate(branches)
21
21
  ]
@@ -15,6 +15,7 @@ require 'curlybars/node/block_helper_else'
15
15
  require 'curlybars/node/option'
16
16
  require 'curlybars/node/partial'
17
17
  require 'curlybars/node/output'
18
+ require 'curlybars/node/sub_expression'
18
19
 
19
20
  module Curlybars
20
21
  class Parser < RLTK::Parser
@@ -155,6 +156,7 @@ module Curlybars
155
156
  production(:expression) do
156
157
  clause('value') { |value| value }
157
158
  clause('path') { |path| path }
159
+ clause('subexpression') { |subexpression| subexpression }
158
160
  end
159
161
 
160
162
  production(:value) do
@@ -164,6 +166,15 @@ module Curlybars
164
166
 
165
167
  production(:path, 'PATH') { |path| Node::Path.new(path, pos(0)) }
166
168
 
169
+ production(:subexpression, 'LPAREN .path .expressions? .options? RPAREN') do |helper, arguments, options|
170
+ Node::SubExpression.new(
171
+ helper,
172
+ arguments || [],
173
+ options || [],
174
+ pos(0)
175
+ )
176
+ end
177
+
167
178
  finalize
168
179
 
169
180
  VOID = Class.new do
@@ -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)
@@ -1,3 +1,3 @@
1
1
  module Curlybars
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.5.0'.freeze
3
3
  end
@@ -160,6 +160,28 @@ describe Curlybars::Lexer do
160
160
  end
161
161
  end
162
162
 
163
+ describe "{{path (path context options)}}" do
164
+ it "is lexed with path, context and options" do
165
+ expect(lex('{{path (path context key=value)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :PATH, :KEY, :PATH, :RPAREN, :END]
166
+ end
167
+
168
+ it "is lexed without options" do
169
+ expect(lex('{{path (path context)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :PATH, :RPAREN, :END]
170
+ end
171
+
172
+ it "is lexed without context" do
173
+ expect(lex('{{path (path key=value)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :KEY, :PATH, :RPAREN, :END]
174
+ end
175
+
176
+ it "is lexed without context and options" do
177
+ expect(lex('{{path (path)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :RPAREN, :END]
178
+ end
179
+
180
+ it "is lexed with a nested subexpression" do
181
+ expect(lex('{{path (path (path context key=value) key=value)}}')).to produce [:START, :PATH, :LPAREN, :PATH, :LPAREN, :PATH, :PATH, :KEY, :PATH, :RPAREN, :KEY, :PATH, :RPAREN, :END]
182
+ end
183
+ end
184
+
163
185
  describe "{{#if path}}...{{/if}}" do
164
186
  it "is lexed" do
165
187
  expect(lex('{{#if path}} text {{/if}}')).to produce(
@@ -1,5 +1,32 @@
1
1
  describe Curlybars::MethodWhitelist do
2
- let(:dummy_class) { Class.new { extend Curlybars::MethodWhitelist } }
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: -> { { color_1: nil } } }
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: { color_1: nil })
237
+ expect(dummy_class.methods_schema(validation_context_class.new)).to eq(settings: { background_color: nil })
117
238
  end
118
239
 
119
- it "supports procs with arguments in schema" do
120
- dummy_class.class_eval { allow_methods settings: ->(name) { Hash[name, nil] } }
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(:background_color)).to eq(settings: { background_color: nil })
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 with errors the helper as condition" do
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).not_to be_empty
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 "without errors when it goes out of context" do
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).to be_empty
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
 
@@ -0,0 +1,428 @@
1
+ describe "{{(helper arg1 arg2 ... key=value ...)}}" do
2
+ let(:global_helpers_providers) { [IntegrationTest::GlobalHelperProvider.new] }
3
+
4
+ describe "#compile" do
5
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context")) }
6
+
7
+ it "can be an argument to helpers" do
8
+ template = Curlybars.compile(<<-HBS)
9
+ {{global_helper (global_helper 'argument' option='value') option='value'}}
10
+ HBS
11
+
12
+ expect(eval(template)).to resemble(<<-HTML)
13
+ argument - option:value - option:value
14
+ HTML
15
+ end
16
+
17
+ it "can be an argument to itself" do
18
+ template = Curlybars.compile(<<-HBS)
19
+ {{global_helper (global_helper (global_helper 'a' option='b') option='c') option='d'}}
20
+ HBS
21
+
22
+ expect(eval(template)).to resemble(<<-HTML)
23
+ a - option:b - option:c - option:d
24
+ HTML
25
+ end
26
+
27
+ it "can handle data objects as argument" do
28
+ template = Curlybars.compile(<<-HBS)
29
+ {{global_helper (extract user attribute='first_name') option='value'}}
30
+ HBS
31
+
32
+ expect(eval(template)).to resemble(<<-HTML)
33
+ Libo - option:value
34
+ HTML
35
+ end
36
+
37
+ it "can handle calls inside with" do
38
+ template = Curlybars.compile(<<-HBS)
39
+ {{#with article}}
40
+ {{global_helper (extract author attribute='first_name') option='value'}}
41
+ {{/with}}
42
+ HBS
43
+
44
+ expect(eval(template)).to resemble(<<-HTML)
45
+ Nicolò - option:value
46
+ HTML
47
+ end
48
+
49
+ it "does not accept subexpressions in the root" do
50
+ expect do
51
+ Curlybars.compile(<<-HBS)
52
+ {{(join articles attribute='title' separator='-'}}
53
+ HBS
54
+ end.to raise_error(Curlybars::Error::Parse)
55
+ end
56
+
57
+ it "can be called within if expressions" do
58
+ template = Curlybars.compile(<<-HBS)
59
+ {{#if (calc 3 ">" 1)}}
60
+ True
61
+ {{/if}}
62
+ HBS
63
+
64
+ expect(eval(template)).to resemble(<<-HTML)
65
+ True
66
+ HTML
67
+ end
68
+
69
+ # Replication of Handlebars' subexpression specs for feature parity
70
+ # https://github.com/handlebars-lang/handlebars.js/blob/1a08e1d0a7f500f2c1188cbd21750bb9180afcbb/spec/subexpressions.js
71
+
72
+ it "arg-less helper" do
73
+ template = Curlybars.compile(<<-HBS)
74
+ {{foo (bar)}}!
75
+ HBS
76
+
77
+ expect(eval(template)).to resemble(<<-HTML)
78
+ LOLLOL!
79
+ HTML
80
+ end
81
+
82
+ context "with blog presenter" do
83
+ let(:presenter) do
84
+ IntegrationTest::BlogPresenter.new(
85
+ lambda { |*args, options|
86
+ val = args.first
87
+ "val is #{val}"
88
+ }
89
+ )
90
+ end
91
+
92
+ it "helper w args" do
93
+ template = Curlybars.compile(<<-HBS)
94
+ {{blog (equal a b)}}
95
+ HBS
96
+
97
+ expect(eval(template)).to resemble(<<-HTML)
98
+ val is true
99
+ HTML
100
+ end
101
+
102
+ it "supports much nesting" do
103
+ template = Curlybars.compile(<<-HBS)
104
+ {{blog (equal (equal true true) true)}}
105
+ HBS
106
+
107
+ expect(eval(template)).to resemble(<<-HTML)
108
+ val is true
109
+ HTML
110
+ end
111
+
112
+ it "with hashes" do
113
+ template = Curlybars.compile(<<-HBS)
114
+ {{blog (equal (equal true true) true fun='yes')}}
115
+ HBS
116
+
117
+ expect(eval(template)).to resemble(<<-HTML)
118
+ val is true
119
+ HTML
120
+ end
121
+ end
122
+
123
+ context "with a different blog presenter" do
124
+ let(:presenter) do
125
+ IntegrationTest::BlogPresenter.new(
126
+ lambda { |*args, options|
127
+ "val is #{options[:fun]}"
128
+ }
129
+ )
130
+ end
131
+
132
+ it "as hashes" do
133
+ template = Curlybars.compile(<<-HBS)
134
+ {{blog fun=(equal (blog fun=1) 'val is 1')}}
135
+ HBS
136
+
137
+ expect(eval(template)).to resemble(<<-HTML)
138
+ val is true
139
+ HTML
140
+ end
141
+ end
142
+
143
+ context "with yet another blog presenter" do
144
+ let(:presenter) do
145
+ IntegrationTest::BlogPresenter.new(
146
+ lambda { |*args, options|
147
+ first, second, third = args
148
+ "val is #{first}, #{second} and #{third}"
149
+ }
150
+ )
151
+ end
152
+
153
+ it "mixed paths and helpers" do
154
+ template = Curlybars.compile(<<-HBS)
155
+ {{blog baz.bat (equal a b) baz.bar}}
156
+ HBS
157
+
158
+ expect(eval(template)).to resemble(<<-HTML)
159
+ val is bat!, true and bar!
160
+ HTML
161
+ end
162
+ end
163
+
164
+ describe "GH-800 : Complex subexpressions" do
165
+ let(:presenter) do
166
+ IntegrationTest::LetterPresenter.new(
167
+ a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' }
168
+ )
169
+ end
170
+
171
+ it "can handle complex subexpressions" do
172
+ inputs = [
173
+ "{{dash 'abc' (concat a b)}}",
174
+ "{{dash d (concat a b)}}",
175
+ "{{dash c.c (concat a b)}}",
176
+ "{{dash (concat a b) c.c}}",
177
+ "{{dash (concat a e.e) c.c}}"
178
+ ]
179
+
180
+ expected_results = [
181
+ "abc-ab",
182
+ "d-ab",
183
+ "c-ab",
184
+ "ab-c",
185
+ "ae-c"
186
+ ]
187
+
188
+ aggregate_failures do
189
+ inputs.each_with_index do |input, i|
190
+ expect(eval(Curlybars.compile(input))).to resemble(expected_results[i])
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ it "multiple subexpressions in a hash" do
197
+ template = Curlybars.compile(<<-HBS)
198
+ {{input aria-label=(t "Name") placeholder=(t "Example User")}}
199
+ HBS
200
+
201
+ expected_output = '<input aria-label="Name" placeholder="Example User" />'
202
+ .gsub("<", "&lt;")
203
+ .gsub(">", "&gt;")
204
+ .gsub('"', "&quot;")
205
+
206
+ expect(eval(template)).to resemble(expected_output)
207
+ end
208
+
209
+ context "with item show presenter" do
210
+ let(:presenter) do
211
+ IntegrationTest::ItemShowPresenter.new(field: "Name", placeholder: "Example User")
212
+ end
213
+
214
+ it "multiple subexpressions in a hash with context" do
215
+ template = Curlybars.compile(<<-HBS)
216
+ {{input aria-label=(t item.field) placeholder=(t item.placeholder)}}
217
+ HBS
218
+
219
+ expected_output = '<input aria-label="Name" placeholder="Example User" />'
220
+ .gsub("<", "&lt;")
221
+ .gsub(">", "&gt;")
222
+ .gsub('"', "&quot;")
223
+
224
+ expect(eval(template)).to resemble(expected_output)
225
+ end
226
+ end
227
+ end
228
+
229
+ describe "#validate" do
230
+ let(:presenter_class) { double(:presenter_class) }
231
+
232
+ before do
233
+ allow(Curlybars.configuration).to receive(:global_helpers_provider_classes).and_return([IntegrationTest::GlobalHelperProvider])
234
+ end
235
+
236
+ it "without errors when global helper" do
237
+ dependency_tree = {}
238
+
239
+ source = <<-HBS
240
+ {{#if (global_helper)}} ... {{/if}}
241
+ HBS
242
+
243
+ errors = Curlybars.validate(dependency_tree, source)
244
+
245
+ expect(errors).to be_empty
246
+ end
247
+
248
+ it "with errors when invoking a leaf" do
249
+ dependency_tree = { name: nil }
250
+
251
+ source = <<-HBS
252
+ {{#if (name)}} ... {{/if}}
253
+ HBS
254
+
255
+ errors = Curlybars.validate(dependency_tree, source)
256
+
257
+ expect(errors).not_to be_empty
258
+ end
259
+
260
+ it "without errors if argument is a leaf" do
261
+ dependency_tree = { helper: :helper, argument: nil }
262
+
263
+ source = <<-HBS
264
+ {{#if (helper argument)}} ... {{/if}}
265
+ HBS
266
+
267
+ errors = Curlybars.validate(dependency_tree, source)
268
+
269
+ expect(errors).to be_empty
270
+ end
271
+
272
+ it "without errors if argument is a literal" do
273
+ dependency_tree = { helper: :helper }
274
+
275
+ source = <<-HBS
276
+ {{#if (helper 'argument')}} ... {{/if}}
277
+ HBS
278
+
279
+ errors = Curlybars.validate(dependency_tree, source)
280
+
281
+ expect(errors).to be_empty
282
+ end
283
+
284
+ it "without errors if argument is a variable" do
285
+ dependency_tree = { helper: :helper }
286
+
287
+ source = <<-HBS
288
+ {{#if (helper @var)}} ... {{/if}}
289
+ HBS
290
+
291
+ errors = Curlybars.validate(dependency_tree, source)
292
+
293
+ expect(errors).to be_empty
294
+ end
295
+
296
+ it "without errors if argument is another subexpression" do
297
+ dependency_tree = { helper: :helper }
298
+
299
+ source = <<-HBS
300
+ {{#if (helper (helper option='argument'))}} ... {{/if}}
301
+ HBS
302
+
303
+ errors = Curlybars.validate(dependency_tree, source)
304
+
305
+ expect(errors).to be_empty
306
+ end
307
+
308
+ it "without errors if option is a leaf" do
309
+ dependency_tree = { helper: :helper, argument: nil }
310
+
311
+ source = <<-HBS
312
+ {{#if (helper option=argument)}} ... {{/if}}
313
+ HBS
314
+
315
+ errors = Curlybars.validate(dependency_tree, source)
316
+
317
+ expect(errors).to be_empty
318
+ end
319
+
320
+ it "without errors if option is a literal" do
321
+ dependency_tree = { helper: :helper }
322
+
323
+ source = <<-HBS
324
+ {{#if (helper option='argument')}} ... {{/if}}
325
+ HBS
326
+
327
+ errors = Curlybars.validate(dependency_tree, source)
328
+
329
+ expect(errors).to be_empty
330
+ end
331
+
332
+ it "without errors if option is a variable" do
333
+ dependency_tree = { helper: :helper }
334
+
335
+ source = <<-HBS
336
+ {{#if (helper option=@var)}} ... {{/if}}
337
+ HBS
338
+
339
+ errors = Curlybars.validate(dependency_tree, source)
340
+
341
+ expect(errors).to be_empty
342
+ end
343
+
344
+ it "without errors if option is another subexpression" do
345
+ dependency_tree = { helper: :helper }
346
+
347
+ source = <<-HBS
348
+ {{#if (helper option=(helper))}} ... {{/if}}
349
+ HBS
350
+
351
+ errors = Curlybars.validate(dependency_tree, source)
352
+
353
+ expect(errors).to be_empty
354
+ end
355
+
356
+ it "with errors when helper does not exist" do
357
+ dependency_tree = {}
358
+
359
+ source = <<-HBS
360
+ {{#if (helper)}} ... {{/if}}
361
+ HBS
362
+
363
+ errors = Curlybars.validate(dependency_tree, source)
364
+
365
+ expect(errors).not_to be_empty
366
+ end
367
+
368
+ it "with errors when invoking a leaf with arguments" do
369
+ dependency_tree = { name: nil }
370
+
371
+ source = <<-HBS
372
+ {{#if (name 'argument')}} ... {{/if}}
373
+ HBS
374
+
375
+ errors = Curlybars.validate(dependency_tree, source)
376
+
377
+ expect(errors).not_to be_empty
378
+ end
379
+
380
+ it "with errors when invoking a leaf with options" do
381
+ dependency_tree = { name: nil }
382
+
383
+ source = <<-HBS
384
+ {{#if (name option='value')}} ... {{/if}}
385
+ HBS
386
+
387
+ errors = Curlybars.validate(dependency_tree, source)
388
+
389
+ expect(errors).not_to be_empty
390
+ end
391
+
392
+ it "with errors if argument is not a value" do
393
+ dependency_tree = { helper: :helper }
394
+
395
+ source = <<-HBS
396
+ {{#if (helper not_a_value)}} ... {{/if}}
397
+ HBS
398
+
399
+ errors = Curlybars.validate(dependency_tree, source)
400
+
401
+ expect(errors).not_to be_empty
402
+ end
403
+
404
+ it "with errors if option is not a value" do
405
+ dependency_tree = { helper: :helper }
406
+
407
+ source = <<-HBS
408
+ {{#if (helper option=not_a_value)}} ... {{/if}}
409
+ HBS
410
+
411
+ errors = Curlybars.validate(dependency_tree, source)
412
+
413
+ expect(errors).not_to be_empty
414
+ end
415
+
416
+ it "without errors when invoking a helper with the result of a subexpression" do
417
+ dependency_tree = { join: :helper, uppercase: :helper, article: nil }
418
+
419
+ source = <<-HBS
420
+ {{join (uppercase article) attribute='title' separator='-'}}
421
+ HBS
422
+
423
+ errors = Curlybars.validate(dependency_tree, source)
424
+
425
+ expect(errors).to be_empty
426
+ end
427
+ end
428
+ end
@@ -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 with errors the helper as condition" do
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).not_to be_empty
108
+ expect(errors).to be_empty
109
109
  end
110
110
 
111
111
  it "validates with errors the nested unless_template" do
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.2.0
4
+ version: 1.5.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: 2019-03-15 00:00:00.000000000 Z
17
+ date: 2020-10-27 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: libo@zendesk.com
194
+ email: vikings@zendesk.com
194
195
  executables: []
195
196
  extensions: []
196
197
  extra_rdoc_files: []
@@ -219,6 +220,7 @@ files:
219
220
  - lib/curlybars/node/path.rb
220
221
  - lib/curlybars/node/root.rb
221
222
  - lib/curlybars/node/string.rb
223
+ - lib/curlybars/node/sub_expression.rb
222
224
  - lib/curlybars/node/template.rb
223
225
  - lib/curlybars/node/text.rb
224
226
  - lib/curlybars/node/unless_else.rb
@@ -265,6 +267,7 @@ files:
265
267
  - spec/integration/node/partial_spec.rb
266
268
  - spec/integration/node/path_spec.rb
267
269
  - spec/integration/node/root_spec.rb
270
+ - spec/integration/node/sub_expression_spec.rb
268
271
  - spec/integration/node/template_spec.rb
269
272
  - spec/integration/node/unless_else_spec.rb
270
273
  - spec/integration/node/unless_spec.rb
@@ -292,8 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
295
  - !ruby/object:Gem::Version
293
296
  version: '0'
294
297
  requirements: []
295
- rubyforge_project:
296
- rubygems_version: 2.7.6
298
+ rubygems_version: 3.1.4
297
299
  signing_key:
298
300
  specification_version: 4
299
301
  summary: Create your views using Handlebars templates!
@@ -319,6 +321,7 @@ test_files:
319
321
  - spec/integration/node/unless_else_spec.rb
320
322
  - spec/integration/node/output_spec.rb
321
323
  - spec/integration/node/if_spec.rb
324
+ - spec/integration/node/sub_expression_spec.rb
322
325
  - spec/integration/node/unless_spec.rb
323
326
  - spec/integration/node/with_spec.rb
324
327
  - spec/integration/node/partial_spec.rb