curlybars 1.2.1 → 1.5.1

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: 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