flavour_saver 4.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +57 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +48 -5
  7. data/README.md +153 -271
  8. data/flavour_saver.gemspec +17 -19
  9. data/lib/flavour_saver/error.rb +5 -0
  10. data/lib/flavour_saver/helpers.rb +19 -18
  11. data/lib/flavour_saver/lexer.rb +123 -50
  12. data/lib/flavour_saver/nodes.rb +107 -19
  13. data/lib/flavour_saver/parser.rb +339 -161
  14. data/lib/flavour_saver/partial.rb +4 -6
  15. data/lib/flavour_saver/rails_partial.rb +10 -4
  16. data/lib/flavour_saver/runtime.rb +76 -81
  17. data/lib/flavour_saver/template.rb +3 -5
  18. data/lib/flavour_saver/version.rb +1 -1
  19. data/lib/flavour_saver.rb +17 -17
  20. data/spec/acceptance/backtrack_spec.rb +9 -9
  21. data/spec/acceptance/comment_spec.rb +7 -7
  22. data/spec/acceptance/custom_block_helper_spec.rb +12 -12
  23. data/spec/acceptance/custom_helper_spec.rb +8 -8
  24. data/spec/acceptance/ensure_no_rce_spec.rb +64 -53
  25. data/spec/acceptance/handlebars_qunit_spec.rb +295 -297
  26. data/spec/acceptance/if_else_spec.rb +6 -6
  27. data/spec/acceptance/multi_level_with_spec.rb +10 -11
  28. data/spec/acceptance/nesting_depth_spec.rb +34 -0
  29. data/spec/acceptance/one_character_identifier_spec.rb +8 -8
  30. data/spec/acceptance/raw_block_spec.rb +7 -7
  31. data/spec/acceptance/sections_spec.rb +17 -14
  32. data/spec/acceptance/segment_literals_spec.rb +5 -5
  33. data/spec/acceptance/simple_expression_spec.rb +9 -9
  34. data/spec/acceptance/subexpression_spec.rb +18 -17
  35. data/spec/acceptance/unless_spec.rb +19 -6
  36. data/spec/lib/flavour_saver/lexer_spec.rb +103 -89
  37. data/spec/lib/flavour_saver/nodes_spec.rb +85 -0
  38. data/spec/lib/flavour_saver/parser_spec.rb +155 -95
  39. data/spec/lib/flavour_saver/runtime_spec.rb +94 -96
  40. data/spec/lib/flavour_saver/template_spec.rb +1 -2
  41. metadata +19 -48
@@ -1,30 +1,30 @@
1
- require 'cgi'
1
+ require "cgi"
2
2
 
3
3
  module FlavourSaver
4
- UnknownNodeTypeException = Class.new(StandardError)
5
- UnknownContextException = Class.new(StandardError)
6
- InappropriateUseOfElseException = Class.new(StandardError)
4
+ UnknownNodeTypeException = Class.new(StandardError)
5
+ UnknownContextException = Class.new(StandardError)
6
+ InappropriateUseOfElseException = Class.new(StandardError)
7
7
  UndefinedPrivateVariableException = Class.new(StandardError)
8
- UnknownHelperException = Class.new(RuntimeError)
9
- ForbiddenMethodException = Class.new(UnknownHelperException)
8
+ UnknownHelperException = Class.new(RuntimeError)
9
+ ForbiddenMethodException = Class.new(UnknownHelperException)
10
10
  class Runtime
11
+ attr_accessor :context, :ast, :privates
12
+ attr_writer :parent
11
13
 
12
- attr_accessor :context, :parent, :ast, :privates
13
-
14
- def self.run(ast, context, locals={}, helpers=[])
15
- self.new(ast, context, locals, helpers).to_s
14
+ def self.run(ast, context, locals = {}, helpers = [])
15
+ new(ast, context, locals, helpers).to_s
16
16
  end
17
17
 
18
- def initialize(ast, context=nil, locals={}, helpers=[],parent=nil)
18
+ def initialize(ast, context = nil, locals = {}, helpers = [], parent = nil)
19
19
  @ast = ast
20
20
  @locals = locals
21
21
  @helpers = helpers
22
22
  @context = context
23
23
  @parent = parent
24
- @privates = parent ? parent.privates.dup : { 'root' => context }
24
+ @privates = parent ? parent.privates.dup : {"root" => context}
25
25
  end
26
26
 
27
- def to_s(tmp_context = nil,privates={})
27
+ def to_s(tmp_context = nil, privates = {})
28
28
  result = nil
29
29
  if tmp_context
30
30
  old_context = @context
@@ -35,37 +35,35 @@ module FlavourSaver
35
35
  @privates = old_privates
36
36
  @context = old_context
37
37
  else
38
- result = evaluate_node(@ast).to_s.force_encoding(Encoding::default_external)
38
+ result = evaluate_node(@ast).to_s.force_encoding(Encoding.default_external)
39
39
  end
40
40
  result
41
41
  end
42
42
 
43
- def private_variable_set(name,value)
43
+ def private_variable_set(name, value)
44
44
  @privates[name.to_s] = value
45
45
  end
46
46
 
47
47
  def private_variable_get(name)
48
- begin
49
- @privates.fetch(name)
50
- rescue KeyError
51
- raise UndefinedPrivateVariableException, "private variable not found @#{name}"
52
- end
48
+ @privates.fetch(name)
49
+ rescue KeyError
50
+ raise UndefinedPrivateVariableException, "private variable not found @#{name}"
53
51
  end
54
52
 
55
53
  def strip(tmp_context = nil)
56
- self.to_s(tmp_context).gsub(/[\s]+/,' ').strip
54
+ to_s(tmp_context).gsub(/\s+/, " ").strip
57
55
  end
58
56
 
59
57
  def evaluate_node(node)
60
58
  case node
61
59
  when TemplateNode
62
- node.items.map { |n| evaluate_node(n) }.join('')
60
+ node.items.map { |n| evaluate_node(n) }.join("")
63
61
  when BlockExpressionNode
64
62
  evaluate_block(node).to_s
65
63
  when OutputNode
66
64
  node.value
67
65
  when NumberNode
68
- if node.value =~ /\./
66
+ if /\./.match?(node.value)
69
67
  node.value.to_f
70
68
  else
71
69
  node.value.to_i
@@ -79,12 +77,12 @@ module FlavourSaver
79
77
  when CallNode
80
78
  evaluate_call(node)
81
79
  when Hash
82
- node.each do |key,value|
80
+ node.each do |key, value|
83
81
  node[key] = evaluate_argument(value)
84
82
  end
85
83
  node
86
84
  when CommentNode
87
- ''
85
+ ""
88
86
  when PartialNode
89
87
  evaluate_partial(node)
90
88
  else
@@ -102,37 +100,37 @@ module FlavourSaver
102
100
  end
103
101
 
104
102
  def evaluate_partial(node)
105
- _context = context
106
- _context = evaluate_argument(node.context) if node.context
103
+ partial_context = context
104
+ partial_context = evaluate_argument(node.context) if node.context
107
105
  if defined?(::Rails)
108
- context.send(:render, :partial => node.name, :object => _context)
106
+ context.send(:render, partial: node.name, object: partial_context)
109
107
  else
110
108
  partial = Partial.fetch(node.name)
111
109
  if partial.respond_to? :call
112
- partial.call(_context)
110
+ partial.call(partial_context)
113
111
  else
114
- create_child_runtime(partial).to_s(_context)
112
+ create_child_runtime(partial).to_s(partial_context)
115
113
  end
116
114
  end
117
115
  end
118
116
 
119
- def evaluate_call(call, context=self.context, &block)
120
- context = Helpers.decorate_with(context,@helpers,@locals) unless context.is_a? Helpers::Decorator
117
+ def evaluate_call(call, context = self.context, &block)
118
+ context = Helpers.decorate_with(context, @helpers, @locals) unless context.is_a? Helpers::Decorator
121
119
  case call
122
120
  when ParentCallNode
123
121
  depth = call.depth
124
- (2..depth).inject(parent) { |p| p.parent }.evaluate_call(call.to_callnode,&block)
122
+ (2..depth).inject(parent) { |p| p.parent }.evaluate_call(call.to_callnode, &block)
125
123
  when LiteralCallNode
126
124
  result = context.send(:[], call.name)
127
- result = result.call(*call.arguments.map { |a| evaluate_argument(a) },&block) if result.respond_to? :call
125
+ result = result.call(*call.arguments.map { |a| evaluate_argument(a) }, &block) if result.respond_to? :call
128
126
  result
129
127
  when LocalVarNode
130
- result = private_variable_get(call.name)
128
+ private_variable_get(call.name)
131
129
  else
132
130
  if forbidden_method? context, call.name
133
131
  raise ForbiddenMethodException, "Refusing to call #{call.name.inspect} from a template: it isn't a helper, a local, or part of the template context's own API. Register a helper if you meant to expose it, or use #{"{{[#{call.name}]}}"} to read a data field of that name."
134
132
  end
135
- if call.parent.is_a? BlockExpressionNode and !context.respond_to? call.name
133
+ if call.parent.is_a?(BlockExpressionNode) && !context.respond_to?(call.name)
136
134
  raise UnknownHelperException, "Template context doesn't respond to method #{call.name.inspect}."
137
135
  end
138
136
  context.public_send(call.name, *call.arguments.map { |a| evaluate_argument(a) }, &block)
@@ -148,8 +146,8 @@ module FlavourSaver
148
146
  end
149
147
 
150
148
  def evaluate_object_path(path, &block)
151
- path.inject(context) do |context,call|
152
- context = evaluate_call(call, context, &block)
149
+ path.inject(context) do |context, call|
150
+ evaluate_call(call, context, &block)
153
151
  end
154
152
  end
155
153
 
@@ -157,11 +155,11 @@ module FlavourSaver
157
155
  evaluate_object_path(node.method)
158
156
  end
159
157
 
160
- def evaluate_block(node,block_context=@context)
158
+ def evaluate_block(node, block_context = @context)
161
159
  call = node.method.first
162
160
  content_runtime = create_child_runtime(node.contents)
163
161
  alternate_runtime = create_child_runtime(node.alternate) if node.respond_to? :alternate
164
- block_runtime = BlockRuntime.new(block_context,content_runtime,alternate_runtime)
162
+ block_runtime = BlockRuntime.new(block_context, content_runtime, alternate_runtime)
165
163
 
166
164
  result = evaluate_call(call, block_context) { block_runtime }
167
165
 
@@ -172,17 +170,17 @@ module FlavourSaver
172
170
 
173
171
  # If the result is collectiony then act as an implicit
174
172
  # "each"
175
- if result && result.respond_to?(:each)
173
+ if result&.respond_to?(:each)
176
174
  if result.respond_to?(:size) && (result.size > 0)
177
175
  r = []
178
176
  # Not using #each_with_index because the object might
179
177
  # not actually be an Enumerable
180
178
  count = 0
181
179
  result.each do |e|
182
- r << block_runtime.contents(e, {'index' => count})
180
+ r << block_runtime.contents(e, {"index" => count})
183
181
  count += 1
184
182
  end
185
- result = r.join('')
183
+ result = r.join("")
186
184
  else
187
185
  result = block_runtime.inverse
188
186
  end
@@ -191,19 +189,19 @@ module FlavourSaver
191
189
  elsif result
192
190
  result = block_runtime.contents result
193
191
  else
194
- if block_runtime.has_inverse?
195
- result = block_runtime.inverse
192
+ result = if block_runtime.has_inverse?
193
+ block_runtime.inverse
196
194
  else
197
- result = ''
195
+ ""
198
196
  end
199
197
  end
200
198
  end
201
199
  result
202
200
  end
203
201
 
204
- def create_child_runtime(body=[])
202
+ def create_child_runtime(body = [])
205
203
  node = body.is_a?(TemplateNode) ? body : TemplateNode.new(body)
206
- Runtime.new(node,nil,@locals,@helpers,self)
204
+ Runtime.new(node, nil, @locals, @helpers, self)
207
205
  end
208
206
 
209
207
  def inspect
@@ -211,21 +209,21 @@ module FlavourSaver
211
209
  end
212
210
 
213
211
  class BlockRuntime
214
- def initialize(block_context,content_runtime,alternate_runtime=nil)
212
+ def initialize(block_context, content_runtime, alternate_runtime = nil)
215
213
  @block_context = block_context
216
214
  @content_runtime = content_runtime
217
215
  @alternate_runtime = alternate_runtime
218
216
  @render_count = 0
219
217
  end
220
218
 
221
- def contents(context=@block_context,locals={})
219
+ def contents(context = @block_context, locals = {})
222
220
  @render_count += 1
223
- @content_runtime.to_s(context,locals) if @content_runtime
221
+ @content_runtime&.to_s(context, locals)
224
222
  end
225
223
 
226
- def inverse(context=@block_context)
224
+ def inverse(context = @block_context)
227
225
  @render_count += 1
228
- @alternate_runtime.to_s(context) if @alternate_runtime
226
+ @alternate_runtime&.to_s(context)
229
227
  end
230
228
 
231
229
  def has_inverse?
@@ -233,7 +231,7 @@ module FlavourSaver
233
231
  end
234
232
 
235
233
  def rendered?
236
- @render_count > 0 ? @render_count : false
234
+ (@render_count > 0) ? @render_count : false
237
235
  end
238
236
 
239
237
  def rendered!
@@ -273,10 +271,10 @@ module FlavourSaver
273
271
  # public_send.
274
272
  def forbidden_method?(context, name)
275
273
  owner = begin
276
- context.method(name).owner
277
- rescue NameError
278
- return false
279
- end
274
+ context.method(name).owner
275
+ rescue NameError
276
+ return false
277
+ end
280
278
 
281
279
  return false if owner.equal? Helpers::Defaults
282
280
  return false unless Helpers::Decorator.ancestors.include? owner
@@ -294,32 +292,29 @@ module FlavourSaver
294
292
  end
295
293
 
296
294
  def escape(output)
297
- if output.respond_to?(:html_safe) && output.html_safe?
298
- # If the string is already marked as html_safe then don't
299
- # escape it any further.
300
- output
301
-
302
- else
303
- output = CGI.escapeHTML(output)
304
-
305
- # We can't just use CGI.escapeHTML because Handlebars does extra
306
- # escaping for its JavaScript environment. Thems the breaks.
307
- output = output.gsub(/(['"`])/) do |match|
308
- case match
309
- when "'"
310
- "&#x27;"
311
- when '"'
312
- "&quot;"
313
- when '`'
314
- "&#x60;"
315
- end
295
+ # If the string is already marked as html_safe then don't
296
+ # escape it any further.
297
+ return output if output.respond_to?(:html_safe) && output.html_safe?
298
+
299
+ output = CGI.escapeHTML(output)
300
+
301
+ # We can't just use CGI.escapeHTML because Handlebars does extra
302
+ # escaping for its JavaScript environment. Thems the breaks.
303
+ output = output.gsub(/(['"`])/) do |match|
304
+ case match
305
+ when "'"
306
+ "&#x27;"
307
+ when '"'
308
+ "&quot;"
309
+ when "`"
310
+ "&#x60;"
316
311
  end
312
+ end
317
313
 
318
- # Mark it as already escaped if we're in Rails
319
- output.html_safe if output.respond_to? :html_safe
314
+ # Mark it as already escaped if we're in Rails
315
+ output.html_safe if output.respond_to? :html_safe
320
316
 
321
- output
322
- end
317
+ output
323
318
  end
324
319
  end
325
320
  end
@@ -1,8 +1,7 @@
1
- require 'tilt/template'
1
+ require "tilt/template"
2
2
 
3
3
  module FlavourSaver
4
4
  class Template < Tilt::Template
5
-
6
5
  def self.engine_initialized?
7
6
  true
8
7
  end
@@ -11,9 +10,8 @@ module FlavourSaver
11
10
  @ast = Parser.parse(Lexer.lex(data))
12
11
  end
13
12
 
14
- def evaluate(scope=Object.new,locals={},&block)
15
- Runtime.run(@ast,scope,locals)
13
+ def evaluate(scope = Object.new, locals = {}, &block)
14
+ Runtime.run(@ast, scope, locals)
16
15
  end
17
-
18
16
  end
19
17
  end
@@ -1,3 +1,3 @@
1
1
  module FlavourSaver
2
- VERSION = "4.0.2"
2
+ VERSION = "5.0.0"
3
3
  end
data/lib/flavour_saver.rb CHANGED
@@ -1,16 +1,16 @@
1
1
  require "flavour_saver/version"
2
- require 'tilt'
2
+ require "flavour_saver/error"
3
+ require "tilt"
3
4
 
4
5
  module FlavourSaver
5
-
6
- autoload :Lexer, 'flavour_saver/lexer'
7
- autoload :Parser, 'flavour_saver/parser'
8
- autoload :Runtime, 'flavour_saver/runtime'
9
- autoload :Helpers, 'flavour_saver/helpers'
10
- autoload :Partial, 'flavour_saver/partial'
11
- autoload :RailsPartial, 'flavour_saver/rails_partial'
12
- autoload :Template, 'flavour_saver/template'
13
- autoload :NodeCollection, 'flavour_saver/node_collection'
6
+ autoload :Lexer, "flavour_saver/lexer"
7
+ autoload :Parser, "flavour_saver/parser"
8
+ autoload :Runtime, "flavour_saver/runtime"
9
+ autoload :Helpers, "flavour_saver/helpers"
10
+ autoload :Partial, "flavour_saver/partial"
11
+ autoload :RailsPartial, "flavour_saver/rails_partial"
12
+ autoload :Template, "flavour_saver/template"
13
+ autoload :NodeCollection, "flavour_saver/node_collection"
14
14
 
15
15
  if defined? Rails
16
16
  class Engine < Rails::Engine
@@ -45,20 +45,20 @@ module FlavourSaver
45
45
  Parser.parse(tokens)
46
46
  end
47
47
 
48
- def evaluate(template,context)
48
+ def evaluate(template, context)
49
49
  Runtime.run(parse(lex(template)), context)
50
50
  end
51
51
 
52
- def register_helper(*args,&b)
53
- Helpers.register_helper(*args,&b)
52
+ def register_helper(*args, &b)
53
+ Helpers.register_helper(*args, &b)
54
54
  end
55
55
 
56
56
  def reset_helpers
57
57
  Helpers.reset_helpers
58
58
  end
59
59
 
60
- def register_partial(name,content=nil,&block)
61
- Partial.register_partial(name,content,&block)
60
+ def register_partial(name, content = nil, &block)
61
+ Partial.register_partial(name, content, &block)
62
62
  end
63
63
 
64
64
  def reset_partials
@@ -70,10 +70,10 @@ module FlavourSaver
70
70
  end
71
71
 
72
72
  def logger=(logger)
73
- @logger=logger
73
+ @logger = logger
74
74
  end
75
75
 
76
- Tilt.register(Template, 'handlebars', 'hbs')
76
+ Tilt.register(Template, "handlebars", "hbs")
77
77
  end
78
78
 
79
79
  FS = FlavourSaver
@@ -1,14 +1,14 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: backtrack.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/backtrack.hbs', __FILE__) }
7
- let(:context) { Struct.new(:person,:company).new }
4
+ describe "Fixture: backtrack.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/backtrack.hbs", __FILE__) }
7
+ let(:context) { Struct.new(:person, :company).new }
8
8
 
9
- it 'renders correctly' do
10
- context.person = Struct.new(:name).new('Alan')
11
- context.company = Struct.new(:name).new('Rad, Inc.')
9
+ it "renders correctly" do
10
+ context.person = Struct.new(:name).new("Alan")
11
+ context.company = Struct.new(:name).new("Rad, Inc.")
12
12
  expect(subject).to eq "Alan - Rad, Inc."
13
13
  end
14
14
  end
@@ -1,12 +1,12 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: comment.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/comment.hbs', __FILE__) }
7
- let(:context) { double(:context) }
4
+ describe "Fixture: comment.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/comment.hbs", __FILE__) }
7
+ let(:context) { double(:context) }
8
8
 
9
- it 'renders correctly' do
9
+ it "renders correctly" do
10
10
  expect(subject).to eq "I am a very nice person!"
11
11
  end
12
12
  end
@@ -1,33 +1,33 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: custom_block_helper.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/custom_block_helper.hbs', __FILE__) }
7
- let(:context) { double(:context) }
4
+ describe "Fixture: custom_block_helper.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/custom_block_helper.hbs", __FILE__) }
7
+ let(:context) { double(:context) }
8
8
 
9
9
  before(:each) do
10
10
  FlavourSaver::Helpers.reset_helpers
11
11
  end
12
12
 
13
- describe 'method helper' do
14
- it 'renders correctly' do
13
+ describe "method helper" do
14
+ it "renders correctly" do
15
15
  def three_times
16
16
  (1..3).map do |i|
17
17
  yield.contents i
18
- end.join ''
18
+ end.join ""
19
19
  end
20
20
  FlavourSaver.register_helper(method(:three_times))
21
21
  expect(subject).to eq "1 time. 2 time. 3 time."
22
22
  end
23
23
  end
24
24
 
25
- describe 'proc helper' do
26
- it 'renders correctly' do
25
+ describe "proc helper" do
26
+ it "renders correctly" do
27
27
  FlavourSaver.register_helper(:three_times) { |&b|
28
28
  (1..3).map do |i|
29
29
  b.call.contents i
30
- end.join ''
30
+ end.join ""
31
31
  }
32
32
  expect(subject).to eq "1 time. 2 time. 3 time."
33
33
  end
@@ -1,14 +1,14 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: custom_helper.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/custom_helper.hbs', __FILE__) }
7
- let(:context) { double(:context) }
4
+ describe "Fixture: custom_helper.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/custom_helper.hbs", __FILE__) }
7
+ let(:context) { double(:context) }
8
8
 
9
- it 'renders correctly' do
9
+ it "renders correctly" do
10
10
  FlavourSaver.register_helper(:say_what_again) do
11
- 'What?'
11
+ "What?"
12
12
  end
13
13
  expect(subject).to eq "What?"
14
14
  end