curlybars 1.4.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: 92b6cb3082b3fc52f509b72511b93937529f57170d4f8751f989e17727418889
4
- data.tar.gz: cbe9843e0852ccdb2bd9aabcb4c77e4856de40e60bbc01f6dc10d17e3987754a
3
+ metadata.gz: 662148aedd0a8a6fdb84727cbe92b66fe66abf3fc11fa3e7720d70337fd9c1ff
4
+ data.tar.gz: 9b599e7c0232ee1442e6f9e410a7e19f10405ea0dbb150bc309121a18be6afef
5
5
  SHA512:
6
- metadata.gz: 8a3cb40974bd6568a73b5640452403da9b40e63da6bc152fadd4ea375961cb8c5de21d561f094f5b181359c791ddf4237a0a58022ae2bd3b38d012748ae9eb6b
7
- data.tar.gz: 4bd84f5a24ee7fcb3f59fac17844d65638f496f22c263fc1e94c47aac65c84e848718f1695a20421ade814c9e7e98f17b0e10d58b14920fccb33d92987606e6b
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 }
@@ -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,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
@@ -1,3 +1,3 @@
1
1
  module Curlybars
2
- VERSION = '1.4.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(
@@ -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
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.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Libo Cannici
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2020-09-29 00:00:00.000000000 Z
17
+ date: 2020-10-27 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: actionpack
@@ -220,6 +220,7 @@ files:
220
220
  - lib/curlybars/node/path.rb
221
221
  - lib/curlybars/node/root.rb
222
222
  - lib/curlybars/node/string.rb
223
+ - lib/curlybars/node/sub_expression.rb
223
224
  - lib/curlybars/node/template.rb
224
225
  - lib/curlybars/node/text.rb
225
226
  - lib/curlybars/node/unless_else.rb
@@ -266,6 +267,7 @@ files:
266
267
  - spec/integration/node/partial_spec.rb
267
268
  - spec/integration/node/path_spec.rb
268
269
  - spec/integration/node/root_spec.rb
270
+ - spec/integration/node/sub_expression_spec.rb
269
271
  - spec/integration/node/template_spec.rb
270
272
  - spec/integration/node/unless_else_spec.rb
271
273
  - spec/integration/node/unless_spec.rb
@@ -293,7 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
293
295
  - !ruby/object:Gem::Version
294
296
  version: '0'
295
297
  requirements: []
296
- rubygems_version: 3.0.3
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