namo 0.31.5 → 0.31.6

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: 77e56d154536ec5ebde8dbb2d660277007a76d4ec108c6b669ff96399f9991d7
4
- data.tar.gz: 79baaf53cfd3417b5db049b7c2ea6701c60580e97f41235bcafb91538c49d427
3
+ metadata.gz: 062a5ecf9fba2666e680bc2842d7569bd07a2e00f521cb6ad583a2b13f22d5c4
4
+ data.tar.gz: 7333a5d541ee742f49796d09949b7b2c89ac056635449ce9f1c1e0733ef757b0
5
5
  SHA512:
6
- metadata.gz: ffa93bb4ad5f35eaffcefa76778dbffc843bc24a95130327d4da88e8ae8e3103fdc6a28a2eabaf0f38b996b768e6125f7e1538612ac8c922e0bb7c495870f128
7
- data.tar.gz: 24e8d924c61152922e757ab30b87662cede03e6dcb85dbb2cd964de4b9230ee4d65617e74760b7e37a3d8831dd332c3f46e829adfbaa82cf275f2a9108528558
6
+ metadata.gz: 705357a13d1495c3a96fbc470fb4f796f7d8eb9a40a82552708801c3969972dc2cf59255f64a8d7ad0d3479442980f3e5a6748d9b4e16992e70a5258291a787b
7
+ data.tar.gz: 911df517eaeafb86233af02c3d56a92dfc27afa4cc7cadc8e8b4b02e6f16e52df8ee683a79de7be57f89480f8c9420c02b2d8a8bd3b8a4e75606561d5ce3850a
data/CHANGELOG CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## 20260826
4
4
 
5
+ 0.31.6: ~ Namo::Collection: a row operation yields a Namo rather than a memberless Collection.
6
+
7
+ 1. + Namo#return_class: the class an operation returns, protected, returning self.class. The twenty-two sites which built what they return — projection and contraction, the five set operators, the two composition operators, and the subset Enumerable methods in Namo::Enumerable — now ask for it rather than naming self.class themselves.
8
+ 2. ~ Namo::Collection#return_class: returns Namo. A Collection's kind is its members and a row operation carries none across, so what came back was a Collection with none: it answered summary with an empty Namo rather than raising, and rendered as [] beside a row count. 0.18.0 has these operations read the detail view and behave as the detail, and the detail is a Namo; this is the code meeting that sentence.
9
+ 3. ~ README.md: the four views, on what a row operation on a Collection returns.
10
+ 4. ~ ROADMAP.md: + 0.31.6.
11
+ 5. ~ Namo::VERSION: /0.31.5/0.31.6/
12
+
13
+
5
14
  0.31.5: ~ Namo::Collection#detail, #as_detail: the detail view carries the members' formulae.
6
15
 
7
16
  1. ~ Namo::Collection#detail: the returned Namo carries the members' formulae, folded in member order so that a later member wins a name collision, as a later member wins a collision of names in <<. It had been built with none, so the view documented at 0.18.0 as the lossless union of the members' rows dropped every derived dimension: group_by hands each member the source's formulae, and detail handed them back stripped.
@@ -12,8 +21,6 @@
12
21
  6. ~ Namo::VERSION: /0.31.4/0.31.5/
13
22
 
14
23
 
15
- ## 20260826
16
-
17
24
  0.31.4: ~ Namo::Collection#inspect: the members are rendered, nested under their names.
18
25
 
19
26
  1. ~ Namo::Collection#inspect: the members are rendered nested under their names rather than listed as bare labels beside a row count, the members being the substance and the data view derived from them. The Namo::INSPECTED_ROWS budget is spread across the members shown — one member renders ten of its rows, five render two apiece, ten render one each and are put on one line — and members beyond it are elided with a count, as rows are. The member list had no cap at all: 2,641 members emitted 22,704 characters on a single line, which is the failure INSPECTED_ROWS was introduced at 0.31.0 to end. A member renders nested as it renders alone, its derived values among its stored ones and its own derived: suffix naming whatever none of its rendered rows could materialise. The detail view's row count closes the block, and a Collection's own formulae are named after it, being a namespace its members cannot resolve.
data/README.md CHANGED
@@ -1119,6 +1119,8 @@ gt[component: 'engine'].values(:cost)
1119
1119
 
1120
1120
  Detail is the lazy view because a Collection's rows simply *are* its members' rows; a summary is a reduction you pose against them, so it is never reached by accident — only through `summary` or `as_summary`.
1121
1121
 
1122
+ An inherited row-operation on a Collection — selection, projection, `select`, `sort_by`, the set and composition operators — returns a `Namo`, not a Collection. It reads the detail view and behaves as the detail, and the detail is a Namo. A Collection is defined by its members, and a row operation carries none of them across, so a Collection is not what the result is: asking one for a `summary` should say there is none to give rather than answer for no members. A subclass of `Namo` is unaffected — a projection of a `PriceData` is a `PriceData`.
1123
+
1122
1124
  #### Four view methods
1123
1125
 
1124
1126
  `detail` carries the members' formulae, its rows being the members' rows — folded in member order, so a later member wins a name collision as it wins a collision of names in `<<`. A `summary`'s rows are reductions, holding neither the dimensions those formulae read nor the rows they read them from, so it carries none.
@@ -70,6 +70,16 @@ class Namo
70
70
  "#<#{self.class}#{inspected_name} #{inspected_members} #{@data.length} rows#{inspected_derived}>"
71
71
  end
72
72
 
73
+ protected
74
+
75
+ # A row operation on a Collection yields a Namo: 0.18.0 has it read the detail
76
+ # view and behave as the detail, and the detail is a Namo. Built as a
77
+ # Collection it would be one with no members, answering summary with an empty
78
+ # Namo rather than saying it cannot.
79
+ def return_class
80
+ Namo
81
+ end
82
+
73
83
  private
74
84
 
75
85
  # The members' formulae, folded in member order so that a later member wins a
@@ -11,22 +11,22 @@ class Namo
11
11
  end
12
12
 
13
13
  def select(&block)
14
- self.class.new(@data.select{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
14
+ return_class.new(@data.select{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
15
15
  end
16
16
  alias_method :filter, :select
17
17
  alias_method :find_all, :select
18
18
 
19
19
  def reject(&block)
20
- self.class.new(@data.reject{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
20
+ return_class.new(@data.reject{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
21
21
  end
22
22
 
23
23
  def sort_by(&block)
24
- self.class.new(@data.sort_by{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
24
+ return_class.new(@data.sort_by{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
25
25
  end
26
26
 
27
27
  def first(n = nil)
28
28
  if n
29
- self.class.new(@data.first(n), formulae: @formulae.dup)
29
+ return_class.new(@data.first(n), formulae: @formulae.dup)
30
30
  else
31
31
  @data.first ? Row.new(@data.first, @formulae, self) : nil
32
32
  end
@@ -34,38 +34,38 @@ class Namo
34
34
 
35
35
  def last(n = nil)
36
36
  if n
37
- self.class.new(@data.last(n), formulae: @formulae.dup)
37
+ return_class.new(@data.last(n), formulae: @formulae.dup)
38
38
  else
39
39
  @data.last ? Row.new(@data.last, @formulae, self) : nil
40
40
  end
41
41
  end
42
42
 
43
43
  def take(n)
44
- self.class.new(@data.take(n), formulae: @formulae.dup)
44
+ return_class.new(@data.take(n), formulae: @formulae.dup)
45
45
  end
46
46
 
47
47
  def drop(n)
48
- self.class.new(@data.drop(n), formulae: @formulae.dup)
48
+ return_class.new(@data.drop(n), formulae: @formulae.dup)
49
49
  end
50
50
 
51
51
  def take_while(&block)
52
- self.class.new(@data.take_while{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
52
+ return_class.new(@data.take_while{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
53
53
  end
54
54
 
55
55
  def drop_while(&block)
56
- self.class.new(@data.drop_while{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
56
+ return_class.new(@data.drop_while{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
57
57
  end
58
58
 
59
59
  def uniq(&block)
60
60
  rows = block ? @data.uniq{|row| block.call(Row.new(row, @formulae, self))} : @data.uniq
61
- self.class.new(rows, formulae: @formulae.dup)
61
+ return_class.new(rows, formulae: @formulae.dup)
62
62
  end
63
63
 
64
64
  def partition(&block)
65
65
  matches, non_matches = @data.partition{|row| block.call(Row.new(row, @formulae, self))}
66
66
  [
67
- self.class.new(matches, formulae: @formulae.dup),
68
- self.class.new(non_matches, formulae: @formulae.dup),
67
+ return_class.new(matches, formulae: @formulae.dup),
68
+ return_class.new(non_matches, formulae: @formulae.dup),
69
69
  ]
70
70
  end
71
71
 
@@ -74,7 +74,7 @@ class Namo
74
74
  source = derived_dimensions.include?(dimension) ? self[*data_dimensions, dimension] : self
75
75
  members = (
76
76
  source.data.group_by{|row_data| row_data[dimension]}.map do |value, rows|
77
- self.class.new(rows, formulae: source.formulae.dup, name: value)
77
+ return_class.new(rows, formulae: source.formulae.dup, name: value)
78
78
  end
79
79
  )
80
80
  collection << members
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.31.5'
5
+ VERSION = '0.31.6'
6
6
  end
data/lib/namo.rb CHANGED
@@ -79,7 +79,7 @@ class Namo
79
79
  end
80
80
  )
81
81
  carried = positive.any? ? @formulae.reject{|name, _| positive.include?(name)} : @formulae.dup
82
- self.class.new(projected, formulae: carried)
82
+ return_class.new(projected, formulae: carried)
83
83
  end
84
84
 
85
85
  def []=(name, value)
@@ -124,31 +124,31 @@ class Namo
124
124
  def +(other)
125
125
  raise_unless_namo(other)
126
126
  raise_unless_matching_data_dimensions(other)
127
- self.class.new(@data + other.data, formulae: other.formulae.merge(@formulae))
127
+ return_class.new(@data + other.data, formulae: other.formulae.merge(@formulae))
128
128
  end
129
129
 
130
130
  def -(other)
131
131
  raise_unless_namo(other)
132
132
  raise_unless_matching_data_dimensions(other)
133
- self.class.new(@data - other.data, formulae: @formulae.dup)
133
+ return_class.new(@data - other.data, formulae: @formulae.dup)
134
134
  end
135
135
 
136
136
  def &(other)
137
137
  raise_unless_namo(other)
138
138
  raise_unless_matching_data_dimensions(other)
139
- self.class.new(@data & other.data, formulae: @formulae.dup)
139
+ return_class.new(@data & other.data, formulae: @formulae.dup)
140
140
  end
141
141
 
142
142
  def |(other)
143
143
  raise_unless_namo(other)
144
144
  raise_unless_matching_data_dimensions(other)
145
- self.class.new((@data | other.data), formulae: other.formulae.merge(@formulae))
145
+ return_class.new((@data | other.data), formulae: other.formulae.merge(@formulae))
146
146
  end
147
147
 
148
148
  def ^(other)
149
149
  raise_unless_namo(other)
150
150
  raise_unless_matching_data_dimensions(other)
151
- self.class.new((@data - other.data) + (other.data - @data), formulae: other.formulae.merge(@formulae))
151
+ return_class.new((@data - other.data) + (other.data - @data), formulae: other.formulae.merge(@formulae))
152
152
  end
153
153
 
154
154
  def *(other, &block)
@@ -167,7 +167,7 @@ class Namo
167
167
  matched.each{|right_row| combined_data << left_row.merge(right_row)}
168
168
  end
169
169
  end
170
- self.class.new(combined_data, formulae: other.formulae.merge(@formulae))
170
+ return_class.new(combined_data, formulae: other.formulae.merge(@formulae))
171
171
  end
172
172
 
173
173
  def **(other, &block)
@@ -184,7 +184,7 @@ class Namo
184
184
  other.data.each{|right_row| combined_data << left_row.merge(right_row)}
185
185
  end
186
186
  end
187
- self.class.new(combined_data, formulae: other.formulae.merge(@formulae))
187
+ return_class.new(combined_data, formulae: other.formulae.merge(@formulae))
188
188
  end
189
189
 
190
190
  def /(other)
@@ -193,7 +193,7 @@ class Namo
193
193
  projected = @data.map do |row|
194
194
  kept.each_with_object({}){|dim, hash| hash[dim] = row[dim]}
195
195
  end
196
- self.class.new(projected.uniq, formulae: @formulae.dup)
196
+ return_class.new(projected.uniq, formulae: @formulae.dup)
197
197
  end
198
198
 
199
199
  def ==(other)
@@ -262,6 +262,13 @@ class Namo
262
262
 
263
263
  protected
264
264
 
265
+ # The class an operation returns. A subclass is its own kind of thing, so a
266
+ # projection of a PriceData is a PriceData; Namo::Collection is not, its kind
267
+ # being its members, which a row operation carries none of.
268
+ def return_class
269
+ self.class
270
+ end
271
+
265
272
  # The rendered rows, the count of those not shown, and the derived dimensions
266
273
  # none of them carries. Shared with Collection, which renders its members' rows
267
274
  # rather than its own and must report what a member would report of itself.
@@ -347,6 +347,60 @@ describe Namo::Collection do
347
347
  end
348
348
  end
349
349
 
350
+ # 0.18.0 has an inherited row-operation read the detail view and "behave as the
351
+ # detail", and the detail is a Namo. Built as a Collection the result was one
352
+ # with no members: it answered summary with an empty Namo rather than saying it
353
+ # had no summary to give, and rendered as [] beside a row count.
354
+ describe "the class of a derived result" do
355
+ def grouped
356
+ Namo.new([
357
+ {station: "Melbourne", month: "2025-01", high_temp: 26.4},
358
+ {station: "Perth", month: "2025-01", high_temp: 32.0},
359
+ {station: "Perth", month: "2025-02", high_temp: 29.5}]).group_by(:month)
360
+ end
361
+
362
+ it "is a Namo for selection and projection" do
363
+ _(grouped[station: "Perth"]).must_be_instance_of Namo
364
+ _(grouped[:station, :high_temp]).must_be_instance_of Namo
365
+ end
366
+
367
+ it "is a Namo for the subset Enumerable methods" do
368
+ collection = grouped
369
+ _(collection.select{|row| row[:station] == "Perth"}).must_be_instance_of Namo
370
+ _(collection.sort_by{|row| row[:high_temp]}).must_be_instance_of Namo
371
+ _(collection.first(2)).must_be_instance_of Namo
372
+ _(collection.uniq).must_be_instance_of Namo
373
+ end
374
+
375
+ it "is a Namo for the set and composition operators" do
376
+ collection = grouped
377
+ _(collection + collection).must_be_instance_of Namo
378
+ _(collection - collection).must_be_instance_of Namo
379
+ _(collection & collection).must_be_instance_of Namo
380
+ end
381
+
382
+ it "carries the rows the operation selected" do
383
+ _(grouped[station: "Perth"].values(:high_temp)).must_equal [32.0, 29.5]
384
+ end
385
+
386
+ # The point of it: a summary of no members is not an empty summary.
387
+ it "has no summary to give, rather than an empty one" do
388
+ _{grouped[station: "Perth"].summary(:high_temp)}.must_raise NoMethodError
389
+ end
390
+
391
+ it "leaves a subclass its own kind" do
392
+ priced = Class.new(Namo)
393
+ instance = priced.new([{a: 1}, {a: 2}])
394
+ _(instance.select{|row| row[:a] > 1}).must_be_instance_of priced
395
+ _(instance[:a]).must_be_instance_of priced
396
+ end
397
+
398
+ it "leaves group_by returning a Collection" do
399
+ _(grouped).must_be_instance_of Namo::Collection
400
+ _(grouped.members.length).must_equal 2
401
+ end
402
+ end
403
+
350
404
  describe "the detail view and its formulae" do
351
405
  def readings
352
406
  namo = Namo.new([
data/test/console_test.rb CHANGED
@@ -4,17 +4,20 @@ require 'minitest/autorun'
4
4
  require 'minitest-spec-context'
5
5
 
6
6
  require_relative '../lib/namo'
7
- require_relative '../script/fixtures'
8
7
 
9
- # script/console exists so that a question asked at a prompt is asked of the same
10
- # data the demo put on the screen. That is only true while both read
11
- # script/fixtures.rb, so what is worth asserting is the agreement rather than the
12
- # session: the names it defines, and that they hold what the fixtures say.
8
+ # bin/console is the conventional gem prompt: irb with the library loaded and
9
+ # nothing else. It held the demo's fixtures until 20260826, so that a question
10
+ # asked here was asked of the data the demo had shown — a guarantee the demo's
11
+ # own `i` now keeps better, opening irb on the binding the run is using rather
12
+ # than on a second process holding equal values.
13
+ #
14
+ # What is left to assert is that it starts, that Namo is there, and that it needs
15
+ # nothing off the load path.
13
16
 
14
- describe 'script/console' do
17
+ describe 'bin/console' do
15
18
  def console(*expressions)
16
19
  root = File.expand_path('..', __dir__)
17
- IO.popen([File.join(root, 'script', 'console'), '--prompt', 'simple'],
20
+ IO.popen([File.join(root, 'bin', 'console'), '--prompt', 'simple'],
18
21
  'r+', chdir: root, err: [:child, :out]) do |io|
19
22
  io.puts(expressions, 'exit')
20
23
  io.close_write
@@ -22,21 +25,22 @@ describe 'script/console' do
22
25
  end
23
26
  end
24
27
 
25
- it "defines the names it says it does" do
26
- output = console('[readings, stations, months].map{|n| n.class}.inspect')
27
- _(output).must_match(/\[Namo, Namo, Namo\]/)
28
+ it "starts with Namo loaded" do
29
+ _(console('Namo.new([{a: 1}]).dimensions.inspect')).must_match(/\[:a\]/)
28
30
  end
29
31
 
30
- it "gives sales the revenue formula" do
31
- _(console('readings.derived_dimensions.inspect')).must_match(/\[:anomaly\]/)
32
- end
33
-
34
- it "holds what the fixtures hold, so it cannot drift from the demo" do
35
- expected = eval(Fixtures.readings)
36
- _(console('readings.data == ' + expected.inspect)).must_match(/true/)
32
+ it "starts without echoing its own source" do
33
+ _(console('1')).wont_match(/IRB\.start/)
37
34
  end
38
35
 
39
- it "starts without echoing its own source" do
40
- _(console('1')).wont_match(/binding\.irb/)
36
+ it "needs nothing but the gem's own dependencies" do
37
+ root = File.expand_path('..', __dir__)
38
+ output = IO.popen({'RUBYLIB' => nil}, [File.join(root, 'bin', 'console'), '--prompt', 'simple'],
39
+ 'r+', chdir: root, err: [:child, :out]) do |io|
40
+ io.puts('exit')
41
+ io.close_write
42
+ io.read
43
+ end
44
+ _(output).wont_match(/LoadError/)
41
45
  end
42
46
  end
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.31.5
4
+ version: 0.31.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran