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,10 +1,10 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: if_else.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
4
+ describe "Fixture: if_else.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
6
  let(:context) { Struct.new(:value).new }
7
- let(:template) { File.expand_path('../../fixtures/if_else.hbs', __FILE__) }
7
+ let(:template) { File.expand_path("../../fixtures/if_else.hbs", __FILE__) }
8
8
 
9
9
  it "renders the if block when given a string" do
10
10
  context.value = "Alan"
@@ -26,7 +26,7 @@ describe 'Fixture: if_else.hbs' do
26
26
  expect(subject).to eq "The given value is falsy: false."
27
27
  end
28
28
 
29
- it 'renders the else block when given nil' do
29
+ it "renders the else block when given nil" do
30
30
  context.value = nil
31
31
  expect(subject).to eq "The given value is falsy: ."
32
32
  end
@@ -1,15 +1,14 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: multi_level_with.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/multi_level_with.hbs', __FILE__) }
7
- let(:context) { Struct.new(:person,:company).new }
4
+ describe "Fixture: multi_level_with.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/multi_level_with.hbs", __FILE__) }
7
+ let(:context) { Struct.new(:person, :company).new }
8
8
 
9
- it 'renders correctly when person has a name' do
10
- context.person = Struct.new(:name).new('Alan')
11
- context.company = Struct.new(:name).new('Rad, Inc.')
12
- expect(subject).to eq 'Alan - Rad, Inc.'
9
+ it "renders correctly when person has a name" do
10
+ context.person = Struct.new(:name).new("Alan")
11
+ context.company = Struct.new(:name).new("Rad, Inc.")
12
+ expect(subject).to eq "Alan - Rad, Inc."
13
13
  end
14
-
15
14
  end
@@ -0,0 +1,34 @@
1
+ require "tilt"
2
+ require "flavour_saver"
3
+
4
+ # The two constructs that use the most stack per level.
5
+ describe "Rendering at the nesting depth limit inside a Fiber" do
6
+ subject do
7
+ # RSpec memoizes `let` behind a mutex that another Fiber can't unlock.
8
+ compiled = Tilt["handlebars"].new { template }
9
+ scope = context
10
+ Fiber.new { compiled.render(scope) }.resume
11
+ end
12
+
13
+ let(:max) { FlavourSaver::Parser::MAX_DEPTH }
14
+ let(:context) do
15
+ Struct.new(:items, :name).new.tap do |node|
16
+ node.items = [node]
17
+ node.name = "leaf"
18
+ end
19
+ end
20
+
21
+ before(:all) do
22
+ FlavourSaver.register_helper(:pass) { |value, *| value }
23
+ end
24
+
25
+ context "nested {{#each}} blocks" do
26
+ let(:template) { ("{{#each items}}" * max) + "{{name}}" + ("{{/each}}" * max) }
27
+ specify { expect(subject).to eq "leaf" }
28
+ end
29
+
30
+ context "nested hash subexpressions" do
31
+ let(:template) { "{{pass " + ("(pass name k=" * max) + "name" + (")" * max) + "}}" }
32
+ specify { expect(subject).to eq "leaf" }
33
+ end
34
+ end
@@ -1,13 +1,13 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: one_character_identifier.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/one_character_identifier.hbs', __FILE__) }
7
- let(:context) { double(:context) }
4
+ describe "Fixture: one_character_identifier.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/one_character_identifier.hbs", __FILE__) }
7
+ let(:context) { double(:context) }
8
8
 
9
- it 'renders correctly' do
10
- expect(context).to receive(:a).and_return('foo')
9
+ it "renders correctly" do
10
+ expect(context).to receive(:a).and_return("foo")
11
11
  expect(subject).to eq "foo"
12
12
  end
13
13
  end
@@ -1,12 +1,12 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: raw.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/raw.hbs', __FILE__) }
7
- let(:context) { double(:context) }
4
+ describe "Fixture: raw.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/raw.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 "{{=if brokensyntax}"
11
11
  end
12
12
  end
@@ -1,25 +1,28 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: sections.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
4
+ describe "Fixture: sections.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
6
  let(:context) { Struct.new(:name, :names, :order).new }
7
- let(:template) { File.expand_path('../../fixtures/sections.hbs', __FILE__) }
7
+ let(:template) { File.expand_path("../../fixtures/sections.hbs", __FILE__) }
8
8
 
9
- it 'renders correctly when given a name' do
10
- context.name = 'Alan'
9
+ it "renders correctly when given a name" do
10
+ context.name = "Alan"
11
11
  expect(subject).to eq "Say hello to Alan."
12
12
  end
13
13
 
14
- it 'renders correctly when given a list of names' do
15
- context.names = ['Foo', 'Bar']
14
+ it "renders correctly when given a list of names" do
15
+ context.names = ["Foo", "Bar"]
16
16
  expect(subject).to eq "* Foo * Bar"
17
17
  end
18
18
 
19
- it 'renders correctly when given an order' do
20
- class Order; def number; 1234; end; end
21
- context.order = Order.new
22
- expect(subject).to eq 'Number: 1234'
19
+ it "renders correctly when given an order" do
20
+ order = Class.new do
21
+ def number
22
+ 1234
23
+ end
24
+ end
25
+ context.order = order.new
26
+ expect(subject).to eq "Number: 1234"
23
27
  end
24
28
  end
25
-
@@ -1,4 +1,4 @@
1
- require 'flavour_saver'
1
+ require "flavour_saver"
2
2
 
3
3
  describe FlavourSaver do
4
4
  subject { FS.evaluate(template, context) }
@@ -10,17 +10,17 @@ describe FlavourSaver do
10
10
  FS.reset_partials
11
11
  end
12
12
 
13
- describe 'segment literal array access' do
14
- let(:template) { '{{foos.[1].bar}}' }
13
+ describe "segment literal array access" do
14
+ let(:template) { "{{foos.[1].bar}}" }
15
15
 
16
16
  it 'returns "two"' do
17
17
  foos = []
18
18
  foos << double(:foo)
19
19
  foos << double(:foo)
20
- expect(foos[1]).to receive(:bar).and_return('two')
20
+ expect(foos[1]).to receive(:bar).and_return("two")
21
21
 
22
22
  allow(context).to receive(:foos).and_return(foos)
23
- expect(subject).to eq 'two'
23
+ expect(subject).to eq "two"
24
24
  end
25
25
  end
26
26
  end
@@ -1,17 +1,17 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: simple_expression.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:template) { File.expand_path('../../fixtures/simple_expression.hbs', __FILE__) }
7
- let(:context) { double(:context) }
4
+ describe "Fixture: simple_expression.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
+ let(:template) { File.expand_path("../../fixtures/simple_expression.hbs", __FILE__) }
7
+ let(:context) { double(:context) }
8
8
 
9
- it 'renders correctly' do
10
- expect(context).to receive(:hello).and_return('hello world')
9
+ it "renders correctly" do
10
+ expect(context).to receive(:hello).and_return("hello world")
11
11
  expect(subject).to eq "hello world"
12
12
  end
13
13
 
14
- it 'renders nothing if undefined' do
14
+ it "renders nothing if undefined" do
15
15
  expect(subject).to eq ""
16
16
  end
17
17
  end
@@ -1,37 +1,38 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Subexpressions' do
4
+ describe "Subexpressions" do
5
+ subject { Tilt["handlebars"].new { template }.render(context).gsub(/[\s\r\n]+/, " ").strip }
5
6
 
6
- subject { Tilt['handlebars'].new{ template }.render(context).gsub(/[\s\r\n]+/, ' ').strip }
7
-
8
- let(:context) { double(:context) }
7
+ let(:context) { double(:context) }
9
8
  before(:all) do
10
- FlavourSaver.register_helper(:sum) { |a,b| a + b}
9
+ FlavourSaver.register_helper(:sum) { |a, b| a + b }
11
10
  end
12
11
  context "simple subexpression" do
13
12
  let(:template) { "{{sum 1 (sum 1 1)}}" }
14
- specify{expect(subject).to eq "3"}
13
+ specify { expect(subject).to eq "3" }
15
14
  end
16
15
 
17
16
  context "nested subexpressions" do
18
17
  let(:template) { "{{sum 1 (sum 1 (sum 1 1))}}" }
19
- specify{expect(subject).to eq "4"}
18
+ specify { expect(subject).to eq "4" }
20
19
  end
21
20
 
22
21
  context "subexpression as argument" do
23
- before {FlavourSaver.register_helper(:cents) { |a| a[:total] + 10}}
22
+ before { FlavourSaver.register_helper(:cents) { |a| a[:total] + 10 } }
24
23
  let(:template) { "{{cents total=(sum 1 1)}}" }
25
- specify{expect(subject).to eq "12"}
24
+ specify { expect(subject).to eq "12" }
26
25
  end
27
26
 
28
27
  context "subexpression in block" do
29
- before {FlavourSaver.register_helper(:repeat) do |a, &block|
30
- s = ''
31
- a.times {s += block.call.contents}
32
- s
33
- end}
28
+ before {
29
+ FlavourSaver.register_helper(:repeat) do |a, &block|
30
+ s = ""
31
+ a.times { s += block.call.contents }
32
+ s
33
+ end
34
+ }
34
35
  let(:template) { "{{#repeat (sum 1 2)}}*{{/repeat}}" }
35
- specify{expect(subject).to eq "***"}
36
+ specify { expect(subject).to eq "***" }
36
37
  end
37
38
  end
@@ -1,17 +1,17 @@
1
- require 'tilt'
2
- require 'flavour_saver'
1
+ require "tilt"
2
+ require "flavour_saver"
3
3
 
4
- describe 'Fixture: unless.hbs' do
5
- subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
4
+ describe "Fixture: unless.hbs" do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, " ").strip }
6
6
  let(:context) { Struct.new(:value).new }
7
- let(:template) { File.expand_path('../../fixtures/unless.hbs', __FILE__) }
7
+ let(:template) { File.expand_path("../../fixtures/unless.hbs", __FILE__) }
8
8
 
9
9
  it "renders the unless block when given false" do
10
10
  context.value = false
11
11
  expect(subject).to eq "The given value is falsy: false."
12
12
  end
13
13
 
14
- it 'renders the unless block when given nil' do
14
+ it "renders the unless block when given nil" do
15
15
  context.value = nil
16
16
  expect(subject).to eq "The given value is falsy: ."
17
17
  end
@@ -45,4 +45,17 @@ describe 'Fixture: unless.hbs' do
45
45
  context.value = [1]
46
46
  expect(subject).to eq "The given value is truthy: [1]."
47
47
  end
48
+ describe "an inverted section with an else" do
49
+ subject { FlavourSaver.evaluate("{{^value}}falsy{{else}}truthy{{/value}}", context) }
50
+
51
+ it "renders the body when the value is falsy" do
52
+ context.value = false
53
+ expect(subject).to eq "falsy"
54
+ end
55
+
56
+ it "renders the else section when the value is truthy" do
57
+ context.value = true
58
+ expect(subject).to eq "truthy"
59
+ end
60
+ end
48
61
  end