namo 0.2.0 → 0.3.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: 6841b3921358ce7eefdbfbdba3a62eafd74857223d9df86038f9cc8087bbb947
4
- data.tar.gz: bf05398ce78ea49683fbb7adb0b9989c6d6df86864a9bdf2373b156b14d8d5e7
3
+ metadata.gz: a0cb29939a05de5108211f0d9f89b8c6b3cd0826433199f3f4014f1201a28fee
4
+ data.tar.gz: d628a872b860561da5c9f02867123864508c34c3311e7d9962457146226d1071
5
5
  SHA512:
6
- metadata.gz: 2887c9dee76341b5e4ee75d50a1416475bd8c579d52117f67dcab81c9fe7c8cb1c50c08be7295db74aaf1f2d8727aa678b95d231d4c2e1d82a25b1ca050de61e
7
- data.tar.gz: d5c5a35ac078d60dcd1baf43e73b5be1aa1bf02ed27a65071fd146223402a812a8b9ff9b88e6be5e4e62b5b47966460df613994d79215405439cde96c1a63278
6
+ metadata.gz: 72b7db10583b8316b0e727b4af8404c2069533049c40e83a3974841b8a1822139898922492f721e31328632b32582c8795a50fb9a53262bee04fa5bc75672713
7
+ data.tar.gz: b269559c27dd1999ba2c3fb638c85abc0ee22530d984ffe620b2ac9e3fa1726655ac7c7d26c96273baf1bd1396df6ff0eeaeaf7c4e71753aee588eabdb09417f
data/CHANGELOG CHANGED
@@ -1,6 +1,17 @@
1
1
  CHANGELOG
2
2
  _________
3
3
 
4
+ 20260415
5
+ 0.3.0: + contraction
6
+
7
+ 1. + Namo::NegatedDimension: Wrapper for negated dimension names used in contraction.
8
+ 2. + Symbol#-@: Produce a NegatedDimension, enabling -:date syntax.
9
+ 3. ~ Namo#[]: Extend to recognise NegatedDimension for contraction (remove named dimensions, keep everything else). Raise ArgumentError when mixing projection and contraction.
10
+ 4. > Namo::Row: Extract to Namo/Row.rb.
11
+ 5. ~ namo_test.rb: Add tests for contraction, mixed-mode error, and contraction with selection and formulae. Split tests into one file per class: Row_test.rb, NegatedDimension_test.rb, Symbol_test.rb.
12
+ 6. ~ README.md: + Contraction section
13
+ 7. ~ Namo::VERSION: /0.2.0/0.3.0/
14
+
4
15
  20260414
5
16
  0.2.0: Include Enumerable.
6
17
 
data/README.md CHANGED
@@ -111,7 +111,47 @@ sales[:quarter, :price, product: 'Widget']
111
111
  # ]>
112
112
  ```
113
113
 
114
- Selection and projection always return a new Namo instance, so everything chains.
114
+ ### Contraction
115
+
116
+ Contraction is the complement of projection. Projection says "keep these dimensions"; contraction says "remove these dimensions, keep everything else":
117
+
118
+ ```ruby
119
+ sales[-:price, -:quantity]
120
+ # => #<Namo [
121
+ # {product: 'Widget', quarter: 'Q1'},
122
+ # {product: 'Widget', quarter: 'Q2'},
123
+ # {product: 'Gadget', quarter: 'Q1'},
124
+ # {product: 'Gadget', quarter: 'Q2'}
125
+ # ]>
126
+ ```
127
+
128
+ The `-:price` syntax uses unary minus on Symbol to produce a negated dimension. Mixing projection and contraction in the same call is an error — the two modes are mutually exclusive:
129
+
130
+ ```ruby
131
+ sales[:product, -:price] # => ArgumentError
132
+ ```
133
+
134
+ Selection and contraction can be chained:
135
+
136
+ ```ruby
137
+ sales[product: 'Widget'][-:price, -:quantity]
138
+ # => #<Namo [
139
+ # {product: 'Widget', quarter: 'Q1'},
140
+ # {product: 'Widget', quarter: 'Q2'}
141
+ # ]>
142
+ ```
143
+
144
+ Or combined in a single call (names before selectors):
145
+
146
+ ```ruby
147
+ sales[-:price, -:quantity, product: 'Widget']
148
+ # => #<Namo [
149
+ # {product: 'Widget', quarter: 'Q1'},
150
+ # {product: 'Widget', quarter: 'Q2'}
151
+ # ]>
152
+ ```
153
+
154
+ Selection, projection, and contraction always return a new Namo instance, so everything chains.
115
155
 
116
156
  ### Formulae
117
157
 
@@ -0,0 +1,14 @@
1
+ # Namo/NegatedDimension.rb
2
+ # Namo::NegatedDimension
3
+
4
+ class Namo
5
+ class NegatedDimension
6
+ attr_reader :name
7
+
8
+ private
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ end
13
+ end
14
+ end
data/lib/Namo/Row.rb ADDED
@@ -0,0 +1,36 @@
1
+ # Namo/Row.rb
2
+ # Namo::Row
3
+
4
+ class Namo
5
+ class Row
6
+ def [](name)
7
+ if @formulae.key?(name)
8
+ @formulae[name].call(self)
9
+ else
10
+ @row[name]
11
+ end
12
+ end
13
+
14
+ def match?(selections)
15
+ selections.all? do |dimension, coordinate|
16
+ case coordinate
17
+ when Array, Range
18
+ coordinate.include?(self[dimension])
19
+ else
20
+ self[dimension] == coordinate
21
+ end
22
+ end
23
+ end
24
+
25
+ def to_h
26
+ @row
27
+ end
28
+
29
+ private
30
+
31
+ def initialize(row, formulae)
32
+ @row = row
33
+ @formulae = formulae
34
+ end
35
+ end
36
+ end
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
data/lib/Symbol.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Symbol.rb
2
+ # Symbol#-@
3
+
4
+ class Symbol
5
+ def -@
6
+ Namo::NegatedDimension.new(self)
7
+ end
8
+ end
data/lib/namo.rb CHANGED
@@ -1,41 +1,13 @@
1
1
  # namo.rb
2
2
  # Namo
3
3
 
4
+ require_relative 'Namo/NegatedDimension'
5
+ require_relative 'Namo/Row'
6
+ require_relative 'Symbol'
7
+
4
8
  class Namo
5
9
  include Enumerable
6
10
 
7
- class Row
8
- def [](name)
9
- if @formulae.key?(name)
10
- @formulae[name].call(self)
11
- else
12
- @row[name]
13
- end
14
- end
15
-
16
- def match?(selections)
17
- selections.all? do |dimension, coordinate|
18
- case coordinate
19
- when Array, Range
20
- coordinate.include?(self[dimension])
21
- else
22
- self[dimension] == coordinate
23
- end
24
- end
25
- end
26
-
27
- def to_h
28
- @row
29
- end
30
-
31
- private
32
-
33
- def initialize(row, formulae)
34
- @row = row
35
- @formulae = formulae
36
- end
37
- end
38
-
39
11
  attr_accessor :data
40
12
  attr_accessor :formulae
41
13
 
@@ -53,16 +25,26 @@ class Namo
53
25
 
54
26
  def [](*names, **selections)
55
27
  rows = selections.any? ? select{|row| row.match?(selections)} : entries
56
- data = (
57
- if names.any?
28
+ negated, positive = names.partition{|n| n.is_a?(NegatedDimension)}
29
+ if negated.any? && positive.any?
30
+ raise ArgumentError, "cannot mix projection and contraction in a single call"
31
+ end
32
+ projected = (
33
+ if negated.any?
34
+ excluded = negated.map(&:name)
35
+ kept = dimensions - excluded
36
+ rows.map do |row|
37
+ kept.each_with_object({}){|name, hash| hash[name] = row[name]}
38
+ end
39
+ elsif positive.any?
58
40
  rows.map do |row|
59
- names.each_with_object({}){|name, hash| hash[name] = row[name]}
41
+ positive.each_with_object({}){|name, hash| hash[name] = row[name]}
60
42
  end
61
43
  else
62
44
  rows.map(&:to_h)
63
45
  end
64
46
  )
65
- self.class.new(data, formulae: @formulae.dup)
47
+ self.class.new(projected, formulae: @formulae.dup)
66
48
  end
67
49
 
68
50
  def []=(name, proc)
data/namo.gemspec CHANGED
@@ -1,5 +1,11 @@
1
1
  require_relative './lib/Namo/VERSION'
2
2
 
3
+ class Gem::Specification
4
+ def development_dependencies=(gems)
5
+ gems.each{|gem| add_development_dependency(*gem)}
6
+ end
7
+ end
8
+
3
9
  Gem::Specification.new do |spec|
4
10
  spec.name = 'namo'
5
11
  spec.version = Namo::VERSION
@@ -13,20 +19,23 @@ Gem::Specification.new do |spec|
13
19
  spec.license = 'MIT'
14
20
 
15
21
  spec.required_ruby_version = '>= 2.7'
22
+
16
23
  spec.require_paths = ['lib']
17
24
 
18
25
  spec.files = [
26
+ 'namo.gemspec',
19
27
  'CHANGELOG',
20
28
  'Gemfile',
21
- Dir['lib/**/*.rb'],
22
29
  'LICENSE',
23
- 'namo.gemspec',
24
30
  'Rakefile',
25
31
  'README.md',
32
+ Dir['lib/**/*.rb'],
26
33
  Dir['test/**/*.rb'],
27
34
  ].flatten
28
35
 
29
- spec.add_development_dependency 'minitest'
30
- spec.add_development_dependency 'minitest-spec-context'
31
- spec.add_development_dependency 'rake'
36
+ spec.development_dependencies = %w{
37
+ minitest
38
+ minitest-spec-context
39
+ rake
40
+ }
32
41
  end
@@ -0,0 +1,13 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest-spec-context'
3
+
4
+ require_relative '../../lib/namo'
5
+
6
+ describe Namo::NegatedDimension do
7
+ describe "#name" do
8
+ it "returns the original symbol" do
9
+ nd = Namo::NegatedDimension.new(:price)
10
+ _(nd.name).must_equal :price
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,69 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest-spec-context'
3
+
4
+ require_relative '../../lib/namo'
5
+
6
+ describe Namo::Row do
7
+ let(:row_data) do
8
+ {product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100}
9
+ end
10
+
11
+ let(:formulae) do
12
+ {}
13
+ end
14
+
15
+ let(:row) do
16
+ Namo::Row.new(row_data, formulae)
17
+ end
18
+
19
+ describe "#[]" do
20
+ it "returns raw data by dimension name" do
21
+ _(row[:product]).must_equal 'Widget'
22
+ _(row[:price]).must_equal 10.0
23
+ end
24
+
25
+ it "returns nil for missing dimensions" do
26
+ _(row[:missing]).must_be_nil
27
+ end
28
+
29
+ it "resolves formulae over raw data" do
30
+ formulae[:revenue] = proc{|r| r[:price] * r[:quantity]}
31
+ _(row[:revenue]).must_equal 1000.0
32
+ end
33
+
34
+ it "composes formulae" do
35
+ formulae[:revenue] = proc{|r| r[:price] * r[:quantity]}
36
+ formulae[:cost] = proc{|r| r[:quantity] * 4.0}
37
+ formulae[:profit] = proc{|r| r[:revenue] - r[:cost]}
38
+ _(row[:profit]).must_equal 600.0
39
+ end
40
+ end
41
+
42
+ describe "#match?" do
43
+ it "matches a single value" do
44
+ _(row.match?(product: 'Widget')).must_equal true
45
+ _(row.match?(product: 'Gadget')).must_equal false
46
+ end
47
+
48
+ it "matches an array of values" do
49
+ _(row.match?(product: ['Widget', 'Gadget'])).must_equal true
50
+ _(row.match?(product: ['Gadget'])).must_equal false
51
+ end
52
+
53
+ it "matches a range" do
54
+ _(row.match?(price: 5.0..15.0)).must_equal true
55
+ _(row.match?(price: 20.0..30.0)).must_equal false
56
+ end
57
+
58
+ it "matches multiple dimensions" do
59
+ _(row.match?(product: 'Widget', quarter: 'Q1')).must_equal true
60
+ _(row.match?(product: 'Widget', quarter: 'Q2')).must_equal false
61
+ end
62
+ end
63
+
64
+ describe "#to_h" do
65
+ it "returns the underlying row hash" do
66
+ _(row.to_h).must_equal row_data
67
+ end
68
+ end
69
+ end
@@ -85,6 +85,53 @@ describe Namo do
85
85
  _(result.to_a).must_equal [{price: 10.0}, {price: 10.0}]
86
86
  end
87
87
  end
88
+
89
+ context "contraction" do
90
+ it "removes named dimensions" do
91
+ result = sales[-:price, -:quantity]
92
+ _(result.dimensions).must_equal [:product, :quarter]
93
+ _(result.to_a.count).must_equal 4
94
+ _(result.to_a.first).must_equal({product: 'Widget', quarter: 'Q1'})
95
+ end
96
+
97
+ it "removes a single dimension" do
98
+ result = sales[-:price]
99
+ _(result.dimensions).must_equal [:product, :quarter, :quantity]
100
+ _(result.to_a.count).must_equal 4
101
+ end
102
+
103
+ it "raises when mixing projection and contraction" do
104
+ _ { sales[:product, -:price] }.must_raise ArgumentError
105
+ end
106
+
107
+ it "carries formulae through contraction" do
108
+ sales[:label] = proc{|r| "#{r[:product]}-#{r[:quarter]}"}
109
+ result = sales[-:price, -:quantity]
110
+ _(result.map{|row| row[:label]}).must_equal [
111
+ 'Widget-Q1', 'Widget-Q2', 'Gadget-Q1', 'Gadget-Q2'
112
+ ]
113
+ end
114
+ end
115
+
116
+ context "selection and contraction" do
117
+ it "can use them together" do
118
+ result = sales[-:price, -:quantity, product: 'Widget']
119
+ _(result.to_a.count).must_equal 2
120
+ _(result.to_a).must_equal [
121
+ {product: 'Widget', quarter: 'Q1'},
122
+ {product: 'Widget', quarter: 'Q2'}
123
+ ]
124
+ end
125
+
126
+ it "can chain them" do
127
+ result = sales[product: 'Widget'][-:price, -:quantity]
128
+ _(result.to_a.count).must_equal 2
129
+ _(result.to_a).must_equal [
130
+ {product: 'Widget', quarter: 'Q1'},
131
+ {product: 'Widget', quarter: 'Q2'}
132
+ ]
133
+ end
134
+ end
88
135
  end
89
136
 
90
137
  describe "#[]= formulae" do
@@ -0,0 +1,16 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest-spec-context'
3
+
4
+ require_relative '../lib/namo'
5
+
6
+ describe Symbol do
7
+ describe "#-@" do
8
+ it "returns a NegatedDimension" do
9
+ _(-:price).must_be_kind_of Namo::NegatedDimension
10
+ end
11
+
12
+ it "preserves the symbol name" do
13
+ _((-:price).name).must_equal :price
14
+ end
15
+ end
16
+ 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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -64,10 +64,16 @@ files:
64
64
  - LICENSE
65
65
  - README.md
66
66
  - Rakefile
67
+ - lib/Namo/NegatedDimension.rb
68
+ - lib/Namo/Row.rb
67
69
  - lib/Namo/VERSION.rb
70
+ - lib/Symbol.rb
68
71
  - lib/namo.rb
69
72
  - namo.gemspec
70
- - test/namo_test.rb
73
+ - test/Namo/NegatedDimension_test.rb
74
+ - test/Namo/Row_test.rb
75
+ - test/Namo_test.rb
76
+ - test/Symbol_test.rb
71
77
  homepage: https://github.com/thoran/namo
72
78
  licenses:
73
79
  - MIT