curlybars 1.2.1 → 1.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fbfe78e305b68cad9c83ab0deafe39a1814b519ede424de8459dfe8f096bee1
4
- data.tar.gz: 7fb5ee604e9379a73ef24cd607aea3a847656a3ecc6f3fddbedd0c4031b398fc
3
+ metadata.gz: 307c759c2acc1c33ab91a236aeb5042e6ecfee67581eef05f468c241a722e1f6
4
+ data.tar.gz: 68b0a401f830e5985d0d6b657974f84c2afdda2d49e70c696e97641845d31a16
5
5
  SHA512:
6
- metadata.gz: 1f3d779a0858270de68a2715f6badb08290cf977ad792ea019ee8d5b6f20ecc837371129d98a97f2a118df2edff352ec41d71023b4789b05321315fac5847526
7
- data.tar.gz: baeb86fd6563d5baca979bcc6cdd08f3a3a66b75bc29692fcbf366b7b3518725e2bd1fbdd6afa6b66567d95436f6d2d241c3d4c1cb81c6315a337df0aa9fc0aa
6
+ metadata.gz: 7db835baca28f2c15af2fb1a582059bb8346d1de41f8d9ed72262ea7ef1e2ff2e8130091b4e1494858b366f050e1b2fe2cf1753afbd1a403c66058c37f8fa9ff
7
+ data.tar.gz: 9a54e30bba2976a6c6f0ff4e6fef5f00075dc6186a9eb07c70a63ead7a96629a17897ce9ea9e09fad00049262a891eb88b1389873b629ca22f12c7b712686166
@@ -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
  ]
@@ -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.1'.freeze
2
+ VERSION = '1.5.1'.freeze
3
3
  end
@@ -75,6 +75,12 @@ module Curlybars
75
75
  def visit_string(_node)
76
76
  end
77
77
 
78
+ def visit_sub_expression(node)
79
+ visit(node.helper)
80
+ node.arguments.each { |argument| visit(argument) }
81
+ node.options.each { |option| visit(option) }
82
+ end
83
+
78
84
  def visit_template(node)
79
85
  node.items.each { |item| visit(item) }
80
86
  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'}}
@@ -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
@@ -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
@@ -4,6 +4,8 @@ describe "visitor" do
4
4
  {{#print_args_and_options 'first' 'second' key='value'}}
5
5
  {{/print_args_and_options}}
6
6
 
7
+ {{calc (calc 1 "+" 2) "*" 3}}
8
+
7
9
  {{#render_inverse}}
8
10
  fn
9
11
  {{else}}
@@ -53,7 +55,7 @@ describe "visitor" do
53
55
  it "visits BlockHelperElse nodes" do
54
56
  visitor = counting_visitor_for(Curlybars::Node::BlockHelperElse)
55
57
  output = Curlybars.visit(visitor, source)
56
- expect(output).to eq(4)
58
+ expect(output).to eq(5)
57
59
  end
58
60
 
59
61
  it "visits EachElse nodes" do
@@ -71,13 +73,13 @@ describe "visitor" do
71
73
  it "visits Item nodes" do
72
74
  visitor = counting_visitor_for(Curlybars::Node::Item)
73
75
  output = Curlybars.visit(visitor, source)
74
- expect(output).to eq(42)
76
+ expect(output).to eq(44)
75
77
  end
76
78
 
77
79
  it "visits Literal nodes" do
78
80
  visitor = counting_visitor_for(Curlybars::Node::Literal)
79
81
  output = Curlybars.visit(visitor, source)
80
- expect(output).to eq(3)
82
+ expect(output).to eq(8)
81
83
  end
82
84
 
83
85
  it "visits Option nodes" do
@@ -95,7 +97,7 @@ describe "visitor" do
95
97
  it "visits Path nodes" do
96
98
  visitor = counting_visitor_for(Curlybars::Node::Path)
97
99
  output = Curlybars.visit(visitor, source)
98
- expect(output).to eq(13)
100
+ expect(output).to eq(15)
99
101
  end
100
102
 
101
103
  it "visits Root nodes" do
@@ -104,6 +106,12 @@ describe "visitor" do
104
106
  expect(output).to eq(1)
105
107
  end
106
108
 
109
+ it "visits SubExpression nodes" do
110
+ visitor = counting_visitor_for(Curlybars::Node::SubExpression)
111
+ output = Curlybars.visit(visitor, source)
112
+ expect(output).to eq(1)
113
+ end
114
+
107
115
  it "visits Template nodes" do
108
116
  visitor = counting_visitor_for(Curlybars::Node::Template)
109
117
  output = Curlybars.visit(visitor, source)
@@ -113,7 +121,7 @@ describe "visitor" do
113
121
  it "visits Text nodes" do
114
122
  visitor = counting_visitor_for(Curlybars::Node::Text)
115
123
  output = Curlybars.visit(visitor, source)
116
- expect(output).to eq(28)
124
+ expect(output).to eq(29)
117
125
  end
118
126
 
119
127
  it "visits UnlessElse nodes" 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.1
4
+ version: 1.5.1
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-11-14 00:00:00.000000000 Z
17
+ date: 2020-11-02 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,7 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
295
  - !ruby/object:Gem::Version
293
296
  version: '0'
294
297
  requirements: []
295
- rubygems_version: 3.0.6
298
+ rubygems_version: 3.1.4
296
299
  signing_key:
297
300
  specification_version: 4
298
301
  summary: Create your views using Handlebars templates!
@@ -318,6 +321,7 @@ test_files:
318
321
  - spec/integration/node/unless_else_spec.rb
319
322
  - spec/integration/node/output_spec.rb
320
323
  - spec/integration/node/if_spec.rb
324
+ - spec/integration/node/sub_expression_spec.rb
321
325
  - spec/integration/node/unless_spec.rb
322
326
  - spec/integration/node/with_spec.rb
323
327
  - spec/integration/node/partial_spec.rb