namo 0.20.0 → 0.21.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: ae1731b55ec8d0de8dedfb35fc643f6fb6a68e72bb3464c4139242c1bc6bb91e
4
- data.tar.gz: 161a1842a4daf8a4365a8420600f2824f5f15fd2ea3951e18d00d811eec22010
3
+ metadata.gz: 07d3c62244d72800b8f533d4618c8f51b4161338c37dc439ad26f67557a7315f
4
+ data.tar.gz: 7b8c6c808068b97555bb8f98a1d1ecb55a399e16fc51e475b61f13cbad4f0cef
5
5
  SHA512:
6
- metadata.gz: 4a563c97ca83b91e578375af09e533f633d2ed2d6bca1152297d3bf4b24dc60c5f25a5679e0e87023e3031f5019b3c32e825f35f23b75b0259cee6fb765a02b5
7
- data.tar.gz: 140f9b808a8f406ca1ca16611ab776544c332830353f1d9dba58d4cf3578ff746d2ff7788671fd9eda0c082d6cc3f1457f29ee38473d88ec2c1f0aa457a0dace
6
+ metadata.gz: 58db7e089c9a6be6e9d050a549cf5df98983227d3a3d218d0d5f61ee2684009a1af5f4069cb16376fe8211425060fe2b1d609fb5b6770236726782fa57a35b20
7
+ data.tar.gz: a4327a3484ffc1b30755ed38f94c2e432f0d6e338f5348a75b854c1cdebcfedebab4a742441d5e542c832d4a0aa517dc67a51f8b926462f2c0ab4ecad4a990d8
data/CHANGELOG CHANGED
@@ -1,6 +1,31 @@
1
1
  CHANGELOG
2
2
  _________
3
3
 
4
+ 20260625
5
+ 0.21.0: + Namo::Formulae; @formulae: /Hash/Namo::Formulae/. Behaviour-preserving: no new capability, just an object for the formula collection to accrete behaviour onto later, mirroring the 0.1.0 extraction of Row.
6
+
7
+ 1. + lib/Namo/Formulae.rb: + Namo::Formulae, a thin class wrapping a Hash @store.
8
+ Responds to exactly the subset of Hash that lib/namo.rb and Row send the formula
9
+ collection: [], []=, keys, key?, empty?, each, delete, merge, reject, dup; includes
10
+ Enumerable (each yields [name, callable] pairs, giving map/select/etc. for free).
11
+ Three deliberate choices: to_h returns a copy (@store.dup) so callers cannot mutate
12
+ internals; ==/eql?/hash are names-only (keys.sort), matching Namo's own formula
13
+ equality where two sets with the same names but different proc objects are equivalent;
14
+ merge/reject/dup return a Formulae, not a Hash.
15
+ 2. ~ lib/namo.rb: + the require; initialize now normalises the incoming formulae to a
16
+ Formulae (Formulae.new(formulae) unless already one). This is the single behavioural
17
+ seam — every existing internal construction passes a Hash and is wrapped, while
18
+ operator results that now pass a Formulae (merge/dup return Formulae) pass through.
19
+ No other call site changed: each sends a message Formulae answers.
20
+ 3. ~ test/namo_test.rb: the two assertions comparing the public #formulae accessor to a
21
+ literal {} now assert emptiness (must_be_empty). The accessor returns a Formulae, not a
22
+ Hash; the formula set being empty is the unchanged behaviour these tests pin. + two
23
+ construction tests pinning the normalisation seam: a Hash given by keyword is wrapped in
24
+ a Formulae, and a Formulae given by keyword passes through unchanged (same object).
25
+ 4. + test/Namo/Formulae_test.rb: + unit test pinning the three deliberate choices (to_h
26
+ copy, names-only ==/eql?/hash, the Hash-compatible surface plus Enumerable map/select).
27
+ 5. ~ Namo::VERSION: /0.20.0/0.21.0/
28
+
4
29
  20260615
5
30
  0.20.0: + group_by(dimension) — splits a Namo into a Namo::Collection, one member per distinct value, completing the Enumerable coherence pass begun at 0.11.0.
6
31
 
@@ -0,0 +1,71 @@
1
+ # Namo/Formulae.rb
2
+ # Namo::Formulae
3
+
4
+ class Namo
5
+ class Formulae
6
+ include Enumerable
7
+
8
+ def [](name)
9
+ @store[name]
10
+ end
11
+
12
+ def []=(name, callable)
13
+ @store[name] = callable
14
+ end
15
+
16
+ def keys
17
+ @store.keys
18
+ end
19
+
20
+ def key?(name)
21
+ @store.key?(name)
22
+ end
23
+
24
+ def empty?
25
+ @store.empty?
26
+ end
27
+
28
+ def each(&block)
29
+ return enum_for(:each) unless block_given?
30
+ @store.each(&block)
31
+ end
32
+
33
+ def delete(name)
34
+ @store.delete(name)
35
+ end
36
+
37
+ def merge(other)
38
+ self.class.new(@store.merge(other.to_h))
39
+ end
40
+
41
+ def reject(&block)
42
+ self.class.new(@store.reject(&block))
43
+ end
44
+
45
+ def dup
46
+ self.class.new(@store.dup)
47
+ end
48
+
49
+ def to_h
50
+ @store.dup
51
+ end
52
+
53
+ def ==(other)
54
+ other.is_a?(Formulae) && keys.sort == other.keys.sort
55
+ end
56
+
57
+ def eql?(other)
58
+ self == other
59
+ end
60
+
61
+ def hash
62
+ keys.sort.hash
63
+ end
64
+
65
+ private
66
+
67
+ def initialize(store = {})
68
+ @store = store
69
+ end
70
+ end
71
+ end
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.20.0'
5
+ VERSION = '0.21.0'
6
6
  end
data/lib/namo.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  # Namo
3
3
 
4
4
  require_relative './Namo/NegatedDimension'
5
+ require_relative './Namo/Formulae'
5
6
  require_relative './Namo/Row'
6
7
  require_relative './Namo/Collection'
7
8
  require_relative './Namo/Enumerable'
@@ -235,7 +236,7 @@ class Namo
235
236
 
236
237
  def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
237
238
  @data = positional_data || data
238
- @formulae = formulae
239
+ @formulae = formulae.is_a?(Formulae) ? formulae : Formulae.new(formulae)
239
240
  @name = name
240
241
  end
241
242
 
@@ -0,0 +1,163 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest-spec-context'
3
+
4
+ require_relative '../../lib/namo'
5
+
6
+ describe Namo::Formulae do
7
+ let(:revenue) do
8
+ proc{|r| r[:price] * r[:quantity]}
9
+ end
10
+
11
+ let(:cost) do
12
+ proc{|r| r[:quantity] * 4.0}
13
+ end
14
+
15
+ let(:formulae) do
16
+ Namo::Formulae.new({revenue: revenue})
17
+ end
18
+
19
+ describe "#[]" do
20
+ it "returns the callable stored under a name" do
21
+ _(formulae[:revenue]).must_be_same_as revenue
22
+ end
23
+
24
+ it "returns nil for an absent name" do
25
+ _(formulae[:missing]).must_be_nil
26
+ end
27
+ end
28
+
29
+ describe "#[]=" do
30
+ it "stores a callable under a name" do
31
+ formulae[:cost] = cost
32
+ _(formulae[:cost]).must_be_same_as cost
33
+ end
34
+ end
35
+
36
+ describe "#keys" do
37
+ it "returns the stored names" do
38
+ _(formulae.keys).must_equal [:revenue]
39
+ end
40
+ end
41
+
42
+ describe "#key?" do
43
+ it "is true for a stored name" do
44
+ _(formulae.key?(:revenue)).must_equal true
45
+ end
46
+
47
+ it "is false for an absent name" do
48
+ _(formulae.key?(:missing)).must_equal false
49
+ end
50
+ end
51
+
52
+ describe "#empty?" do
53
+ it "is true with no formulae" do
54
+ _(Namo::Formulae.new).must_be_empty
55
+ end
56
+
57
+ it "is false with formulae" do
58
+ _(formulae.empty?).must_equal false
59
+ end
60
+ end
61
+
62
+ describe "#each" do
63
+ it "yields name/callable pairs" do
64
+ yielded = {}
65
+ formulae.each{|name, callable| yielded[name] = callable}
66
+ _(yielded).must_equal({revenue: revenue})
67
+ end
68
+
69
+ it "returns an enumerator with no block" do
70
+ _(formulae.each).must_be_kind_of Enumerator
71
+ end
72
+ end
73
+
74
+ describe "#delete" do
75
+ it "removes a name" do
76
+ formulae.delete(:revenue)
77
+ _(formulae.key?(:revenue)).must_equal false
78
+ end
79
+ end
80
+
81
+ describe "#merge" do
82
+ it "returns a Formulae combining both sets" do
83
+ merged = formulae.merge(Namo::Formulae.new({cost: cost}))
84
+ _(merged).must_be_kind_of Namo::Formulae
85
+ _(merged.keys.sort).must_equal [:cost, :revenue]
86
+ end
87
+
88
+ it "lets the argument win on a name conflict" do
89
+ other = proc{|r| 0}
90
+ merged = formulae.merge(Namo::Formulae.new({revenue: other}))
91
+ _(merged[:revenue]).must_be_same_as other
92
+ end
93
+
94
+ it "does not mutate the receiver" do
95
+ formulae.merge(Namo::Formulae.new({cost: cost}))
96
+ _(formulae.keys).must_equal [:revenue]
97
+ end
98
+ end
99
+
100
+ describe "#reject" do
101
+ it "returns a Formulae without the rejected names" do
102
+ formulae[:cost] = cost
103
+ rejected = formulae.reject{|name, _| name == :cost}
104
+ _(rejected).must_be_kind_of Namo::Formulae
105
+ _(rejected.keys).must_equal [:revenue]
106
+ end
107
+ end
108
+
109
+ describe "#dup" do
110
+ it "returns an independent Formulae" do
111
+ copy = formulae.dup
112
+ copy[:cost] = cost
113
+ _(formulae.key?(:cost)).must_equal false
114
+ end
115
+ end
116
+
117
+ describe "#to_h" do
118
+ it "returns a copy, not the live store" do
119
+ hash = formulae.to_h
120
+ hash[:cost] = cost
121
+ _(formulae.key?(:cost)).must_equal false
122
+ end
123
+ end
124
+
125
+ describe "Enumerable" do
126
+ it "supports map over name/callable pairs" do
127
+ _(formulae.map{|name, _| name}).must_equal [:revenue]
128
+ end
129
+
130
+ it "supports select returning name/callable pairs" do
131
+ formulae[:cost] = cost
132
+ _(formulae.select{|name, _| name == :cost}).must_equal [[:cost, cost]]
133
+ end
134
+ end
135
+
136
+ describe "value semantics" do
137
+ it "is == when names match, regardless of proc objects" do
138
+ _(Namo::Formulae.new({revenue: revenue})).must_equal Namo::Formulae.new({revenue: proc{|r| 0}})
139
+ end
140
+
141
+ it "is not == when names differ" do
142
+ _(formulae).wont_equal Namo::Formulae.new({cost: cost})
143
+ end
144
+
145
+ it "is not == to a bare Hash" do
146
+ _(formulae).wont_equal({revenue: revenue})
147
+ end
148
+
149
+ it "is eql? on matching names with differing procs" do
150
+ _(Namo::Formulae.new({revenue: revenue}).eql?(Namo::Formulae.new({revenue: proc{|r| 0}}))).must_equal true
151
+ end
152
+
153
+ it "hashes equal on matching names with differing procs" do
154
+ _(Namo::Formulae.new({revenue: revenue}).hash).must_equal Namo::Formulae.new({revenue: proc{|r| 0}}).hash
155
+ end
156
+ end
157
+
158
+ describe "constructor" do
159
+ it "defaults to an empty store" do
160
+ _(Namo::Formulae.new.keys).must_equal []
161
+ end
162
+ end
163
+ end
data/test/namo_test.rb CHANGED
@@ -47,7 +47,7 @@ describe Namo do
47
47
  it "produces an empty Namo with no arguments" do
48
48
  namo = Namo.new
49
49
  _(namo.data).must_equal []
50
- _(namo.formulae).must_equal({})
50
+ _(namo.formulae).must_be_empty
51
51
  end
52
52
 
53
53
  it "accepts keyword formulae with no data" do
@@ -56,6 +56,17 @@ describe Namo do
56
56
  _(namo.derived_dimensions).must_equal [:y]
57
57
  end
58
58
 
59
+ it "wraps keyword formulae given as a Hash in a Formulae" do
60
+ namo = Namo.new(formulae: {y: proc{|r| r[:x] * 2}})
61
+ _(namo.formulae).must_be_kind_of Namo::Formulae
62
+ _(namo.formulae.keys).must_equal [:y]
63
+ end
64
+
65
+ it "passes keyword formulae already given as a Formulae through unchanged" do
66
+ formulae = Namo::Formulae.new({y: proc{|r| r[:x] * 2}})
67
+ _(Namo.new(formulae: formulae).formulae).must_be_same_as formulae
68
+ end
69
+
59
70
  it "honours an explicit empty positional array over the nil sentinel" do
60
71
  _(Namo.new([]).data).must_equal []
61
72
  end
@@ -1010,7 +1021,7 @@ describe Namo do
1010
1021
  prices[:sma] = sma
1011
1022
  projected = prices[:sma]
1012
1023
  _(projected.to_a).must_equal [{sma: 10.0}, {sma: 15.0}, {sma: 20.0}]
1013
- _(projected.formulae).must_equal({})
1024
+ _(projected.formulae).must_be_empty
1014
1025
  end
1015
1026
 
1016
1027
  it "returns an instance of self's class" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: namo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -66,6 +66,7 @@ files:
66
66
  - Rakefile
67
67
  - lib/Namo/Collection.rb
68
68
  - lib/Namo/Enumerable.rb
69
+ - lib/Namo/Formulae.rb
69
70
  - lib/Namo/NegatedDimension.rb
70
71
  - lib/Namo/Row.rb
71
72
  - lib/Namo/VERSION.rb
@@ -73,6 +74,7 @@ files:
73
74
  - lib/namo.rb
74
75
  - namo.gemspec
75
76
  - test/Namo/Collection_test.rb
77
+ - test/Namo/Formulae_test.rb
76
78
  - test/Namo/NegatedDimension_test.rb
77
79
  - test/Namo/Row_test.rb
78
80
  - test/Symbol_test.rb
@@ -95,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
97
  - !ruby/object:Gem::Version
96
98
  version: '0'
97
99
  requirements: []
98
- rubygems_version: 4.0.14
100
+ rubygems_version: 4.0.10
99
101
  specification_version: 4
100
102
  summary: Named dimensional data for Ruby.
101
103
  test_files: []