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 +4 -4
- data/CHANGELOG +9 -2
- data/README.md +2 -0
- data/lib/Namo/Collection.rb +10 -0
- data/lib/Namo/Enumerable.rb +13 -13
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +16 -9
- data/test/Namo/Collection_test.rb +54 -0
- data/test/console_test.rb +23 -19
- 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: 062a5ecf9fba2666e680bc2842d7569bd07a2e00f521cb6ad583a2b13f22d5c4
|
|
4
|
+
data.tar.gz: 7333a5d541ee742f49796d09949b7b2c89ac056635449ce9f1c1e0733ef757b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
data/lib/Namo/Collection.rb
CHANGED
|
@@ -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
|
data/lib/Namo/Enumerable.rb
CHANGED
|
@@ -11,22 +11,22 @@ class Namo
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def select(&block)
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
return_class.new(@data.take(n), formulae: @formulae.dup)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def drop(n)
|
|
48
|
-
|
|
48
|
+
return_class.new(@data.drop(n), formulae: @formulae.dup)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def take_while(&block)
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
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 '
|
|
17
|
+
describe 'bin/console' do
|
|
15
18
|
def console(*expressions)
|
|
16
19
|
root = File.expand_path('..', __dir__)
|
|
17
|
-
IO.popen([File.join(root, '
|
|
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 "
|
|
26
|
-
|
|
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 "
|
|
31
|
-
_(console('
|
|
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 "
|
|
40
|
-
|
|
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
|