namo 0.31.4 → 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 +19 -0
- data/README.md +4 -0
- data/lib/Namo/Collection.rb +23 -2
- 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 +96 -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,25 @@
|
|
|
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
|
+
|
|
14
|
+
0.31.5: ~ Namo::Collection#detail, #as_detail: the detail view carries the members' formulae.
|
|
15
|
+
|
|
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.
|
|
17
|
+
2. ~ Namo::Collection#as_detail: merges the detail view's formulae into the Collection's own, the Collection having become that view. Without it the round trip still lost them, as_detail having taken only the data.
|
|
18
|
+
3. ~ script/demo: the collections section checks the round trip with === rather than ==, == comparing row multisets alone and so unable to see the formulae go missing, which is why this stood.
|
|
19
|
+
4. ~ README.md: the four views, on which of them carries formulae and why.
|
|
20
|
+
5. ~ ROADMAP.md: + 0.31.5.
|
|
21
|
+
6. ~ Namo::VERSION: /0.31.4/0.31.5/
|
|
22
|
+
|
|
23
|
+
|
|
5
24
|
0.31.4: ~ Namo::Collection#inspect: the members are rendered, nested under their names.
|
|
6
25
|
|
|
7
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,8 +1119,12 @@ 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
|
|
|
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.
|
|
1127
|
+
|
|
1124
1128
|
The views come in a non-mutating pair and a mutating pair:
|
|
1125
1129
|
|
|
1126
1130
|
- `summary(dimension = nil, by:, reducer:, &block)` and `detail(by:)` are **non-mutating** — each returns a fresh `Namo` derived from the members, leaving the Collection untouched. Use these when you want a view to keep: assign the result to a variable and operate on it independently.
|
data/lib/Namo/Collection.rb
CHANGED
|
@@ -42,7 +42,7 @@ class Namo
|
|
|
42
42
|
member.data.map{|row| row.key?(by) ? row : row.merge(by => member.name)}
|
|
43
43
|
end
|
|
44
44
|
)
|
|
45
|
-
Namo.new(rows)
|
|
45
|
+
Namo.new(rows, formulae: member_formulae)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def as_summary(dimension = nil, by: :member, reducer: :sum, &block)
|
|
@@ -52,7 +52,9 @@ class Namo
|
|
|
52
52
|
|
|
53
53
|
def as_detail(positional_by = nil, by: :member)
|
|
54
54
|
by = positional_by || by
|
|
55
|
-
|
|
55
|
+
view = detail(by)
|
|
56
|
+
@data = view.data
|
|
57
|
+
@formulae = @formulae.merge(view.formulae)
|
|
56
58
|
self
|
|
57
59
|
end
|
|
58
60
|
|
|
@@ -68,8 +70,27 @@ class Namo
|
|
|
68
70
|
"#<#{self.class}#{inspected_name} #{inspected_members} #{@data.length} rows#{inspected_derived}>"
|
|
69
71
|
end
|
|
70
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
|
+
|
|
71
83
|
private
|
|
72
84
|
|
|
85
|
+
# The members' formulae, folded in member order so that a later member wins a
|
|
86
|
+
# name collision, as a later member wins a collision of names in <<. The
|
|
87
|
+
# detail view is the members' rows, so it must be able to answer what the
|
|
88
|
+
# members can answer of them; group_by hands every member the same formulae,
|
|
89
|
+
# so only an assembled Collection can have members which disagree.
|
|
90
|
+
def member_formulae
|
|
91
|
+
@members.map(&:formulae).reduce(Formulae.new){|merged, formulae| merged.merge(formulae)}
|
|
92
|
+
end
|
|
93
|
+
|
|
73
94
|
def inspected_members
|
|
74
95
|
return '[]' if @members.empty?
|
|
75
96
|
shown = [@members.length, INSPECTED_ROWS].min
|
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,102 @@ 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
|
+
|
|
404
|
+
describe "the detail view and its formulae" do
|
|
405
|
+
def readings
|
|
406
|
+
namo = Namo.new([
|
|
407
|
+
{station: "Melbourne", month: "2025-01", high_temp: 26.4, mean_temp: 25.9},
|
|
408
|
+
{station: "Perth", month: "2025-01", high_temp: 32.0, mean_temp: 30.0}])
|
|
409
|
+
namo[:anomaly] = proc{|row| (row[:high_temp] - row[:mean_temp]).round(1)}
|
|
410
|
+
namo
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
it "carries the members' formulae, the view being the members' rows" do
|
|
414
|
+
_(readings.group_by(:station).detail(:station).derived_dimensions).must_equal [:anomaly]
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it "answers a derived dimension through the detail view" do
|
|
418
|
+
_(readings.group_by(:station).detail(:station).values(:anomaly)).must_equal [0.5, 2.0]
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# == compares row multisets and cannot see a formula go missing, so it called
|
|
422
|
+
# the partition exact while the derived dimensions were being dropped. === is
|
|
423
|
+
# the operator which compares the formula names too.
|
|
424
|
+
it "inverts the partition exactly, formulae and all" do
|
|
425
|
+
source = readings
|
|
426
|
+
_(source.group_by(:station).as_detail(:station)).must_be :===, source
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it "folds the members' formulae, a later member winning the name" do
|
|
430
|
+
first = Namo.new([{a: 1}], name: :first)
|
|
431
|
+
first[:b] = proc{|row| :from_first}
|
|
432
|
+
second = Namo.new([{a: 2}], name: :second)
|
|
433
|
+
second[:b] = proc{|row| :from_second}
|
|
434
|
+
collection = Namo::Collection.new
|
|
435
|
+
collection << first << second
|
|
436
|
+
_(collection.detail.values(:b)).must_equal [:from_second, :from_second]
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# A summary's rows are reductions, holding neither the dimensions the members'
|
|
440
|
+
# formulae read nor the rows they read them from, so it carries none.
|
|
441
|
+
it "leaves the summary without them" do
|
|
442
|
+
_(readings.group_by(:station).summary(:high_temp).derived_dimensions).must_equal []
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
350
446
|
describe "#inspect" do
|
|
351
447
|
def collection_of(members, rows_each)
|
|
352
448
|
rows = (1..members).flat_map{|member| (1..rows_each).map{|row| {group: "g#{member}", n: row}}}
|
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
|