namo 0.31.0 → 0.31.2

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: 1241332321c6f9d7643932d1b53949bfb12eb8596b1e388c206a9fad705efaf7
4
- data.tar.gz: 00c558afd1fe979599820a39539f3216b496557a69f94229a59fc45c48cfd062
3
+ metadata.gz: 820a3c18db30a81cef333ad6b6bd95cd3129610d8d237a0a4f14554d23bdd2e9
4
+ data.tar.gz: 8b0c94985ae46c47e8b129eecd7fe7eba7d30f499aea917b2c9e80b1edf2ad7c
5
5
  SHA512:
6
- metadata.gz: 74b61473e59179fcd559d52eb98435ab9127499c17403174c1505f5076d1c6d2dd1f829ea8a69403da948709d047d42603bb1fd6bc32205bfb1bb3b0459d47c8
7
- data.tar.gz: c9690afd20a5ab0de834d9dcca3b2a310e4da3e4811b0a29545e60290f1cb62f7571e8c177bacd405b380e8bfe6f78358a075c9c2eb5a4519654843c90ddaf5a
6
+ metadata.gz: a5631205eb63c196e4ba9d6b8cf5bf54807e61a462e55645e216aeb5515d767934413c226a0cb567ae0637002e6c3a80a3490c6db7c94397ae17da2c42ae8c8f
7
+ data.tar.gz: 829d09221f044a4572ff7b97bc76c3a84cd7c8ce8e9e2ee8656b2ac5c82a3753d2f793223bdf3125a0e22d925bf86e19078680b78d6dcde7077248aef7488740
data/CHANGELOG CHANGED
@@ -1,5 +1,24 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 20260824
4
+
5
+ 0.31.2: ~ Namo#inspect: the derived: suffix names only what the rows do not carry.
6
+
7
+ 1. ~ Namo#inspect: the derived: suffix names only the derived dimensions the rendered rows do not already carry, and is omitted where they all do. A formula which raises, a parameterised one which cannot be materialised without its arguments, and any formula on a Namo with no rows to render, are named as before, being nowhere else in the output. Namo::Collection renders no rows and so names every one of its own. Namo::Row is unchanged, naming its formulae in every case.
8
+ 2. ~ README.md: the Inspection output section, and the block whose suffix goes.
9
+ 3. ~ ROADMAP.md: + 0.31.2.
10
+ 4. ~ Namo::VERSION: /0.31.1/0.31.2/
11
+
12
+
13
+ 0.31.1: ~ Namo#inspect, Namo::Row#inspect: derived dimensions render their values.
14
+
15
+ 1. ~ Namo#inspect: a derived dimension renders with its value, in among the stored ones, rather than being named only. Where there is no value to show it is omitted and the derived: suffix names it alone: a formula which raises, and a parameterised one which cannot be materialised without its arguments.
16
+ 2. ~ Namo::Row#inspect: the same.
17
+ 3. ~ README.md: the Inspection output section rewritten, and the two blocks whose rendering changes.
18
+ 4. ~ ROADMAP.md: + 0.31.1.
19
+ 5. ~ Namo::VERSION: /0.31.0/0.31.1/
20
+
21
+
3
22
  ## 20260822
4
23
 
5
24
  0.31.0: + Namo#inspect, Namo::Collection#inspect, Namo::Row#inspect, and Namo::Formulae#inspect.
data/README.md CHANGED
@@ -972,29 +972,33 @@ sales[:product, :quarter, :revenue].to_h
972
972
 
973
973
  ### Inspection output
974
974
 
975
- `inspect` renders the class, the stored rows, and the names of any derived dimensions:
975
+ `inspect` renders the class and the stored rows, with any derived dimensions among them:
976
976
 
977
977
  ```ruby
978
978
  sales.inspect
979
979
  # => #<Namo [
980
- # {product: "Widget", quarter: "Q1", price: 10.0, quantity: 100},
981
- # {product: "Widget", quarter: "Q2", price: 10.0, quantity: 150},
982
- # {product: "Gadget", quarter: "Q1", price: 25.0, quantity: 40},
983
- # {product: "Gadget", quarter: "Q2", price: 25.0, quantity: 60}
984
- # ] derived: [:revenue]>
980
+ # {product: "Widget", quarter: "Q1", price: 10.0, quantity: 100, revenue: 1000.0},
981
+ # {product: "Widget", quarter: "Q2", price: 10.0, quantity: 150, revenue: 1500.0},
982
+ # {product: "Gadget", quarter: "Q1", price: 25.0, quantity: 40, revenue: 1000.0},
983
+ # {product: "Gadget", quarter: "Q2", price: 25.0, quantity: 60, revenue: 1500.0}
984
+ # ]>
985
985
  ```
986
986
 
987
987
  A name, where the Namo carries one, follows the class: `#<Namo :sales [`. A subclass reports its own name in place of `Namo`.
988
988
 
989
- Derived dimensions are *named*, not evaluated. A console calls `inspect` on every result, so evaluating there would cost a pass over the data on each one, would raise whatever a formula raises, and could not render a parameterised formula at all. Ask through `values`, `coordinates`, or a projection when you want the values.
989
+ A derived dimension shows its value, in among the stored ones, so a computed dimension renders exactly as a stored one does — which is the claim the library makes about it. Where there is no value to show, the rendering omits it rather than failing, and the `derived:` suffix names it: a formula which raises, a parameterised one which cannot be materialised without its arguments, and any formula at all on a Namo with no rows to render.
990
+
991
+ The suffix carries only those. Where every derived dimension rendered a value it has nothing left to say and does not appear. Ask `derived_dimensions` when you want the distinction named.
992
+
993
+ Evaluation is bounded by the row cap rather than by the data — at most ten rows are rendered, so at most ten evaluations per formula. A collection-scoped formula makes each of those a pass over the Namo, so inspecting a large one that carries such a formula is not free.
990
994
 
991
995
  Rows beyond `Namo::INSPECTED_ROWS` — ten — are elided with a count of the remainder, so the output stays bounded however large the data: a thousand-row Namo renders its first ten rows and then `... 990 more rows`.
992
996
 
993
- A `Row` renders itself and its formula names, never the Namo it came from. A `Collection` renders its member names and its row count, the members being its substance and the data view derived from them.
997
+ A `Row` renders itself, its derived values and its formula names in every case, having no `derived_dimensions` of its own to be asked, and never the Namo it came from. A `Collection` renders its member names and its row count, the members being its substance and the data view derived from them.
994
998
 
995
999
  ```ruby
996
1000
  sales.first
997
- # => #<Namo::Row {product: "Widget", quarter: "Q1", price: 10.0, quantity: 100} derived: [:revenue]>
1001
+ # => #<Namo::Row {product: "Widget", quarter: "Q1", price: 10.0, quantity: 100, revenue: 1000.0} derived: [:revenue]>
998
1002
 
999
1003
  sales.group_by(:quarter)
1000
1004
  # => #<Namo::Collection members: ["Q1", "Q2"], 4 rows>
data/lib/Namo/Row.rb CHANGED
@@ -44,11 +44,26 @@ class Namo
44
44
  end
45
45
 
46
46
  def inspect
47
- "#<#{self.class} #{@row.inspect}#{inspected_derived}>"
47
+ "#<#{self.class} #{inspected_row}#{inspected_derived}>"
48
48
  end
49
49
 
50
50
  private
51
51
 
52
+ def inspected_row
53
+ @row.merge(inspected_derivations).inspect
54
+ end
55
+
56
+ # As Namo#inspected_derivations: the value where there is one to show, and
57
+ # the derived list to name it either way.
58
+ def inspected_derivations
59
+ @formulae.keys.each_with_object({}) do |dimension, derived|
60
+ next if @formulae.required_parameter_count(dimension) > 2
61
+ derived[dimension] = self[dimension]
62
+ rescue StandardError
63
+ next
64
+ end
65
+ end
66
+
52
67
  def inspected_derived
53
68
  @formulae.keys.empty? ? '' : " derived: #{@formulae.keys.inspect}"
54
69
  end
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.31.0'
5
+ VERSION = '0.31.2'
6
6
  end
data/lib/namo.rb CHANGED
@@ -250,7 +250,8 @@ class Namo
250
250
  end
251
251
 
252
252
  def inspect
253
- "#<#{self.class}#{inspected_name}#{inspected_rows}#{inspected_derived}>"
253
+ rendered = @data.first(INSPECTED_ROWS).map{|row| [row, inspected_derivations(row)]}
254
+ "#<#{self.class}#{inspected_name}#{inspected_rows(rendered)}#{inspected_derived(rendered)}>"
254
255
  end
255
256
 
256
257
  protected
@@ -278,15 +279,35 @@ class Namo
278
279
  # The rows as they are stored, never the derived values: inspect is called
279
280
  # for every result in a console, and evaluating a formula there would cost a
280
281
  # pass over the data per access and raise whatever the formula raises.
281
- def inspected_rows
282
+ def inspected_rows(rendered)
282
283
  return ' []' if @data.empty?
283
- shown = @data.first(INSPECTED_ROWS).map{|row| " #{row.inspect}"}.join(",\n")
284
+ shown = rendered.map{|row, derived| " #{row.merge(derived).inspect}"}.join(",\n")
284
285
  shown += "\n ... #{@data.length - INSPECTED_ROWS} more rows" if @data.length > INSPECTED_ROWS
285
286
  " [\n#{shown}\n]"
286
287
  end
287
288
 
288
- def inspected_derived
289
- derived_dimensions.empty? ? '' : " derived: #{derived_dimensions.inspect}"
289
+ # A derived dimension is shown with its value, in among the stored ones. A
290
+ # formula wanting arguments has no value to show without them, and one which
291
+ # raises has none to show either; the derived list still names both.
292
+ def inspected_derivations(row)
293
+ subject = Row.new(row, @formulae, self)
294
+ derived_dimensions.each_with_object({}) do |dimension, derived|
295
+ next if @formulae.required_parameter_count(dimension) > 2
296
+ derived[dimension] = subject[dimension]
297
+ rescue StandardError
298
+ next
299
+ end
300
+ end
301
+
302
+ # Only those the rows above do not already carry. A formula which raises, and a
303
+ # parameterised one which cannot be materialised without its arguments, render
304
+ # no value and would otherwise be nowhere in the output at all. A Collection
305
+ # renders no rows and so passes none, naming every one of them.
306
+ def inspected_derived(rendered = [])
307
+ unshown = derived_dimensions.reject do |dimension|
308
+ rendered.any? && rendered.all?{|_, derived| derived.key?(dimension)}
309
+ end
310
+ unshown.empty? ? '' : " derived: #{unshown.inspect}"
290
311
  end
291
312
 
292
313
  def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
@@ -465,9 +465,15 @@ describe Namo::Row do
465
465
  _(Namo::Row.new({a: 1}, Namo::Formulae.new).inspect).must_equal "#<Namo::Row {a: 1}>"
466
466
  end
467
467
 
468
- it "names derived dimensions without evaluating them" do
468
+ it "shows a derived dimension with its value, in among the stored ones" do
469
469
  formulae = Namo::Formulae.new
470
- formulae[:b] = proc{|row| raise 'never called'}
470
+ formulae[:b] = proc{|row| row[:a] + 1}
471
+ _(Namo::Row.new({a: 1}, formulae).inspect).must_equal "#<Namo::Row {a: 1, b: 2} derived: [:b]>"
472
+ end
473
+
474
+ it "names a derived dimension whose formula raises, without a value" do
475
+ formulae = Namo::Formulae.new
476
+ formulae[:b] = proc{|row| raise 'no value to show'}
471
477
  _(Namo::Row.new({a: 1}, formulae).inspect).must_equal "#<Namo::Row {a: 1} derived: [:b]>"
472
478
  end
473
479
 
@@ -0,0 +1,42 @@
1
+ # test/console_test.rb
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+
6
+ require_relative '../lib/namo'
7
+ require_relative '../script/fixtures'
8
+
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.
13
+
14
+ describe 'script/console' do
15
+ def console(*expressions)
16
+ root = File.expand_path('..', __dir__)
17
+ IO.popen([File.join(root, 'script', 'console'), '--prompt', 'simple'],
18
+ 'r+', chdir: root, err: [:child, :out]) do |io|
19
+ io.puts(expressions, 'exit')
20
+ io.close_write
21
+ io.read
22
+ end
23
+ end
24
+
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
+ end
29
+
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.to_a == ' + expected.inspect)).must_match(/true/)
37
+ end
38
+
39
+ it "starts without echoing its own source" do
40
+ _(console('1')).wont_match(/binding\.irb/)
41
+ end
42
+ end
data/test/demo_test.rb ADDED
@@ -0,0 +1,88 @@
1
+ # test/demo_test.rb
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+
6
+ require_relative '../lib/namo'
7
+
8
+ # script/demo is 300-odd lines exercising selection, projection, formulae,
9
+ # group_by, summary, the operators and inspect, and nothing else runs it. These
10
+ # assert only that each section completes, which is the difference between a
11
+ # demonstration which rots quietly and one which fails the suite when the library
12
+ # moves under it.
13
+ #
14
+ # A process apiece, since the script is a program rather than a library and its
15
+ # sections share a binding within a run.
16
+
17
+ describe 'script/demo' do
18
+ def demo(*sections)
19
+ root = File.expand_path('..', __dir__)
20
+ IO.popen([File.join(root, 'script', 'demo'), *sections],
21
+ chdir: root, err: [:child, :out]){|io| io.read}
22
+ end
23
+
24
+ def sections
25
+ @sections ||= demo('--help').scan(/^ [* ] ([a-z_]+)$/).flatten
26
+ end
27
+
28
+ def cut
29
+ @cut ||= demo('--help').scan(/^ \* ([a-z_]+)$/).flatten
30
+ end
31
+
32
+ it "lists its sections" do
33
+ _(sections).wont_be_empty
34
+ _(sections).must_include 'ingestion'
35
+ end
36
+
37
+ it "runs every section without raising" do
38
+ failed = sections.reject do |section|
39
+ demo(section)
40
+ $?.success?
41
+ end
42
+ _(failed).must_be_empty
43
+ end
44
+
45
+ it "has a talk cut, and it is a subset of the sections" do
46
+ _(cut).wont_be_empty
47
+ _(cut - sections).must_be_empty
48
+ end
49
+
50
+ it "runs the talk cut" do
51
+ demo('talk')
52
+ _($?.success?).must_equal true
53
+ end
54
+
55
+ # The padding is what holds a section title in one place on the screen, and it
56
+ # can only pad down to the constants. A section grown past them would silently
57
+ # start pushing the next one about. Rows rather than lines, since a line wider
58
+ # than the window costs more than one of them.
59
+ #
60
+ # The cut rather than every section: those are the ones which have to fit on the
61
+ # day, and the rest are reference, free to run long and be scrolled.
62
+ it "has no section in the talk cut taller than the slide height" do
63
+ source = File.read(File.join(File.expand_path('..', __dir__), 'script', 'demo'))
64
+ height, width = %w[SLIDE_HEIGHT SLIDE_WIDTH].map{|name| source[/^#{name} = (\d+)$/, 1].to_i}
65
+ _([height, width].min).must_be :>, 0
66
+ overlong = cut.select do |section|
67
+ demo(section).lines.sum{|line| [(line.chomp.size / width.to_f).ceil, 1].max} > height
68
+ end
69
+ _(overlong).must_be_empty
70
+ end
71
+
72
+ it "runs the whole script" do
73
+ demo
74
+ _($?.success?).must_equal true
75
+ end
76
+
77
+ it "refuses a section it does not have" do
78
+ demo('nonexistent')
79
+ _($?.success?).must_equal false
80
+ end
81
+
82
+ it "needs nothing but the gem's own dependencies" do
83
+ root = File.expand_path('..', __dir__)
84
+ output = IO.popen({'RUBYLIB' => nil}, [File.join(root, 'script', 'demo'), 'ingestion'],
85
+ chdir: root, err: [:child, :out]){|io| io.read}
86
+ _(output).wont_match(/LoadError/)
87
+ end
88
+ end
data/test/namo_test.rb CHANGED
@@ -3231,9 +3231,33 @@ describe Namo do
3231
3231
  _(Namo.new([]).inspect).must_equal "#<Namo []>"
3232
3232
  end
3233
3233
 
3234
- it "names derived dimensions without evaluating them" do
3234
+ it "shows a derived dimension with its value, in among the stored ones" do
3235
3235
  namo = Namo.new([{a: 1}])
3236
- namo[:b] = proc{|row| raise 'never called'}
3236
+ namo[:b] = proc{|row| row[:a] + 1}
3237
+ _(namo.inspect).must_equal "#<Namo [\n {a: 1, b: 2}\n]>"
3238
+ end
3239
+
3240
+ it "shows a collection-scoped derived value" do
3241
+ namo = Namo.new([{a: 1}, {a: 2}])
3242
+ namo[:total] = proc{|row, namo| namo.values(:a).sum}
3243
+ _(namo.inspect).must_equal "#<Namo [\n {a: 1, total: 3},\n {a: 2, total: 3}\n]>"
3244
+ end
3245
+
3246
+ it "names a derived dimension on a Namo with no rows, there being nowhere else to learn of it" do
3247
+ namo = Namo.new([])
3248
+ namo[:b] = proc{|row| row[:a] + 1}
3249
+ _(namo.inspect).must_equal "#<Namo [] derived: [:b]>"
3250
+ end
3251
+
3252
+ it "names a derived dimension whose formula raises, without a value" do
3253
+ namo = Namo.new([{a: 1}])
3254
+ namo[:b] = proc{|row| raise 'no value to show'}
3255
+ _(namo.inspect).must_equal "#<Namo [\n {a: 1}\n] derived: [:b]>"
3256
+ end
3257
+
3258
+ it "names a derived dimension whose formula wants arguments, without a value" do
3259
+ namo = Namo.new([{a: 1}])
3260
+ namo[:b] = proc{|row, namo, factor| row[:a] * factor}
3237
3261
  _(namo.inspect).must_equal "#<Namo [\n {a: 1}\n] derived: [:b]>"
3238
3262
  end
3239
3263
 
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.0
4
+ version: 0.31.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -79,6 +79,8 @@ files:
79
79
  - test/Namo/NegatedDimension_test.rb
80
80
  - test/Namo/Row_test.rb
81
81
  - test/Symbol_test.rb
82
+ - test/console_test.rb
83
+ - test/demo_test.rb
82
84
  - test/namo_test.rb
83
85
  homepage: https://github.com/thoran/namo
84
86
  licenses: