monolens 0.4.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30152f626d5675640e96816c7036d5a6dbac8a25
4
- data.tar.gz: a26d4e5d0715bd511aea96da758fd12d36e2a70b
3
+ metadata.gz: 2b96981f78af52f468676e9de0aeb3f79e79ab66
4
+ data.tar.gz: fb6445fc49c216be5868b27bef78cdfba79a1264
5
5
  SHA512:
6
- metadata.gz: 7b27ab005aaf2a6bf1abdc8c9284072ce1307498b7edad606526198c3e4ae003d0ddda72eb532266b7b737fbb04146959903868c50e16be4fbb8f1a073f20e9b
7
- data.tar.gz: 8fd2b3ad776ff72da243ba29ab7fc2bd5fd05e88b8d34c93cd38c362142b83ff4c8164559af707d3ea314c39af3b9c415c1716841c269ab73bf9e06b852c883e
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
@@ -0,0 +1,11 @@
1
+ module Monolens
2
+ module Check
3
+ def notEmpty(options)
4
+ NotEmpty.new(options)
5
+ end
6
+ module_function :notEmpty
7
+
8
+ Monolens.define_namespace 'check', self
9
+ end
10
+ end
11
+ require_relative 'check/not_empty'
@@ -27,7 +27,7 @@ module Monolens
27
27
  end
28
28
  end
29
29
 
30
- fail!(first_error.message, world)
30
+ fail!("Invalid date `#{arg}`", world) if first_error
31
31
  end
32
32
 
33
33
  def strptime(arg, format = nil)
@@ -27,7 +27,7 @@ module Monolens
27
27
  end
28
28
  end
29
29
 
30
- fail!(first_error.message, world)
30
+ fail!("Invalid DateTime `#{arg}`", world) if first_error
31
31
  end
32
32
 
33
33
  private
@@ -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
@@ -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
- 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,8 +1,8 @@
1
1
  module Monolens
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 4
5
- TINY = 0
4
+ MINOR = 5
5
+ TINY = 2
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
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 '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.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-12 00:00:00.000000000 Z
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