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 +4 -4
- data/CHANGELOG +11 -0
- data/README.md +41 -1
- data/lib/Namo/NegatedDimension.rb +14 -0
- data/lib/Namo/Row.rb +36 -0
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/Symbol.rb +8 -0
- data/lib/namo.rb +18 -36
- data/namo.gemspec +14 -5
- data/test/Namo/NegatedDimension_test.rb +13 -0
- data/test/Namo/Row_test.rb +69 -0
- data/test/{namo_test.rb → Namo_test.rb} +47 -0
- data/test/Symbol_test.rb +16 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0cb29939a05de5108211f0d9f89b8c6b3cd0826433199f3f4014f1201a28fee
|
|
4
|
+
data.tar.gz: d628a872b860561da5c9f02867123864508c34c3311e7d9962457146226d1071
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
|
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
data/lib/Symbol.rb
ADDED
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
30
|
-
|
|
31
|
-
|
|
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
|
data/test/Symbol_test.rb
ADDED
|
@@ -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.
|
|
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/
|
|
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
|