monolens 0.4.0 → 0.5.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/lib/monolens/command.rb +12 -3
- data/lib/monolens/core/mapping.rb +22 -2
- data/lib/monolens/lens.rb +5 -5
- data/lib/monolens/object/select.rb +43 -15
- data/lib/monolens/object/transform.rb +1 -1
- data/lib/monolens/version.rb +1 -1
- data/spec/monolens/core/test_mapping.rb +29 -12
- data/spec/monolens/object/test_select.rb +86 -2
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2a9267753f8725ed91fc71b3c9b995a11d3b0c1
|
4
|
+
data.tar.gz: 4aaaf0247bcb4f2195052a416ebe7742bfd2b72a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99df61206eb26141c13a7a364fc9f5af97ade23d9a50e92e82cd8b2980cb88790e36402a852116d800a2caebe71af7b1bb90e4ad3dc8fdd450360d1ce63fe5e6
|
7
|
+
data.tar.gz: a93491cec013f9993299ee227c2055c0c93a9f224c5da6a2526afbb95fe2516bf1b29250670c769667cd93bb018e613e3ae21b98d545a62109602e2fa2c1f843
|
data/lib/monolens/command.rb
CHANGED
@@ -47,10 +47,19 @@ module Monolens
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def read_file(file)
|
50
|
-
content = ::File.read(file)
|
51
50
|
case ::File.extname(file)
|
52
|
-
when /json$/
|
53
|
-
|
51
|
+
when /json$/
|
52
|
+
content = ::File.read(file)
|
53
|
+
JSON.parse(content)
|
54
|
+
when /ya?ml$/
|
55
|
+
content = ::File.read(file)
|
56
|
+
YAML.safe_load(content)
|
57
|
+
when /csv$/
|
58
|
+
require 'bmg'
|
59
|
+
Bmg.csv(file).to_a
|
60
|
+
when /xlsx?$/
|
61
|
+
require 'bmg'
|
62
|
+
Bmg.excel(file).to_a
|
54
63
|
else
|
55
64
|
fail!("Unable to use #{file}")
|
56
65
|
end
|
@@ -5,11 +5,31 @@ module Monolens
|
|
5
5
|
|
6
6
|
def call(arg, world = {})
|
7
7
|
option(:values, {}).fetch(arg) do
|
8
|
-
|
8
|
+
on_missing(arg, world)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
9
13
|
|
10
|
-
|
14
|
+
def on_missing(arg, world)
|
15
|
+
strategy = option(:on_missing, :fail)
|
16
|
+
case strategy.to_sym
|
17
|
+
when :fail
|
18
|
+
fail!("Unrecognized value `#{arg}`", world)
|
19
|
+
when :default
|
20
|
+
option(:default, nil)
|
21
|
+
when :null
|
22
|
+
nil
|
23
|
+
when :fallback
|
24
|
+
missing_fallback = ->(arg, world) do
|
25
|
+
raise Monolens::Error, "Unexpected missing fallback handler"
|
26
|
+
end
|
27
|
+
option(:fallback, missing_fallback).call(self, arg, world)
|
28
|
+
else
|
29
|
+
raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
|
11
30
|
end
|
12
31
|
end
|
32
|
+
private :on_missing
|
13
33
|
end
|
14
34
|
end
|
15
35
|
end
|
data/lib/monolens/lens.rb
CHANGED
@@ -11,6 +11,11 @@ module Monolens
|
|
11
11
|
end
|
12
12
|
attr_reader :options
|
13
13
|
|
14
|
+
def fail!(msg, world)
|
15
|
+
location = world[:location]&.to_a || []
|
16
|
+
raise Monolens::LensError.new(msg, location)
|
17
|
+
end
|
18
|
+
|
14
19
|
protected
|
15
20
|
|
16
21
|
def option(name, default = nil)
|
@@ -58,10 +63,5 @@ module Monolens
|
|
58
63
|
|
59
64
|
fail!("Array expected, got #{arg.class}", world)
|
60
65
|
end
|
61
|
-
|
62
|
-
def fail!(msg, world)
|
63
|
-
location = world[:location]&.to_a || []
|
64
|
-
raise Monolens::LensError.new(msg, location)
|
65
|
-
end
|
66
66
|
end
|
67
67
|
end
|
@@ -9,20 +9,12 @@ module Monolens
|
|
9
9
|
result = {}
|
10
10
|
is_symbol = arg.keys.any?{|k| k.is_a?(Symbol) }
|
11
11
|
defn.each_pair do |new_attr, selector|
|
12
|
+
new_attr = is_symbol ? new_attr.to_sym : new_attr.to_s
|
13
|
+
|
12
14
|
deeper(world, new_attr) do |w|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
actual, fetched = fetch_on(old_attr, arg)
|
17
|
-
if actual.nil?
|
18
|
-
on_missing(old_attr, values, w)
|
19
|
-
else
|
20
|
-
values << fetched
|
21
|
-
end
|
22
|
-
end
|
23
|
-
new_attr = is_symbol ? new_attr.to_sym : new_attr.to_s
|
24
|
-
unless values.empty?
|
25
|
-
result[new_attr] = is_array ? values : values.first
|
15
|
+
catch (:skip) do
|
16
|
+
value = do_select(arg, selector, w)
|
17
|
+
result[new_attr] = value
|
26
18
|
end
|
27
19
|
end
|
28
20
|
end
|
@@ -31,6 +23,42 @@ module Monolens
|
|
31
23
|
|
32
24
|
private
|
33
25
|
|
26
|
+
def do_select(arg, selector, world)
|
27
|
+
if selector.is_a?(::Array)
|
28
|
+
do_array_select(arg, selector, world)
|
29
|
+
else
|
30
|
+
do_single_select(arg, selector, world)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_array_select(arg, selector, world)
|
35
|
+
case option(:strategy, :all).to_sym
|
36
|
+
when :all
|
37
|
+
selector.each_with_object([]) do |old_attr, values|
|
38
|
+
catch (:skip) do
|
39
|
+
values << do_single_select(arg, old_attr, world)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
when :first
|
43
|
+
selector.each do |old_attr|
|
44
|
+
actual, fetched = fetch_on(old_attr, arg)
|
45
|
+
return fetched if actual
|
46
|
+
end
|
47
|
+
on_missing(selector.first, [], world).first
|
48
|
+
else
|
49
|
+
raise Monolens::Error, "Unexpected strategy `#{strategy}`"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def do_single_select(arg, selector, world)
|
54
|
+
actual, fetched = fetch_on(selector, arg)
|
55
|
+
if actual.nil?
|
56
|
+
on_missing(selector, [], world).first
|
57
|
+
else
|
58
|
+
fetched
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
34
62
|
def defn
|
35
63
|
defn = option(:defn, {})
|
36
64
|
defn = defn.each_with_object({}) do |attr, memo|
|
@@ -47,9 +75,9 @@ module Monolens
|
|
47
75
|
when :null
|
48
76
|
values << nil
|
49
77
|
when :skip
|
50
|
-
|
78
|
+
throw :skip
|
51
79
|
else
|
52
|
-
raise Monolens::Error, "Unexpected
|
80
|
+
raise Monolens::Error, "Unexpected on_missing strategy `#{strategy}`"
|
53
81
|
end
|
54
82
|
end
|
55
83
|
private :on_missing
|
data/lib/monolens/version.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Monolens, 'core.mapping' do
|
4
4
|
let(:mapping) do
|
5
|
-
{ 'values' => { 'todo' => 'open' }}
|
5
|
+
{ 'values' => { 'todo' => 'open' } }
|
6
6
|
end
|
7
7
|
|
8
8
|
context 'with default options' do
|
@@ -14,14 +14,16 @@ describe Monolens, 'core.mapping' do
|
|
14
14
|
expect(subject.call('todo')).to eql('open')
|
15
15
|
end
|
16
16
|
|
17
|
-
it '
|
18
|
-
expect
|
17
|
+
it 'raises if not found' do
|
18
|
+
expect {
|
19
|
+
subject.call('nosuchone')
|
20
|
+
}.to raise_error(Monolens::LensError)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
context '
|
24
|
+
context 'on_missing: default' do
|
23
25
|
subject do
|
24
|
-
Monolens.lens('core.mapping' => mapping.merge('default' => 'foo'))
|
26
|
+
Monolens.lens('core.mapping' => mapping.merge('on_missing' => 'default', 'default' => 'foo'))
|
25
27
|
end
|
26
28
|
|
27
29
|
it 'replaces the value by its mapped' do
|
@@ -33,19 +35,34 @@ describe Monolens, 'core.mapping' do
|
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
context '
|
38
|
+
context 'on_missing: null' do
|
37
39
|
subject do
|
38
|
-
Monolens.lens('core.mapping' => mapping.merge('
|
40
|
+
Monolens.lens('core.mapping' => mapping.merge('on_missing' => 'null'))
|
39
41
|
end
|
40
42
|
|
41
43
|
it 'replaces the value by its mapped' do
|
42
44
|
expect(subject.call('todo')).to eql('open')
|
43
45
|
end
|
44
46
|
|
45
|
-
it '
|
46
|
-
expect
|
47
|
-
|
48
|
-
|
47
|
+
it 'returns nil if missing' do
|
48
|
+
expect(subject.call('nosuchone')).to eql(nil)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'on_missing: fallback' do
|
53
|
+
subject do
|
54
|
+
Monolens.lens('core.mapping' => mapping.merge(
|
55
|
+
'on_missing' => 'fallback',
|
56
|
+
'fallback' => ->(lens, arg, world) { 'foo' }
|
57
|
+
))
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'replaces the value by its mapped' do
|
61
|
+
expect(subject.call('todo')).to eql('open')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns nil if missing' do
|
65
|
+
expect(subject.call('nosuchone')).to eql('foo')
|
49
66
|
end
|
50
67
|
end
|
51
68
|
|
@@ -54,7 +71,7 @@ describe Monolens, 'core.mapping' do
|
|
54
71
|
Monolens.lens({
|
55
72
|
'array.map' => {
|
56
73
|
:lenses => {
|
57
|
-
'core.mapping' => mapping.merge('
|
74
|
+
'core.mapping' => mapping.merge('on_missing' => 'fail')
|
58
75
|
}
|
59
76
|
}
|
60
77
|
})
|
@@ -75,6 +75,91 @@ describe Monolens, 'object.select' do
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
context 'when using strategy: first' do
|
79
|
+
subject do
|
80
|
+
Monolens.lens('object.select' => {
|
81
|
+
defn: {
|
82
|
+
name: [:firstname, :lastname],
|
83
|
+
status: :priority
|
84
|
+
},
|
85
|
+
strategy: 'first',
|
86
|
+
on_missing: on_missing
|
87
|
+
}.compact)
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'without on_missing' do
|
91
|
+
let(:on_missing) do
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'works as expected when first option is present' do
|
96
|
+
input = {
|
97
|
+
firstname: 'Bernard',
|
98
|
+
priority: 12
|
99
|
+
}
|
100
|
+
expected = {
|
101
|
+
name: 'Bernard',
|
102
|
+
status: 12
|
103
|
+
}
|
104
|
+
expect(subject.call(input)).to eql(expected)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'works as expected when second is present' do
|
108
|
+
input = {
|
109
|
+
lastname: 'Lambeau',
|
110
|
+
priority: 12
|
111
|
+
}
|
112
|
+
expected = {
|
113
|
+
name: 'Lambeau',
|
114
|
+
status: 12
|
115
|
+
}
|
116
|
+
expect(subject.call(input)).to eql(expected)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'fails when none is present' do
|
120
|
+
input = {
|
121
|
+
priority: 12
|
122
|
+
}
|
123
|
+
expect {
|
124
|
+
subject.call(input)
|
125
|
+
}.to raise_error(Monolens::Error)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'with on_missing: skip' do
|
130
|
+
let(:on_missing) do
|
131
|
+
:skip
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'works as expected when missing' do
|
135
|
+
input = {
|
136
|
+
priority: 12
|
137
|
+
}
|
138
|
+
expected = {
|
139
|
+
status: 12
|
140
|
+
}
|
141
|
+
expect(subject.call(input)).to eql(expected)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with on_missing: null' do
|
146
|
+
let(:on_missing) do
|
147
|
+
:null
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'works as expected when missing' do
|
151
|
+
input = {
|
152
|
+
priority: 12
|
153
|
+
}
|
154
|
+
expected = {
|
155
|
+
name: nil,
|
156
|
+
status: 12
|
157
|
+
}
|
158
|
+
expect(subject.call(input)).to eql(expected)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
78
163
|
context 'when using an array as selection' do
|
79
164
|
subject do
|
80
165
|
Monolens.lens('object.select' => {
|
@@ -186,7 +271,6 @@ describe Monolens, 'object.select' do
|
|
186
271
|
|
187
272
|
subject do
|
188
273
|
lens.call(input)
|
189
|
-
nil
|
190
274
|
rescue Monolens::LensError => ex
|
191
275
|
ex
|
192
276
|
end
|
@@ -196,7 +280,7 @@ describe Monolens, 'object.select' do
|
|
196
280
|
end
|
197
281
|
|
198
282
|
it 'correctly updates the location' do
|
199
|
-
expect(subject.location).to eql([
|
283
|
+
expect(subject.location).to eql(['status'])
|
200
284
|
end
|
201
285
|
end
|
202
286
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: monolens
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-05-
|
11
|
+
date: 2022-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bmg
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: Data transformations inspired by Cambria lenses
|
56
70
|
email: blambeau@gmail.com
|
57
71
|
executables: []
|