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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad4994109308fd955b2f508afe1f07251bc4287406ed7f27eb8a484a61dfabcf
4
- data.tar.gz: 04be0a1d0e8f97b86932a3e5e8953bfe3b0e51fe16bcb80b5dec9a5a73aecd36
3
+ metadata.gz: 4a7dd953b56fb4f692b0e92822cb6d5dcf9a38f8645a913aedc06333441ad305
4
+ data.tar.gz: b492295ead6b4a9ea5647290eef7d008e86e6cbc0275176f45f2ea76b1692854
5
5
  SHA512:
6
- metadata.gz: 507a77ecfc3d23fe1ad4ef14783b2e4f86ff44b58431cb93b8e4aa41c12bf4778f03c7a3bdca7f8958620b3d2e3bba3292cdfbcd5636c8286661aed100398b30
7
- data.tar.gz: fa6acfcf416108a871e0697ae998cef8701263446abee9089c62e1cfb24c102d22f002e643f89890a7c40ad2ccedca3dd7d58ebf04125fb4a87d17f58a908abd
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
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.11.1'
5
+ VERSION = '0.12.0'
6
6
  end
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(data = [], formulae: {})
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]
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.11.1
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran