monolens 0.4.0 → 0.5.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 +4 -4
- data/README.md +2 -0
- data/lib/monolens/check/not_empty.rb +26 -0
- data/lib/monolens/check.rb +11 -0
- data/lib/monolens/coerce/date.rb +1 -1
- data/lib/monolens/coerce/date_time.rb +1 -1
- data/lib/monolens/command.rb +12 -3
- data/lib/monolens/core/mapping.rb +22 -2
- data/lib/monolens/error_handler.rb +10 -0
- 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 +2 -2
- data/lib/monolens.rb +2 -1
- data/spec/monolens/check/test_not_empty.rb +50 -0
- data/spec/monolens/core/test_mapping.rb +29 -12
- data/spec/monolens/object/test_select.rb +86 -2
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b96981f78af52f468676e9de0aeb3f79e79ab66
|
4
|
+
data.tar.gz: fb6445fc49c216be5868b27bef78cdfba79a1264
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 110e2c7da4dcc2195599813adf977af62d1698255339d0ba087bb1b1a7fb886ffd90bd12ded6eeb720547c327294eec758ae462f7e98024fffaecddb8e56c2cd
|
7
|
+
data.tar.gz: 1621c07cbba992d5a74c0811e420dd8489ff1c064eaf26bd698404b61bc3ab64ca60da65827ac3a061d0dfad28c483d3e2c07f83e50b4ec5eb22ea944b91d134
|
data/README.md
CHANGED
@@ -102,6 +102,8 @@ coerce.integer - Coerces the input value to an integer
|
|
102
102
|
array.compact - Removes null from the input array
|
103
103
|
array.join - Joins values of the input array as a string
|
104
104
|
array.map - Apply a lens to each member of an Array
|
105
|
+
|
106
|
+
check.notEmpty - Throws an error if the input is null or empty
|
105
107
|
```
|
106
108
|
|
107
109
|
## Public API
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Monolens
|
4
|
+
module Check
|
5
|
+
class NotEmpty
|
6
|
+
include Lens
|
7
|
+
|
8
|
+
def call(arg, world = {})
|
9
|
+
if arg.nil?
|
10
|
+
do_fail!(arg, world)
|
11
|
+
elsif arg.respond_to?(:empty?) && arg.empty?
|
12
|
+
do_fail!(arg, world)
|
13
|
+
else
|
14
|
+
arg
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def do_fail!(arg, world)
|
21
|
+
message = option(:message, 'Input may not be empty')
|
22
|
+
fail!(message, world)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/monolens/coerce/date.rb
CHANGED
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
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Monolens
|
2
2
|
class ErrorHandler
|
3
|
+
include Enumerable
|
4
|
+
|
3
5
|
def initialize
|
4
6
|
@errors = []
|
5
7
|
end
|
@@ -8,6 +10,14 @@ module Monolens
|
|
8
10
|
@errors << error
|
9
11
|
end
|
10
12
|
|
13
|
+
def each(&bl)
|
14
|
+
@errors.each(&bl)
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
@errors.size
|
19
|
+
end
|
20
|
+
|
11
21
|
def empty?
|
12
22
|
@errors.empty?
|
13
23
|
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
data/lib/monolens.rb
CHANGED
@@ -19,6 +19,7 @@ module Monolens
|
|
19
19
|
require_relative 'monolens/array'
|
20
20
|
require_relative 'monolens/object'
|
21
21
|
require_relative 'monolens/coerce'
|
22
|
+
require_relative 'monolens/check'
|
22
23
|
|
23
24
|
def load_file(file)
|
24
25
|
Monolens::File.new(YAML.safe_load(::File.read(file)))
|
@@ -59,7 +60,7 @@ module Monolens
|
|
59
60
|
raise "Invalid lens #{arg}" unless arg.size == 1
|
60
61
|
|
61
62
|
name, options = arg.to_a.first
|
62
|
-
namespace_name, lens_name = if name =~ /^[a-z]+\.[a-z]+$/
|
63
|
+
namespace_name, lens_name = if name =~ /^[a-z]+\.[a-z][a-zA-Z]+$/
|
63
64
|
name.to_s.split('.')
|
64
65
|
else
|
65
66
|
['core', name]
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Monolens, 'check.notEmpty' do
|
4
|
+
subject do
|
5
|
+
Monolens.lens('check.notEmpty' => options)
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'with default options' do
|
9
|
+
let(:options) do
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'works on non empty strings' do
|
14
|
+
input = '12'
|
15
|
+
expect(subject.call(input)).to be(input)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'works on non empty arrays' do
|
19
|
+
input = ['12']
|
20
|
+
expect(subject.call(input)).to be(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises on empty strings' do
|
24
|
+
input = ''
|
25
|
+
expect {
|
26
|
+
subject.call(input)
|
27
|
+
}.to raise_error(Monolens::LensError, 'Input may not be empty')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises on empty arrays' do
|
31
|
+
input = []
|
32
|
+
expect {
|
33
|
+
subject.call(input)
|
34
|
+
}.to raise_error(Monolens::LensError, 'Input may not be empty')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with a specific error message' do
|
39
|
+
let(:options) do
|
40
|
+
{ message: 'Hello failure!' }
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises on empty strings' do
|
44
|
+
input = ''
|
45
|
+
expect {
|
46
|
+
subject.call(input)
|
47
|
+
}.to raise_error(Monolens::LensError, 'Hello failure!')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -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.2
|
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-20 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: []
|
@@ -68,6 +82,8 @@ files:
|
|
68
82
|
- lib/monolens/array/compact.rb
|
69
83
|
- lib/monolens/array/join.rb
|
70
84
|
- lib/monolens/array/map.rb
|
85
|
+
- lib/monolens/check.rb
|
86
|
+
- lib/monolens/check/not_empty.rb
|
71
87
|
- lib/monolens/coerce.rb
|
72
88
|
- lib/monolens/coerce/date.rb
|
73
89
|
- lib/monolens/coerce/date_time.rb
|
@@ -106,6 +122,7 @@ files:
|
|
106
122
|
- spec/monolens/array/test_compact.rb
|
107
123
|
- spec/monolens/array/test_join.rb
|
108
124
|
- spec/monolens/array/test_map.rb
|
125
|
+
- spec/monolens/check/test_not_empty.rb
|
109
126
|
- spec/monolens/coerce/test_date.rb
|
110
127
|
- spec/monolens/coerce/test_datetime.rb
|
111
128
|
- spec/monolens/coerce/test_integer.rb
|