namo 0.11.1 → 0.12.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 +4 -4
- data/CHANGELOG +10 -0
- data/README.md +43 -0
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +4 -2
- data/test/namo_test.rb +107 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a7dd953b56fb4f692b0e92822cb6d5dcf9a38f8645a913aedc06333441ad305
|
|
4
|
+
data.tar.gz: b492295ead6b4a9ea5647290eef7d008e86e6cbc0275176f45f2ea76b1692854
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35a4a0acebfa4f01b83948c7a1639606cb58f9de7c0e75c8cf5886654128526cbe5554e4670503b74b5687674da00929a1b3252ee7bf30d2ecb7db976b580de5
|
|
7
|
+
data.tar.gz: ce62a04e0e1a7db2e1ec1a5f20674e000baadf3de64186ea1afe2718d8acdd36189e612aa14c6ca71a8e6330009aa0bfa380b797020ee9fbb69a6a49e9390e90
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260601
|
|
5
|
+
0.12.0: Constructor widening — keyword data: and name:.
|
|
6
|
+
|
|
7
|
+
1. ~ lib/namo.rb: Namo#initialize widened from `(data = [], formulae: {})` to `(positional_data = nil, data: [], formulae: {}, name: nil)`. Data may now be passed positionally or by the `data:` keyword; positional wins when both are given (`@data = positional_data || data`). The positional default changes from `[]` to `nil` so it acts as a "not supplied" sentinel that lets `|| data` fall through to the keyword path; an explicit `Namo.new([])` still yields `@data == []` because the truthy empty array short-circuits. + `name:` keyword, stored in `@name`. Every pre-existing call site (positional data, positional data + keyword formulae, no-arg, formulae-only, and the operators' internal `self.class.new(rows, formulae: ...)`) is unaffected.
|
|
8
|
+
2. + lib/namo.rb: `attr_accessor :name`, alongside the existing `:data` and `:formulae` accessors. Gives `name` reading and `name=` post-construction mutation. Operator results carry `name == nil` (the operators construct without `name:`), establishing the subclass guard convention: subclasses guard `initialize` side effects with `return unless name`, so operator-derived instances skip them and only explicitly-named constructions fire them.
|
|
9
|
+
3. ~ test/namo_test.rb: + "construction" describe (positional data, positional + keyword formulae, no-arg empty, formulae-only, explicit `[]` honoured over the nil sentinel, keyword `data:`, positional-wins-over-keyword, and round-trips through a set operator and an Enumerable method). + "#name" describe (stored from keyword, defaults to nil, settable post-construction, nil on set-operator and Enumerable-derived results). + "subclass side-effect guard" describe — an anonymous Class.new(Namo) with a `return unless name`-guarded counter, asserting it fires for a named construction and stays untouched for an unnamed construction and an operator result.
|
|
10
|
+
4. ~ README.md: + note in the Usage section that data may be passed positionally or by `data:`, with positional winning when both are given. + "Named Namos" section: the `name:` keyword, the `name`/`name=` accessor, nil-on-derivation for operator results, and the subclass guard convention with the TradingAnalysis / argument-less `super` example.
|
|
11
|
+
5. ~ ROADMAP.md: Promote 0.12.0 from upcoming to shipped — `## Current state` bumped to 0.12.0, a `### 0.12.0 (2026-06-01)` entry added in the shipped section (written in shipped voice), and the now-redundant `## 0.12.0` upcoming section removed; fold the widened constructor into the Summary's completed vocabulary and point "next phase" at 0.13.0+.
|
|
12
|
+
6. ~ Namo::VERSION: /0.11.1/0.12.0/
|
|
13
|
+
|
|
4
14
|
20260601
|
|
5
15
|
0.11.1: Extract the subset Enumerable methods into a Namo::Enumerable module.
|
|
6
16
|
|
data/README.md
CHANGED
|
@@ -33,6 +33,16 @@ sales = Namo.new([
|
|
|
33
33
|
])
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Data may be passed positionally, as above, or by the `data:` keyword where that reads more explicitly:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
sales = Namo.new(data: [
|
|
40
|
+
{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100}
|
|
41
|
+
])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When both are given, the positional argument wins and the keyword `data:` is ignored.
|
|
45
|
+
|
|
36
46
|
Dimensions and coordinates are inferred:
|
|
37
47
|
|
|
38
48
|
```ruby
|
|
@@ -715,6 +725,39 @@ sales[:product, :quarter, :revenue].to_h
|
|
|
715
725
|
# }
|
|
716
726
|
```
|
|
717
727
|
|
|
728
|
+
### Named Namos
|
|
729
|
+
|
|
730
|
+
A Namo can carry a name, passed by the `name:` keyword and read or set through the `name` accessor:
|
|
731
|
+
|
|
732
|
+
```ruby
|
|
733
|
+
sales = Namo.new(data: rows, name: :sales)
|
|
734
|
+
sales.name
|
|
735
|
+
# => :sales
|
|
736
|
+
|
|
737
|
+
sales.name = :renamed
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
A name defaults to `nil`, and operator results are name-less by design — the result of `+`, `*`, `select`, and the rest is a derived object, not the original, so giving it the parent's name would mislead:
|
|
741
|
+
|
|
742
|
+
```ruby
|
|
743
|
+
(sales + more).name
|
|
744
|
+
# => nil
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
This `nil`-on-derivation behaviour is what lets subclasses with side effects in `initialize` guard those effects on the name. Operator-derived instances are name-less and skip the side effects; explicitly constructed instances pass `name:` and the side effects fire:
|
|
748
|
+
|
|
749
|
+
```ruby
|
|
750
|
+
class TradingAnalysis < Namo
|
|
751
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
752
|
+
super
|
|
753
|
+
return unless name
|
|
754
|
+
register_indicators
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
`super` with no parentheses forwards every argument — positional and keyword — to `Namo#initialize` unchanged. The `return unless name` guard means a subclass need not override every operator to stop the result of `*` or `select` from re-running its construction side effects: it guards on `name` instead.
|
|
760
|
+
|
|
718
761
|
## Why?
|
|
719
762
|
|
|
720
763
|
Every other multi-dimensional array library requires you to pre-shape your data before you can work with it. Namo takes it in the form it likely already comes in.
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -12,6 +12,7 @@ class Namo
|
|
|
12
12
|
|
|
13
13
|
attr_accessor :data
|
|
14
14
|
attr_accessor :formulae
|
|
15
|
+
attr_accessor :name
|
|
15
16
|
|
|
16
17
|
def dimensions
|
|
17
18
|
data_dimensions + derived_dimensions
|
|
@@ -246,8 +247,9 @@ class Namo
|
|
|
246
247
|
end
|
|
247
248
|
end
|
|
248
249
|
|
|
249
|
-
def initialize(
|
|
250
|
-
@data = data
|
|
250
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
251
|
+
@data = positional_data || data
|
|
251
252
|
@formulae = formulae
|
|
253
|
+
@name = name
|
|
252
254
|
end
|
|
253
255
|
end
|
data/test/namo_test.rb
CHANGED
|
@@ -33,6 +33,113 @@ describe Namo do
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
describe "construction" do
|
|
37
|
+
it "accepts positional data" do
|
|
38
|
+
_(Namo.new([{x: 1}]).data).must_equal [{x: 1}]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "accepts positional data with keyword formulae" do
|
|
42
|
+
namo = Namo.new([{x: 1}], formulae: {y: proc{|r| r[:x] * 2}})
|
|
43
|
+
_(namo.data).must_equal [{x: 1}]
|
|
44
|
+
_(namo.values(:y)).must_equal [2]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "produces an empty Namo with no arguments" do
|
|
48
|
+
namo = Namo.new
|
|
49
|
+
_(namo.data).must_equal []
|
|
50
|
+
_(namo.formulae).must_equal({})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "accepts keyword formulae with no data" do
|
|
54
|
+
namo = Namo.new(formulae: {y: proc{|r| r[:x] * 2}})
|
|
55
|
+
_(namo.data).must_equal []
|
|
56
|
+
_(namo.derived_dimensions).must_equal [:y]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "honours an explicit empty positional array over the nil sentinel" do
|
|
60
|
+
_(Namo.new([]).data).must_equal []
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "accepts data by keyword" do
|
|
64
|
+
_(Namo.new(data: [{x: 1}]).data).must_equal [{x: 1}]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "lets positional data win when both positional and keyword data are given" do
|
|
68
|
+
_(Namo.new([{x: 1}], data: [{x: 2}]).data).must_equal [{x: 1}]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "survives a round-trip through a set operator" do
|
|
72
|
+
a = Namo.new([{x: 1}])
|
|
73
|
+
b = Namo.new([{x: 2}])
|
|
74
|
+
_((a + b).data).must_equal [{x: 1}, {x: 2}]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "survives a round-trip through an Enumerable method" do
|
|
78
|
+
namo = Namo.new([{x: 1}, {x: 2}])
|
|
79
|
+
_(namo.select{|row| row[:x] > 1}.data).must_equal [{x: 2}]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "#name" do
|
|
84
|
+
it "stores a name passed by keyword" do
|
|
85
|
+
_(Namo.new([{x: 1}], name: :foo).name).must_equal :foo
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "defaults to nil when no name is passed" do
|
|
89
|
+
_(Namo.new([{x: 1}]).name).must_be_nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "is settable post-construction" do
|
|
93
|
+
namo = Namo.new([{x: 1}])
|
|
94
|
+
namo.name = :bar
|
|
95
|
+
_(namo.name).must_equal :bar
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "is nil on a Namo derived from a set operator" do
|
|
99
|
+
a = Namo.new([{x: 1}], name: :a)
|
|
100
|
+
b = Namo.new([{x: 2}], name: :b)
|
|
101
|
+
_((a + b).name).must_be_nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "is nil on a Namo derived from an Enumerable method" do
|
|
105
|
+
namo = Namo.new([{x: 1}, {x: 2}], name: :original)
|
|
106
|
+
_(namo.select{|row| row[:x] > 1}.name).must_be_nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "subclass side-effect guard" do
|
|
111
|
+
before do
|
|
112
|
+
@guard_class = Class.new(Namo) do
|
|
113
|
+
def self.fired
|
|
114
|
+
@fired ||= []
|
|
115
|
+
end
|
|
116
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
117
|
+
super
|
|
118
|
+
return unless name
|
|
119
|
+
self.class.fired << name
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "fires guarded side effects for an explicitly named construction" do
|
|
125
|
+
@guard_class.new(data: [{x: 1}], name: :foo)
|
|
126
|
+
_(@guard_class.fired).must_equal [:foo]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "skips guarded side effects for an unnamed construction" do
|
|
130
|
+
@guard_class.new(data: [{x: 1}])
|
|
131
|
+
_(@guard_class.fired).must_equal []
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "skips guarded side effects for an operator result" do
|
|
135
|
+
a = @guard_class.new(data: [{x: 1}], name: :a)
|
|
136
|
+
b = @guard_class.new(data: [{x: 2}], name: :b)
|
|
137
|
+
@guard_class.fired.clear
|
|
138
|
+
_((a + b).name).must_be_nil
|
|
139
|
+
_(@guard_class.fired).must_equal []
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
36
143
|
describe "#dimensions" do
|
|
37
144
|
it "infers dimensions from hash keys" do
|
|
38
145
|
_(sales.dimensions).must_equal [:product, :quarter, :price, :quantity]
|