flavour_saver 1.0.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2874ca7f6ce28bbd9ed5f1ba887a0bc7244bdd448be995c2447078439e32bb82
4
- data.tar.gz: cdae84ceb24202a2f03db262a973f254dc750a3de30efae7138e01a3ef6187cb
3
+ metadata.gz: 003f6b787316f313dc1a044deb52b4fcc9a87ef0b00819784ab364dca840d567
4
+ data.tar.gz: 5b9fd6980a44472f106e7e37b046d5abc896198f169896dedb8b58f41416986d
5
5
  SHA512:
6
- metadata.gz: 404531d6a27b3c6e8739d98dc7ab9b5af81e8b29cc9260869784882704b58b4cd7ab2e2f3e741768cbe5386c326c66e1fc1be66d3ab1f6702a2902b9ee811017
7
- data.tar.gz: b5ae4b6c36462dfc80a29e6eb25cca006330462aa44b1d38dd234e353a072ee906ac41943201ab5cce233b3812f14da697be6b29a189b4bf7b8820a98afecc7e
6
+ metadata.gz: 243f501f9280e5394bcb861bf1689e365a9e43b925ab332fad4e1050c8c2aad9e03a6d76d86fb41e9273d4f935e2775fa7d2d5119b7cd938772daaa4ce6e9271
7
+ data.tar.gz: 936b3241cd8bad53c819e02bfc6cac334433207ead739628eaeafd619d36544c60f71da9b6a888c218a83afb292b639eb8246b07efed84d4d436868936950c7a
@@ -3,6 +3,7 @@ name: Continuous integration
3
3
  on:
4
4
  push:
5
5
  branches: [ master ]
6
+ tags: [ 'v*' ]
6
7
  pull_request:
7
8
  branches: [ master ]
8
9
 
@@ -24,3 +25,23 @@ jobs:
24
25
  - name: Run tests
25
26
  run: bundle exec rspec
26
27
 
28
+ publish-gem:
29
+ if: github.ref_type == 'tag'
30
+ runs-on: ubuntu-latest
31
+ needs: [test]
32
+ steps:
33
+ - uses: actions/checkout@v2
34
+ - name: Set up Ruby 2.7
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: 2.7
38
+ - name: Publish to RubyGems
39
+ run: |
40
+ mkdir -p $HOME/.gem
41
+ touch $HOME/.gem/credentials
42
+ chmod 0600 $HOME/.gem/credentials
43
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
44
+ gem build *.gemspec
45
+ gem push *.gem
46
+ env:
47
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.0]
11
+
12
+ ### Breaking Changes
13
+
14
+ * The #if and #unless helpers now treat zero as falsey (#54, #55)
15
+ * The #unless helper now treats empty objects as falsey (#55)
16
+ * Using #each on a hash now yields the value instead of an array of key, value (#52)
17
+
18
+ ### Added
19
+
20
+ * Support for @root object (#49)
21
+ * Support for @key object inside #each blocks with Hashes (#52)
22
+
23
+ ### Fixed
24
+
25
+ * Lex number literals that start with 0 (#53)
26
+
10
27
  ## [1.0.0] - 2022-01-19
11
28
 
12
29
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flavour_saver (1.0.0)
4
+ flavour_saver (2.0.0)
5
5
  rltk (~> 2.2.0)
6
6
  tilt
7
7
 
@@ -11,27 +11,31 @@ module FlavourSaver
11
11
  r = []
12
12
  count = 0
13
13
  collection.each do |element|
14
- r << yield.contents(element,
15
- 'index' => count,
16
- 'last' => count == collection.size - 1,
17
- 'first' => count == 0)
14
+ locals ={
15
+ 'index' => count,
16
+ 'last' => count == collection.size - 1,
17
+ 'first' => count == 0
18
+ }
19
+
20
+ locals['key'], element = element if collection.is_a?(Hash)
21
+
22
+ r << yield.contents(element, locals)
18
23
  count += 1
19
24
  end
20
25
  yield.rendered!
21
26
  r.join ''
22
27
  end
23
28
 
24
- def if(truthy)
25
- truthy = false if truthy.respond_to?(:size) && (truthy.size == 0)
26
- if truthy
29
+ def if(value)
30
+ if truthy?(value)
27
31
  yield.contents
28
32
  else
29
33
  yield.inverse
30
34
  end
31
35
  end
32
36
 
33
- def unless(falsy,&b)
34
- self.if(!falsy,&b)
37
+ def unless(value, &b)
38
+ self.if(!truthy?(value), &b)
35
39
  end
36
40
 
37
41
  def this
@@ -42,6 +46,16 @@ module FlavourSaver
42
46
  FS.logger.debug("FlavourSaver: #{message}")
43
47
  ''
44
48
  end
49
+
50
+ private
51
+
52
+ # Think of this as a compatability layer to make Ruby conditionals
53
+ # behave like JavaScript conditionals.
54
+ def truthy?(value)
55
+ value = false if value.respond_to?(:size) && (value.size == 0)
56
+ value = false if value.respond_to?(:zero?) && value.zero?
57
+ value
58
+ end
45
59
  end
46
60
 
47
61
  class Decorator < Defaults
@@ -107,10 +121,10 @@ module FlavourSaver
107
121
 
108
122
  def decorate_with(context, helper_names=[], locals={})
109
123
  helpers = if helper_names.any?
110
- helper_names = helper_names.map(&:to_sym)
111
- registered_helpers.select { |k,v| helper_names.member? k }.merge(locals)
124
+ helper_symbols = helper_names.map(&:to_sym)
125
+ registered_helpers.select { |k,v| helper_symbols.member? k }
112
126
  else
113
- helpers = registered_helpers
127
+ registered_helpers
114
128
  end
115
129
  helpers = helpers.merge(locals)
116
130
  Decorator.new(helpers, context)
@@ -52,7 +52,7 @@ module FlavourSaver
52
52
  :GT
53
53
  end
54
54
 
55
- rule /([1-9][0-9]*(\.[0-9]+)?)/, :expression do |n|
55
+ rule /([0-9]+(\.[0-9]+)?)/, :expression do |n|
56
56
  [ :NUMBER, n ]
57
57
  end
58
58
 
@@ -115,7 +115,6 @@ module FlavourSaver
115
115
 
116
116
  production(:expression_contents) do
117
117
  clause('WHITE? call WHITE?') { |_,e,_| e }
118
- clause('WHITE? local WHITE?') { |_,e,_| [e] }
119
118
  end
120
119
 
121
120
  production(:call) do
@@ -124,17 +123,13 @@ module FlavourSaver
124
123
  clause('DOT') { |_| [CallNode.new('this', [])] }
125
124
  end
126
125
 
127
- production(:local) do
128
- clause('AT IDENT') { |_,e| LocalVarNode.new(e) }
129
- end
130
-
131
126
  production('arguments') do
132
127
  clause('argument_list') { |e| e }
133
128
  clause('argument_list WHITE hash') { |e0,_,e1| e0 + [e1] }
134
129
  clause('hash') { |e| [e] }
135
130
  end
136
131
 
137
- nonempty_list(:argument_list, [:object_path,:lit, :local, :subexpr], :WHITE)
132
+ nonempty_list(:argument_list, [:object_path,:lit, :subexpr], :WHITE)
138
133
 
139
134
  production(:lit) do
140
135
  clause('string') { |e| e }
@@ -175,6 +170,7 @@ module FlavourSaver
175
170
  nonempty_list(:object_path, :object, :object_sep)
176
171
 
177
172
  production(:object) do
173
+ clause('AT IDENT') { |_,e| LocalVarNode.new(e) }
178
174
  clause('IDENT') { |e| CallNode.new(e, []) }
179
175
  clause('LITERAL') { |e| LiteralCallNode.new(e, []) }
180
176
  clause('parent_call') { |e| e }
@@ -8,7 +8,7 @@ module FlavourSaver
8
8
  UnknownHelperException = Class.new(RuntimeError)
9
9
  class Runtime
10
10
 
11
- attr_accessor :context, :parent, :ast
11
+ attr_accessor :context, :parent, :ast, :privates
12
12
 
13
13
  def self.run(ast, context, locals={}, helpers=[])
14
14
  self.new(ast, context, locals, helpers).to_s
@@ -20,7 +20,7 @@ module FlavourSaver
20
20
  @helpers = helpers
21
21
  @context = context
22
22
  @parent = parent
23
- @privates = {}
23
+ @privates = parent ? parent.privates.dup : { 'root' => context }
24
24
  end
25
25
 
26
26
  def to_s(tmp_context = nil,privates={})
@@ -1,3 +1,3 @@
1
1
  module FlavourSaver
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -909,6 +909,17 @@ describe FlavourSaver do
909
909
  end
910
910
  end
911
911
 
912
+ describe 'each with @index in a nested block' do
913
+ let(:template) { "{{#each goodbyes}}{{#if @last}}{{@index}}. {{/if}}{{text}}! {{/each}}cruel {{world}}!" }
914
+
915
+ example 'the @index variable is used' do
916
+ g = Struct.new(:text)
917
+ allow(context).to receive(:goodbyes).and_return([g.new('goodbye'), g.new('Goodbye'), g.new('GOODBYE')])
918
+ allow(context).to receive(:world).and_return('world')
919
+ expect(subject).to eq "goodbye! Goodbye! 2. GOODBYE! cruel world!"
920
+ end
921
+ end
922
+
912
923
  describe 'each with @last' do
913
924
  let(:template) { "{{#each goodbyes}}{{@index}}. {{text}}! {{#if @last}}last{{/if}}{{/each}} cruel {{world}}!" }
914
925
 
@@ -931,6 +942,58 @@ describe FlavourSaver do
931
942
  end
932
943
  end
933
944
 
945
+ describe 'each with @root' do
946
+ let(:template) { "{{#each goodbyes}}{{@index}}. {{text}} cruel {{@root.place}}! {{/each}}" }
947
+
948
+ example 'the root variable is used' do
949
+ g = Struct.new(:text)
950
+ allow(context).to receive(:goodbyes).and_return([g.new('goodbye'), g.new('Goodbye'), g.new('GOODBYE')])
951
+ allow(context).to receive(:place).and_return('world')
952
+ expect(subject).to eq "0. goodbye cruel world! 1. Goodbye cruel world! 2. GOODBYE cruel world! "
953
+ end
954
+ end
955
+
956
+ describe 'each with @root in a nested block' do
957
+ let(:template) { "{{#each goodbyes}}{{@index}}. {{text}}{{#if @last}} cruel {{@root.place}}{{/if}}! {{/each}}" }
958
+
959
+ example 'the root variable is used' do
960
+ g = Struct.new(:text)
961
+ allow(context).to receive(:goodbyes).and_return([g.new('goodbye'), g.new('Goodbye'), g.new('GOODBYE')])
962
+ allow(context).to receive(:place).and_return('world')
963
+ expect(subject).to eq "0. goodbye! 1. Goodbye! 2. GOODBYE cruel world! "
964
+ end
965
+ end
966
+
967
+ describe 'each with @key' do
968
+ let(:template) { "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!" }
969
+
970
+ example 'the @key variable is used' do
971
+ g = Struct.new(:text)
972
+ allow(context).to receive(:goodbyes).and_return({
973
+ one: g.new('goodbye'),
974
+ two: g.new('Goodbye'),
975
+ three: g.new('GOODBYE')
976
+ })
977
+ allow(context).to receive(:world).and_return('world')
978
+ expect(subject).to eq "one. goodbye! two. Goodbye! three. GOODBYE! cruel world!"
979
+ end
980
+ end
981
+
982
+ describe 'each with @key in a nested block' do
983
+ let(:template) { "{{#each goodbyes}}{{#if @last}}{{@key}}. {{/if}}{{text}}! {{/each}}cruel {{world}}!" }
984
+
985
+ example 'the @key variable is used' do
986
+ g = Struct.new(:text)
987
+ allow(context).to receive(:goodbyes).and_return({
988
+ one: g.new('goodbye'),
989
+ two: g.new('Goodbye'),
990
+ three: g.new('GOODBYE')
991
+ })
992
+ allow(context).to receive(:world).and_return('world')
993
+ expect(subject).to eq "goodbye! Goodbye! three. GOODBYE! cruel world!"
994
+ end
995
+ end
996
+
934
997
  describe 'log' do
935
998
  let(:template) { "{{log blah}}" }
936
999
  let(:log) { double(:log) }
@@ -3,15 +3,46 @@ require 'flavour_saver'
3
3
 
4
4
  describe 'Fixture: if_else.hbs' do
5
5
  subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
- let(:context) { Struct.new(:name).new }
6
+ let(:context) { Struct.new(:value).new }
7
7
  let(:template) { File.expand_path('../../fixtures/if_else.hbs', __FILE__) }
8
8
 
9
- it 'renders correctly when given a name' do
10
- context.name = 'Alan'
11
- expect(subject).to eq "Say hello to Alan."
9
+ it "renders the if block when given a string" do
10
+ context.value = "Alan"
11
+ expect(subject).to eq "The given value is truthy: Alan."
12
12
  end
13
13
 
14
- it 'renders correctly when not given a name' do
15
- expect(subject).to eq "Nobody to say hi to."
14
+ it "renders the if block when given a number greater than zero" do
15
+ context.value = 1
16
+ expect(subject).to eq "The given value is truthy: 1."
17
+ end
18
+
19
+ it "renders the if block when given an array that is not empty" do
20
+ context.value = [1]
21
+ expect(subject).to eq "The given value is truthy: [1]."
22
+ end
23
+
24
+ it "renders the else block when given false" do
25
+ context.value = false
26
+ expect(subject).to eq "The given value is falsy: false."
27
+ end
28
+
29
+ it 'renders the else block when given nil' do
30
+ context.value = nil
31
+ expect(subject).to eq "The given value is falsy: ."
32
+ end
33
+
34
+ it "renders the else block when given an empty string" do
35
+ context.value = ""
36
+ expect(subject).to eq "The given value is falsy: ."
37
+ end
38
+
39
+ it "renders the else block when given a zero" do
40
+ context.value = 0
41
+ expect(subject).to eq "The given value is falsy: 0."
42
+ end
43
+
44
+ it "renders the else block when given an empty array" do
45
+ context.value = []
46
+ expect(subject).to eq "The given value is falsy: []."
16
47
  end
17
48
  end
@@ -0,0 +1,48 @@
1
+ require 'tilt'
2
+ require 'flavour_saver'
3
+
4
+ describe 'Fixture: unless.hbs' do
5
+ subject { Tilt.new(template).render(context).gsub(/[\s\r\n]+/, ' ').strip }
6
+ let(:context) { Struct.new(:value).new }
7
+ let(:template) { File.expand_path('../../fixtures/unless.hbs', __FILE__) }
8
+
9
+ it "renders the unless block when given false" do
10
+ context.value = false
11
+ expect(subject).to eq "The given value is falsy: false."
12
+ end
13
+
14
+ it 'renders the unless block when given nil' do
15
+ context.value = nil
16
+ expect(subject).to eq "The given value is falsy: ."
17
+ end
18
+
19
+ it "renders the unless block when given an empty string" do
20
+ context.value = ""
21
+ expect(subject).to eq "The given value is falsy: ."
22
+ end
23
+
24
+ it "renders the unless block when given a zero" do
25
+ context.value = 0
26
+ expect(subject).to eq "The given value is falsy: 0."
27
+ end
28
+
29
+ it "renders the unless block when given an empty array" do
30
+ context.value = []
31
+ expect(subject).to eq "The given value is falsy: []."
32
+ end
33
+
34
+ it "renders the else block when given a string" do
35
+ context.value = "Alan"
36
+ expect(subject).to eq "The given value is truthy: Alan."
37
+ end
38
+
39
+ it "renders the else block when given a number greater than zero" do
40
+ context.value = 1
41
+ expect(subject).to eq "The given value is truthy: 1."
42
+ end
43
+
44
+ it "renders the else block when given an array that is not empty" do
45
+ context.value = [1]
46
+ expect(subject).to eq "The given value is truthy: [1]."
47
+ end
48
+ end
@@ -1,5 +1,5 @@
1
- {{#if name}}
2
- Say hello to {{name}}.
1
+ {{#if value}}
2
+ The given value is truthy: {{value}}.
3
3
  {{else}}
4
- Nobody to say hi to.
4
+ The given value is falsy: {{value}}.
5
5
  {{/if}}
@@ -0,0 +1,5 @@
1
+ {{#unless value}}
2
+ The given value is falsy: {{value}}.
3
+ {{else}}
4
+ The given value is truthy: {{value}}.
5
+ {{/unless}}
@@ -71,7 +71,38 @@ describe FlavourSaver::Lexer do
71
71
  it 'has values in the correct order' do
72
72
  expect(subject.map(&:value).compact).to eq [ 'foo', 'bar', 'baz', 'hello', 'goodbye' ]
73
73
  end
74
+ end
75
+
76
+ describe '{{0}}' do
77
+ subject { FlavourSaver::Lexer.lex "{{0}}" }
78
+
79
+ it 'properly lexes the expression' do
80
+ expect(subject.map(&:type)).to eq(
81
+ [:EXPRST, :NUMBER, :EXPRE, :EOS]
82
+ )
83
+ end
74
84
 
85
+ it 'contains only the number "0"' do
86
+ expect(subject[1..-3].size).to eq 1
87
+ expect(subject[1].type).to eq :NUMBER
88
+ expect(subject[1].value).to eq '0'
89
+ end
90
+ end
91
+
92
+ describe '{{0.0123456789}}' do
93
+ subject { FlavourSaver::Lexer.lex "{{0.0123456789}}" }
94
+
95
+ it 'properly lexes the expression' do
96
+ expect(subject.map(&:type)).to eq(
97
+ [:EXPRST, :NUMBER, :EXPRE, :EOS]
98
+ )
99
+ end
100
+
101
+ it 'contains only the number "0.0123456789"' do
102
+ expect(subject[1..-3].size).to eq 1
103
+ expect(subject[1].type).to eq :NUMBER
104
+ expect(subject[1].value).to eq '0.0123456789'
105
+ end
75
106
  end
76
107
  end
77
108
 
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flavour_saver
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clayton Passmore
8
8
  - James Harton
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-01-21 00:00:00.000000000 Z
12
+ date: 2022-02-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -123,6 +123,7 @@ files:
123
123
  - spec/acceptance/segment_literals_spec.rb
124
124
  - spec/acceptance/simple_expression_spec.rb
125
125
  - spec/acceptance/subexpression_spec.rb
126
+ - spec/acceptance/unless_spec.rb
126
127
  - spec/fixtures/backtrack.hbs
127
128
  - spec/fixtures/comment.hbs
128
129
  - spec/fixtures/custom_block_helper.hbs
@@ -134,6 +135,7 @@ files:
134
135
  - spec/fixtures/raw.hbs
135
136
  - spec/fixtures/sections.hbs
136
137
  - spec/fixtures/simple_expression.hbs
138
+ - spec/fixtures/unless.hbs
137
139
  - spec/lib/flavour_saver/lexer_spec.rb
138
140
  - spec/lib/flavour_saver/parser_spec.rb
139
141
  - spec/lib/flavour_saver/runtime_spec.rb
@@ -146,7 +148,7 @@ metadata:
146
148
  changelog_uri: http://github.com/FlavourSaver/FlavourSaver/blob/master/CHANGELOG.md
147
149
  homepage_uri: http://github.com/FlavourSaver/FlavourSaver
148
150
  source_code_uri: http://github.com/FlavourSaver/FlavourSaver
149
- post_install_message:
151
+ post_install_message:
150
152
  rdoc_options: []
151
153
  require_paths:
152
154
  - lib
@@ -161,8 +163,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
163
  - !ruby/object:Gem::Version
162
164
  version: '0'
163
165
  requirements: []
164
- rubygems_version: 3.2.15
165
- signing_key:
166
+ rubygems_version: 3.1.6
167
+ signing_key:
166
168
  specification_version: 4
167
169
  summary: Handlebars.js without the .js
168
170
  test_files:
@@ -181,6 +183,7 @@ test_files:
181
183
  - spec/acceptance/segment_literals_spec.rb
182
184
  - spec/acceptance/simple_expression_spec.rb
183
185
  - spec/acceptance/subexpression_spec.rb
186
+ - spec/acceptance/unless_spec.rb
184
187
  - spec/fixtures/backtrack.hbs
185
188
  - spec/fixtures/comment.hbs
186
189
  - spec/fixtures/custom_block_helper.hbs
@@ -192,6 +195,7 @@ test_files:
192
195
  - spec/fixtures/raw.hbs
193
196
  - spec/fixtures/sections.hbs
194
197
  - spec/fixtures/simple_expression.hbs
198
+ - spec/fixtures/unless.hbs
195
199
  - spec/lib/flavour_saver/lexer_spec.rb
196
200
  - spec/lib/flavour_saver/parser_spec.rb
197
201
  - spec/lib/flavour_saver/runtime_spec.rb