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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30152f626d5675640e96816c7036d5a6dbac8a25
4
- data.tar.gz: a26d4e5d0715bd511aea96da758fd12d36e2a70b
3
+ metadata.gz: b2a9267753f8725ed91fc71b3c9b995a11d3b0c1
4
+ data.tar.gz: 4aaaf0247bcb4f2195052a416ebe7742bfd2b72a
5
5
  SHA512:
6
- metadata.gz: 7b27ab005aaf2a6bf1abdc8c9284072ce1307498b7edad606526198c3e4ae003d0ddda72eb532266b7b737fbb04146959903868c50e16be4fbb8f1a073f20e9b
7
- data.tar.gz: 8fd2b3ad776ff72da243ba29ab7fc2bd5fd05e88b8d34c93cd38c362142b83ff4c8164559af707d3ea314c39af3b9c415c1716841c269ab73bf9e06b852c883e
6
+ metadata.gz: 99df61206eb26141c13a7a364fc9f5af97ade23d9a50e92e82cd8b2980cb88790e36402a852116d800a2caebe71af7b1bb90e4ad3dc8fdd450360d1ce63fe5e6
7
+ data.tar.gz: a93491cec013f9993299ee227c2055c0c93a9f224c5da6a2526afbb95fe2516bf1b29250670c769667cd93bb018e613e3ae21b98d545a62109602e2fa2c1f843
@@ -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$/ then JSON.parse(content)
53
- when /ya?ml$/ then YAML.safe_load(content)
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
- fail!("Unrecognized value `#{arg}`", world) if option(:fail_if_missing)
8
+ on_missing(arg, world)
9
+ end
10
+ end
11
+
12
+ private
9
13
 
10
- option(:default)
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
- is_array = selector.is_a?(::Array)
14
- values = []
15
- Array(selector).each do |old_attr|
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
- nil
78
+ throw :skip
51
79
  else
52
- raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
80
+ raise Monolens::Error, "Unexpected on_missing strategy `#{strategy}`"
53
81
  end
54
82
  end
55
83
  private :on_missing
@@ -30,7 +30,7 @@ module Monolens
30
30
 
31
31
  def on_missing(result, attr, world)
32
32
  strategy = option(:on_missing, :fail)
33
- case strategy.to_sym
33
+ case strategy&.to_sym
34
34
  when :fail
35
35
  fail!("Expected `#{attr}` to be defined", world)
36
36
  when :null
@@ -1,7 +1,7 @@
1
1
  module Monolens
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 4
4
+ MINOR = 5
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
@@ -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 'returns nil if not found' do
18
- expect(subject.call('nosuchone')).to eql(nil)
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 'specifying a default value' do
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 'lets raise if not found' do
38
+ context 'on_missing: null' do
37
39
  subject do
38
- Monolens.lens('core.mapping' => mapping.merge('fail_if_missing' => true))
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 'raises if not found' do
46
- expect {
47
- subject.call('nosuchone')
48
- }.to raise_error(Monolens::LensError)
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('fail_if_missing' => true)
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([:status])
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.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-12 00:00:00.000000000 Z
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: []