flavour_saver 1.0.0 → 2.0.0

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