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,224 +1,222 @@
1
- require 'flavour_saver'
1
+ require "flavour_saver"
2
2
 
3
3
  describe FlavourSaver::Runtime do
4
- let(:template) { '' }
5
- let(:tokens) { FlavourSaver.lex(template) }
6
- let(:ast) { FlavourSaver.parse(tokens) }
7
- let(:context) { double(:context) }
8
- subject { FlavourSaver::Runtime.new(ast, context) }
9
-
10
- describe '#evaluate_node' do
11
- describe 'when passed a TemplateNode' do
4
+ let(:template) { "" }
5
+ let(:tokens) { FlavourSaver.lex(template) }
6
+ let(:ast) { FlavourSaver.parse(tokens) }
7
+ let(:context) { double(:context) }
8
+ subject { FlavourSaver::Runtime.new(ast, context) }
9
+
10
+ describe "#evaluate_node" do
11
+ describe "when passed a TemplateNode" do
12
12
  let(:template) { "hello world" }
13
13
 
14
14
  it "concatenates all the nodes items together" do
15
- expect(subject.evaluate_node(ast)).to eq 'hello world'
15
+ expect(subject.evaluate_node(ast)).to eq "hello world"
16
16
  end
17
17
  end
18
18
 
19
- describe 'when passed an OutputNode' do
19
+ describe "when passed an OutputNode" do
20
20
  let(:template) { "hello world" }
21
21
 
22
22
  it "returns the value of OutputNodes" do
23
23
  node = ast.items.first
24
- expect(subject.evaluate_node(node)).to eq 'hello world'
24
+ expect(subject.evaluate_node(node)).to eq "hello world"
25
25
  end
26
26
  end
27
27
 
28
- describe 'when passed a StringNode' do
28
+ describe "when passed a StringNode" do
29
29
  let(:template) { "{{foo \"WAT\"}}" }
30
- let(:node) { ast.items.select { |n| n.class == FlavourSaver::ExpressionNode }.first.method.first.arguments.first }
30
+ let(:node) { ast.items.find { |n| n.instance_of?(FlavourSaver::ExpressionNode) }.method.first.arguments.first }
31
31
 
32
- it 'returns the value of the string' do
33
- expect(subject.evaluate_node(node)).to eq 'WAT'
32
+ it "returns the value of the string" do
33
+ expect(subject.evaluate_node(node)).to eq "WAT"
34
34
  end
35
35
  end
36
36
 
37
- describe 'when passed an ExpressionNode' do
38
- let(:node) { ast.items.select { |n| n.class == FlavourSaver::ExpressionNode }.first }
39
- let (:template) { "{{foo}}" }
37
+ describe "when passed an ExpressionNode" do
38
+ let(:node) { ast.items.find { |n| n.instance_of?(FlavourSaver::ExpressionNode) } }
39
+ let(:template) { "{{foo}}" }
40
40
 
41
- it 'calls evaluate_node with the node' do
41
+ it "calls evaluate_node with the node" do
42
42
  expect(subject).to receive(:evaluate_expression).with(node)
43
43
  subject.evaluate_node(node)
44
44
  end
45
45
 
46
- it 'should HTML escape the output' do
46
+ it "should HTML escape the output" do
47
47
  expect(context).to receive(:foo).and_return("<html>LOL</html>")
48
- expect(subject.evaluate_node(node)).to eq '&lt;html&gt;LOL&lt;/html&gt;'
48
+ expect(subject.evaluate_node(node)).to eq "&lt;html&gt;LOL&lt;/html&gt;"
49
49
  end
50
50
  end
51
51
 
52
- describe 'when passed a SafeExpressionNode' do
53
- let(:node) { ast.items.select { |n| n.class == FlavourSaver::SafeExpressionNode }.first }
54
- let (:template) { "{{{foo}}}" }
52
+ describe "when passed a SafeExpressionNode" do
53
+ let(:node) { ast.items.find { |n| n.instance_of?(FlavourSaver::SafeExpressionNode) } }
54
+ let(:template) { "{{{foo}}}" }
55
55
 
56
- it 'should not HTML escape the output' do
56
+ it "should not HTML escape the output" do
57
57
  expect(context).to receive(:foo).and_return("<html>LOL</html>")
58
- expect(subject.evaluate_node(node)).to eq '<html>LOL</html>'
58
+ expect(subject.evaluate_node(node)).to eq "<html>LOL</html>"
59
59
  end
60
60
  end
61
61
 
62
- describe 'when passed a CommentNode' do
62
+ describe "when passed a CommentNode" do
63
63
  let(:template) { "{{! I am a comment}}" }
64
- let(:node) { ast.items.select { |n| n.class == FlavourSaver::CommentNode }.first }
64
+ let(:node) { ast.items.find { |n| n.instance_of?(FlavourSaver::CommentNode) } }
65
65
 
66
- it 'should return zilch' do
67
- expect(subject.evaluate_node(node)).to eq ''
66
+ it "should return zilch" do
67
+ expect(subject.evaluate_node(node)).to eq ""
68
68
  end
69
69
  end
70
70
 
71
- describe 'when passed a BlockExpressionStartNode' do
71
+ describe "when passed a BlockExpressionStartNode" do
72
72
  let(:template) { "{{#foo}}bar{{/foo}}baz" }
73
73
 
74
- it 'snatches up the block contents and skips them from evaluation' do
74
+ it "snatches up the block contents and skips them from evaluation" do
75
75
  allow(context).to receive(:foo)
76
- expect(subject.evaluate_node(ast)).to eq 'baz'
76
+ expect(subject.evaluate_node(ast)).to eq "baz"
77
77
  end
78
78
  end
79
79
  end
80
80
 
81
- describe '#evaluate_expression' do
82
- let(:node) { ast.items.select { |n| n.class == FlavourSaver::ExpressionNode }.first }
81
+ describe "#evaluate_expression" do
82
+ let(:node) { ast.items.find { |n| n.instance_of?(FlavourSaver::ExpressionNode) } }
83
83
  let(:expr) { node }
84
84
 
85
- describe 'when called with a simple method expression' do
86
- let (:template) { "{{foo}}" }
85
+ describe "when called with a simple method expression" do
86
+ let(:template) { "{{foo}}" }
87
87
 
88
- it 'calls the method and return the result' do
89
- expect(context).to receive(:foo).and_return('foo result')
90
- expect(subject.evaluate_expression(expr)).to eq 'foo result'
88
+ it "calls the method and return the result" do
89
+ expect(context).to receive(:foo).and_return("foo result")
90
+ expect(subject.evaluate_expression(expr)).to eq "foo result"
91
91
  end
92
92
  end
93
93
 
94
- describe 'when called with a simple method with arguments' do
94
+ describe "when called with a simple method with arguments" do
95
95
  let(:template) { "{{hello \"world\"}}" }
96
96
 
97
- it 'calls the method with the arguments and return the result' do
97
+ it "calls the method with the arguments and return the result" do
98
98
  expect(context).to receive(:hello).with("world").and_return("hello world")
99
- expect(subject.evaluate_expression(expr)).to eq 'hello world'
99
+ expect(subject.evaluate_expression(expr)).to eq "hello world"
100
100
  end
101
101
  end
102
102
 
103
- describe 'when called with a simple method with another simple method argument' do
103
+ describe "when called with a simple method with another simple method argument" do
104
104
  let(:template) { "{{hello world}}" }
105
105
 
106
- it 'calls hello & world on the context and return the result' do
107
- expect(context).to receive(:world).and_return('world')
108
- expect(context).to receive(:hello).with('world').and_return('hello world')
109
- expect(subject.evaluate_expression(expr)).to eq 'hello world'
106
+ it "calls hello & world on the context and return the result" do
107
+ expect(context).to receive(:world).and_return("world")
108
+ expect(context).to receive(:hello).with("world").and_return("hello world")
109
+ expect(subject.evaluate_expression(expr)).to eq "hello world"
110
110
  end
111
111
  end
112
112
 
113
- describe 'when called with a subexpression' do
113
+ describe "when called with a subexpression" do
114
114
  let(:template) { "{{hello (there world)}}" }
115
115
 
116
- it 'calls there & world first, then passes off to hello' do
117
- expect(context).to receive(:world).and_return('world')
118
- expect(context).to receive(:there).with('world').and_return('there world')
119
- expect(context).to receive(:hello).with('there world').and_return('hello there world')
120
- expect(subject.evaluate_expression(expr)).to eq 'hello there world'
116
+ it "calls there & world first, then passes off to hello" do
117
+ expect(context).to receive(:world).and_return("world")
118
+ expect(context).to receive(:there).with("world").and_return("there world")
119
+ expect(context).to receive(:hello).with("there world").and_return("hello there world")
120
+ expect(subject.evaluate_expression(expr)).to eq "hello there world"
121
121
  end
122
122
  end
123
123
 
124
- describe 'when called with an object path' do
124
+ describe "when called with an object path" do
125
125
  let(:template) { "{{hello.world}}" }
126
126
 
127
- it 'calls world on the result of hello' do
128
- allow(context).to receive_message_chain(:hello, :world).and_return('hello world')
129
- expect(subject.evaluate_expression(expr)).to eq 'hello world'
127
+ it "calls world on the result of hello" do
128
+ allow(context).to receive_message_chain(:hello, :world).and_return("hello world")
129
+ expect(subject.evaluate_expression(expr)).to eq "hello world"
130
130
  end
131
131
  end
132
132
 
133
- describe 'when called with a literal' do
133
+ describe "when called with a literal" do
134
134
  let(:template) { "{{[WAT]}}" }
135
135
 
136
- it 'indexes the context with WAT' do
137
- expect(context).to receive(:[]).with('WAT').and_return 'w00t'
138
- expect(subject.evaluate_expression(expr)).to eq 'w00t'
136
+ it "indexes the context with WAT" do
137
+ expect(context).to receive(:[]).with("WAT").and_return "w00t"
138
+ expect(subject.evaluate_expression(expr)).to eq "w00t"
139
139
  end
140
140
  end
141
141
 
142
- describe 'when called with an object path containing a literal' do
143
- let (:template) { "{{hello.[WAT].world}}" }
142
+ describe "when called with an object path containing a literal" do
143
+ let(:template) { "{{hello.[WAT].world}}" }
144
144
 
145
- it 'indexes the result of hello and calls world on it' do
145
+ it "indexes the result of hello and calls world on it" do
146
146
  world = double(:world)
147
- expect(world).to receive(:world).and_return('vr00m')
147
+ expect(world).to receive(:world).and_return("vr00m")
148
148
  hash = double(:hash)
149
- expect(hash).to receive(:[]).with('WAT').and_return(world)
149
+ expect(hash).to receive(:[]).with("WAT").and_return(world)
150
150
  expect(context).to receive(:hello).and_return(hash)
151
- expect(subject.evaluate_expression(expr)).to eq 'vr00m'
151
+ expect(subject.evaluate_expression(expr)).to eq "vr00m"
152
152
  end
153
153
  end
154
154
 
155
- describe 'when called with an path containing array indexes' do
156
- let (:template) { "{{hello.[0].[1]}}" }
155
+ describe "when called with an path containing array indexes" do
156
+ let(:template) { "{{hello.[0].[1]}}" }
157
157
 
158
- it 'indexes the value of a nested array' do
159
- array2 = ['w00t', 'vr00m']
158
+ it "indexes the value of a nested array" do
159
+ array2 = ["w00t", "vr00m"]
160
160
  array = [array2]
161
161
  expect(context).to receive(:hello).and_return(array)
162
- expect(subject.evaluate_expression(expr)).to eq 'vr00m'
162
+ expect(subject.evaluate_expression(expr)).to eq "vr00m"
163
163
  end
164
164
 
165
- it 'handles non-existent indexes' do
166
- world = ['w00t']
165
+ it "handles non-existent indexes" do
166
+ world = ["w00t"]
167
167
  array = [world]
168
168
  expect(context).to receive(:hello).and_return(array)
169
169
  expect(subject.evaluate_expression(expr)).to eq nil
170
170
  end
171
171
 
172
- it 'handles calling on nil values' do
172
+ it "handles calling on nil values" do
173
173
  expect(context).to receive(:hello).and_return(nil)
174
174
  expect(subject.evaluate_expression(expr)).to eq nil
175
175
  end
176
176
  end
177
177
 
178
- describe 'when called with a parent call node without a surrounding block' do
179
- let (:template) { "{{../foo}}" }
178
+ describe "when called with a parent call node without a surrounding block" do
179
+ let(:template) { "{{../foo}}" }
180
180
 
181
- it 'raises an error' do
181
+ it "raises an error" do
182
182
  expect { subject.evaluate_expression(expr) }.to raise_error(FlavourSaver::UnknownContextException)
183
183
  end
184
184
  end
185
185
 
186
- describe 'when called with a hash argument containing a string value' do
187
- let (:template) { '{{foo bar="baz"}}' }
186
+ describe "when called with a hash argument containing a string value" do
187
+ let(:template) { '{{foo bar="baz"}}' }
188
188
 
189
- it 'receives the argument as a hash' do
190
- expect(context).to receive(:foo).with({:bar => 'baz'})
189
+ it "receives the argument as a hash" do
190
+ expect(context).to receive(:foo).with({bar: "baz"})
191
191
  subject.evaluate_expression(expr)
192
192
  end
193
193
  end
194
194
 
195
- describe 'when called with a hash argument containing a method reference' do
196
- let (:template) { "{{foo bar=baz}}" }
195
+ describe "when called with a hash argument containing a method reference" do
196
+ let(:template) { "{{foo bar=baz}}" }
197
197
 
198
- it 'calls the value and returns it\'s result in the hash' do
199
- expect(context).to receive(:baz).and_return('OMGLOLWAT')
200
- expect(context).to receive(:foo).with({:bar => 'OMGLOLWAT'})
198
+ it "calls the value and returns it's result in the hash" do
199
+ expect(context).to receive(:baz).and_return("OMGLOLWAT")
200
+ expect(context).to receive(:foo).with({bar: "OMGLOLWAT"})
201
201
  subject.evaluate_expression(expr)
202
202
  end
203
203
  end
204
-
205
204
  end
206
205
 
207
- describe '#evaluate_block' do
206
+ describe "#evaluate_block" do
208
207
  let(:template) { "{{#foo}}hello world{{/foo}}" }
209
- let(:block) { ast.items.first }
208
+ let(:block) { ast.items.first }
210
209
 
211
- it 'creates a new runtime' do
210
+ it "creates a new runtime" do
212
211
  expect(subject).to receive(:create_child_runtime)
213
212
  allow(context).to receive(:foo)
214
213
  subject.evaluate_block(block, context)
215
214
  end
216
215
  end
217
216
 
218
- describe '#create_child_runtime' do
219
- it 'creates a new runtime' do
217
+ describe "#create_child_runtime" do
218
+ it "creates a new runtime" do
220
219
  expect(subject.create_child_runtime([])).to be_a(FlavourSaver::Runtime)
221
220
  end
222
221
  end
223
-
224
222
  end
@@ -1,5 +1,4 @@
1
- require 'flavour_saver'
1
+ require "flavour_saver"
2
2
 
3
3
  describe FlavourSaver::Template do
4
-
5
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flavour_saver
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clayton Passmore
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-08-12 00:00:00.000000000 Z
12
+ date: 2026-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -40,33 +40,33 @@ dependencies:
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
42
  - !ruby/object:Gem::Dependency
43
- name: activesupport
43
+ name: standard
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: 7.2.0
48
+ version: '0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: 7.2.0
55
+ version: '0'
56
56
  - !ruby/object:Gem::Dependency
57
- name: rltk
57
+ name: activesupport
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - "<"
60
+ - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '3.0'
63
- type: :runtime
62
+ version: 7.2.0
63
+ type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - "<"
67
+ - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '3.0'
69
+ version: 7.2.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: tilt
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +81,7 @@ dependencies:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '2.6'
84
- description: FlavourSaver is a pure-ruby implimentation of the Handlebars templating
84
+ description: FlavourSaver is a pure-ruby implementation of the Handlebars templating
85
85
  language
86
86
  email:
87
87
  - ctpassmore+flavoursaver@gmail.com
@@ -91,6 +91,7 @@ extra_rdoc_files: []
91
91
  files:
92
92
  - ".github/workflows/ci.yml"
93
93
  - ".gitignore"
94
+ - ".standard.yml"
94
95
  - CHANGELOG.md
95
96
  - Gemfile
96
97
  - Gemfile.lock
@@ -99,6 +100,7 @@ files:
99
100
  - Rakefile
100
101
  - flavour_saver.gemspec
101
102
  - lib/flavour_saver.rb
103
+ - lib/flavour_saver/error.rb
102
104
  - lib/flavour_saver/helpers.rb
103
105
  - lib/flavour_saver/lexer.rb
104
106
  - lib/flavour_saver/nodes.rb
@@ -116,6 +118,7 @@ files:
116
118
  - spec/acceptance/handlebars_qunit_spec.rb
117
119
  - spec/acceptance/if_else_spec.rb
118
120
  - spec/acceptance/multi_level_with_spec.rb
121
+ - spec/acceptance/nesting_depth_spec.rb
119
122
  - spec/acceptance/one_character_identifier_spec.rb
120
123
  - spec/acceptance/raw_block_spec.rb
121
124
  - spec/acceptance/runtime_run_spec.rb
@@ -138,6 +141,7 @@ files:
138
141
  - spec/fixtures/simple_expression.hbs
139
142
  - spec/fixtures/unless.hbs
140
143
  - spec/lib/flavour_saver/lexer_spec.rb
144
+ - spec/lib/flavour_saver/nodes_spec.rb
141
145
  - spec/lib/flavour_saver/parser_spec.rb
142
146
  - spec/lib/flavour_saver/runtime_spec.rb
143
147
  - spec/lib/flavour_saver/template_spec.rb
@@ -168,37 +172,4 @@ rubygems_version: 3.3.27
168
172
  signing_key:
169
173
  specification_version: 4
170
174
  summary: Handlebars.js without the .js
171
- test_files:
172
- - spec/acceptance/backtrack_spec.rb
173
- - spec/acceptance/comment_spec.rb
174
- - spec/acceptance/custom_block_helper_spec.rb
175
- - spec/acceptance/custom_helper_spec.rb
176
- - spec/acceptance/ensure_no_rce_spec.rb
177
- - spec/acceptance/handlebars_qunit_spec.rb
178
- - spec/acceptance/if_else_spec.rb
179
- - spec/acceptance/multi_level_with_spec.rb
180
- - spec/acceptance/one_character_identifier_spec.rb
181
- - spec/acceptance/raw_block_spec.rb
182
- - spec/acceptance/runtime_run_spec.rb
183
- - spec/acceptance/sections_spec.rb
184
- - spec/acceptance/segment_literals_spec.rb
185
- - spec/acceptance/simple_expression_spec.rb
186
- - spec/acceptance/subexpression_spec.rb
187
- - spec/acceptance/unless_spec.rb
188
- - spec/fixtures/backtrack.hbs
189
- - spec/fixtures/comment.hbs
190
- - spec/fixtures/custom_block_helper.hbs
191
- - spec/fixtures/custom_helper.hbs
192
- - spec/fixtures/if_else.hbs
193
- - spec/fixtures/multi_level_if.hbs
194
- - spec/fixtures/multi_level_with.hbs
195
- - spec/fixtures/one_character_identifier.hbs
196
- - spec/fixtures/raw.hbs
197
- - spec/fixtures/rce.hbs
198
- - spec/fixtures/sections.hbs
199
- - spec/fixtures/simple_expression.hbs
200
- - spec/fixtures/unless.hbs
201
- - spec/lib/flavour_saver/lexer_spec.rb
202
- - spec/lib/flavour_saver/parser_spec.rb
203
- - spec/lib/flavour_saver/runtime_spec.rb
204
- - spec/lib/flavour_saver/template_spec.rb
175
+ test_files: []