monolens 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/monolens/array/compact.rb +2 -2
- data/lib/monolens/array/join.rb +2 -2
- data/lib/monolens/array/map.rb +45 -6
- data/lib/monolens/array.rb +2 -2
- data/lib/monolens/coerce/date.rb +20 -6
- data/lib/monolens/coerce/date_time.rb +20 -6
- data/lib/monolens/coerce/string.rb +13 -0
- data/lib/monolens/coerce.rb +6 -3
- data/lib/monolens/core/chain.rb +2 -2
- data/lib/monolens/core/mapping.rb +2 -2
- data/lib/monolens/error.rb +9 -2
- data/lib/monolens/file.rb +2 -7
- data/lib/monolens/lens/fetch_support.rb +21 -0
- data/lib/monolens/lens/location.rb +17 -0
- data/lib/monolens/lens/options.rb +41 -0
- data/lib/monolens/lens.rb +39 -23
- data/lib/monolens/object/keys.rb +8 -10
- data/lib/monolens/object/rename.rb +3 -3
- data/lib/monolens/object/select.rb +34 -16
- data/lib/monolens/object/transform.rb +34 -12
- data/lib/monolens/object/values.rb +34 -10
- data/lib/monolens/skip/null.rb +1 -1
- data/lib/monolens/str/downcase.rb +2 -2
- data/lib/monolens/str/split.rb +2 -2
- data/lib/monolens/str/strip.rb +3 -1
- data/lib/monolens/str/upcase.rb +2 -2
- data/lib/monolens/version.rb +1 -1
- data/spec/fixtures/coerce.yml +3 -2
- data/spec/fixtures/transform.yml +5 -4
- data/spec/monolens/array/test_map.rb +89 -6
- data/spec/monolens/coerce/test_date.rb +29 -4
- data/spec/monolens/coerce/test_datetime.rb +29 -4
- data/spec/monolens/coerce/test_string.rb +15 -0
- data/spec/monolens/core/test_mapping.rb +25 -0
- data/spec/monolens/lens/test_options.rb +73 -0
- data/spec/monolens/object/test_keys.rb +54 -22
- data/spec/monolens/object/test_rename.rb +1 -1
- data/spec/monolens/object/test_select.rb +109 -4
- data/spec/monolens/object/test_transform.rb +93 -6
- data/spec/monolens/object/test_values.rb +110 -12
- data/spec/monolens/test_error_traceability.rb +60 -0
- data/spec/monolens/test_lens.rb +1 -1
- data/spec/test_readme.rb +7 -5
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a6edbb2568985503bf9228ded96c6d179acc196
|
4
|
+
data.tar.gz: f37432cfc01970a18c57884547f5520ac31fb374
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e83f9436b81460041e6ed9edd73c1fd95ca8b095f2cd0d27d063649b49c8b125363735258c5d03293c1fb74afaa1d042e4a4663b47c2a45276c0b8a730d8ee77
|
7
|
+
data.tar.gz: 5c704058d773ab5bddc4d58541d043a63ef176ad68f9a5f7ba670335b167da7df50c7935691ecb8951e98b354bd39d79ad5d36fb8cafa210372d30d44376051f
|
data/lib/monolens/array/join.rb
CHANGED
data/lib/monolens/array/map.rb
CHANGED
@@ -3,16 +3,55 @@ module Monolens
|
|
3
3
|
class Map
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(arg)
|
7
|
+
options, lenses = case arg
|
8
|
+
when ::Hash
|
9
|
+
opts = arg.dup; opts.delete(:lenses)
|
10
|
+
_, ls = fetch_on(:lenses, arg)
|
11
|
+
raise ArgumentError, 'Lenses are required' if ls.nil?
|
12
|
+
[ opts, ls ]
|
13
|
+
else
|
14
|
+
[{}, arg]
|
15
|
+
end
|
16
|
+
super(options)
|
17
|
+
@lenses = Monolens.lens(lenses)
|
9
18
|
end
|
10
19
|
|
11
|
-
def call(arg,
|
12
|
-
is_enumerable!(arg)
|
20
|
+
def call(arg, world = {})
|
21
|
+
is_enumerable!(arg, world)
|
13
22
|
|
14
|
-
|
23
|
+
result = []
|
24
|
+
arg.each_with_index do |a, i|
|
25
|
+
deeper(world, i) do |w|
|
26
|
+
begin
|
27
|
+
result << @lenses.call(a, w)
|
28
|
+
rescue Monolens::LensError => ex
|
29
|
+
strategy = option(:on_error, :fail)
|
30
|
+
handle_error(strategy, ex, result, world)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result
|
15
35
|
end
|
36
|
+
|
37
|
+
def handle_error(strategy, ex, result, world)
|
38
|
+
strategy = strategy.to_sym unless strategy.is_a?(::Array)
|
39
|
+
case strategy
|
40
|
+
when ::Array
|
41
|
+
strategy.each{|s| handle_error(s, ex, result, world) }
|
42
|
+
when :handler
|
43
|
+
error_handler!(world).call(ex)
|
44
|
+
when :fail
|
45
|
+
raise
|
46
|
+
when :null
|
47
|
+
result << nil
|
48
|
+
when :skip
|
49
|
+
nil
|
50
|
+
else
|
51
|
+
raise Monolens::Error, "Unexpected error strategy `#{strategy}`"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private :handle_error
|
16
55
|
end
|
17
56
|
end
|
18
57
|
end
|
data/lib/monolens/array.rb
CHANGED
data/lib/monolens/coerce/date.rb
CHANGED
@@ -5,13 +5,19 @@ module Monolens
|
|
5
5
|
class Date
|
6
6
|
include Lens
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
DEFAULT_FORMATS = [
|
9
|
+
nil
|
10
|
+
]
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
def call(arg, world = {})
|
13
|
+
is_string!(arg, world)
|
14
|
+
|
15
|
+
date = nil
|
16
|
+
first_error = nil
|
17
|
+
formats = @options.fetch(:formats, DEFAULT_FORMATS)
|
18
|
+
formats.each do |format|
|
13
19
|
begin
|
14
|
-
return date =
|
20
|
+
return date = strptime(arg, format)
|
15
21
|
rescue ArgumentError => ex
|
16
22
|
first_error ||= ex
|
17
23
|
rescue ::Date::Error => ex
|
@@ -19,7 +25,15 @@ module Monolens
|
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
|
-
|
28
|
+
fail!(first_error.message, world)
|
29
|
+
end
|
30
|
+
|
31
|
+
def strptime(arg, format = nil)
|
32
|
+
if format.nil?
|
33
|
+
::Date.strptime(arg)
|
34
|
+
else
|
35
|
+
::Date.strptime(arg, format)
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
25
39
|
end
|
@@ -5,13 +5,19 @@ module Monolens
|
|
5
5
|
class DateTime
|
6
6
|
include Lens
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
DEFAULT_FORMATS = [
|
9
|
+
nil
|
10
|
+
]
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
def call(arg, world = {})
|
13
|
+
is_string!(arg, world)
|
14
|
+
|
15
|
+
date = nil
|
16
|
+
first_error = nil
|
17
|
+
formats = @options.fetch(:formats, DEFAULT_FORMATS)
|
18
|
+
formats.each do |format|
|
13
19
|
begin
|
14
|
-
return date =
|
20
|
+
return date = strptime(arg, format)
|
15
21
|
rescue ArgumentError => ex
|
16
22
|
first_error ||= ex
|
17
23
|
rescue ::Date::Error => ex
|
@@ -19,7 +25,15 @@ module Monolens
|
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
|
-
|
28
|
+
fail!(first_error.message, world)
|
29
|
+
end
|
30
|
+
|
31
|
+
def strptime(arg, format = nil)
|
32
|
+
if format.nil?
|
33
|
+
::DateTime.strptime(arg)
|
34
|
+
else
|
35
|
+
::DateTime.strptime(arg, format)
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
25
39
|
end
|
data/lib/monolens/coerce.rb
CHANGED
@@ -10,11 +10,14 @@ module Monolens
|
|
10
10
|
end
|
11
11
|
module_function :datetime
|
12
12
|
|
13
|
+
def string(options = {})
|
14
|
+
String.new(options)
|
15
|
+
end
|
16
|
+
module_function :string
|
17
|
+
|
13
18
|
Monolens.define_namespace 'coerce', self
|
14
19
|
end
|
15
20
|
end
|
16
|
-
require_relative 'str/strip'
|
17
|
-
require_relative 'str/upcase'
|
18
|
-
require_relative 'str/downcase'
|
19
21
|
require_relative 'coerce/date'
|
20
22
|
require_relative 'coerce/date_time'
|
23
|
+
require_relative 'coerce/string'
|
data/lib/monolens/core/chain.rb
CHANGED
@@ -8,12 +8,12 @@ module Monolens
|
|
8
8
|
@lenses = lenses
|
9
9
|
end
|
10
10
|
|
11
|
-
def call(arg,
|
11
|
+
def call(arg, world = {})
|
12
12
|
result = arg
|
13
13
|
@lenses.each do |lens|
|
14
14
|
done = false
|
15
15
|
catch(:skip) do
|
16
|
-
result = lens.call(result,
|
16
|
+
result = lens.call(result, world)
|
17
17
|
done = true
|
18
18
|
end
|
19
19
|
break unless done
|
@@ -3,9 +3,9 @@ module Monolens
|
|
3
3
|
class Mapping
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def call(arg,
|
6
|
+
def call(arg, world = {})
|
7
7
|
option(:values, {}).fetch(arg) do
|
8
|
-
|
8
|
+
fail!("Unrecognized value `#{arg}`", world) if option(:fail_if_missing)
|
9
9
|
|
10
10
|
option(:default)
|
11
11
|
end
|
data/lib/monolens/error.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
module Monolens
|
2
|
-
class Error < StandardError
|
3
|
-
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
class LensError < Error
|
5
|
+
def initialize(message, location = [])
|
6
|
+
super(message)
|
7
|
+
@location = location
|
8
|
+
end
|
9
|
+
attr_reader :location
|
10
|
+
end
|
4
11
|
end
|
data/lib/monolens/file.rb
CHANGED
@@ -2,13 +2,8 @@ module Monolens
|
|
2
2
|
class File
|
3
3
|
include Lens
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
options[:lenses] = Monolens.lens(options[:lenses])
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(*args, &bl)
|
11
|
-
options[:lenses].call(*args, &bl)
|
5
|
+
def call(arg, world = {})
|
6
|
+
option(:lenses).call(arg, world)
|
12
7
|
end
|
13
8
|
end
|
14
9
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Lens
|
3
|
+
module FetchSupport
|
4
|
+
|
5
|
+
def fetch_on(attr, arg, default = nil)
|
6
|
+
if arg.key?(attr)
|
7
|
+
[ attr, arg[attr] ]
|
8
|
+
elsif arg.key?(attr_s = attr.to_s)
|
9
|
+
[ attr_s, arg[attr_s] ]
|
10
|
+
elsif arg.key?(attr_sym = attr.to_sym)
|
11
|
+
[ attr_sym, arg[attr_sym] ]
|
12
|
+
elsif default
|
13
|
+
[ attr, default ]
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Lens
|
3
|
+
class Options
|
4
|
+
include FetchSupport
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@options = case options
|
8
|
+
when Hash
|
9
|
+
options.dup
|
10
|
+
else
|
11
|
+
{ lenses: options }
|
12
|
+
end
|
13
|
+
actual, lenses = fetch_on(:lenses, @options)
|
14
|
+
@options[actual] = Monolens.lens(lenses) if actual && lenses
|
15
|
+
@options.freeze
|
16
|
+
end
|
17
|
+
attr_reader :options
|
18
|
+
private :options
|
19
|
+
|
20
|
+
NO_DEFAULT = Object.new.freeze
|
21
|
+
|
22
|
+
def fetch(key, default = NO_DEFAULT, on = @options)
|
23
|
+
if on.key?(key)
|
24
|
+
on[key]
|
25
|
+
elsif on.key?(s = key.to_s)
|
26
|
+
on[s]
|
27
|
+
elsif on.key?(sym = key.to_sym)
|
28
|
+
on[sym]
|
29
|
+
elsif default != NO_DEFAULT
|
30
|
+
default
|
31
|
+
else
|
32
|
+
raise Error, "Missing option #{key}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
@options.dup
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/monolens/lens.rb
CHANGED
@@ -1,51 +1,67 @@
|
|
1
|
+
require_relative 'lens/fetch_support'
|
2
|
+
require_relative 'lens/options'
|
3
|
+
require_relative 'lens/location'
|
4
|
+
|
1
5
|
module Monolens
|
2
6
|
module Lens
|
7
|
+
include FetchSupport
|
8
|
+
|
3
9
|
def initialize(options = {})
|
4
|
-
@options =
|
5
|
-
memo[k.to_sym] = v
|
6
|
-
}
|
10
|
+
@options = Options.new(options)
|
7
11
|
end
|
8
12
|
attr_reader :options
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
protected
|
15
|
+
|
16
|
+
def option(name, default = nil)
|
17
|
+
@options.fetch(name, default)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deeper(world, part)
|
21
|
+
world[:location] ||= Location.new
|
22
|
+
world[:location].deeper(part) do |loc|
|
23
|
+
yield(world.merge(location: loc))
|
19
24
|
end
|
20
25
|
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
MISSING_ERROR_HANDLER = <<~TXT
|
28
|
+
An :error_handler world entry is required to use error handling
|
29
|
+
TXT
|
30
|
+
|
31
|
+
def error_handler!(world)
|
32
|
+
_, handler = fetch_on(:error_handler, world)
|
33
|
+
return handler if handler
|
34
|
+
|
35
|
+
raise Monolens::Error, MISSING_ERROR_HANDLER
|
25
36
|
end
|
26
37
|
|
27
|
-
def is_string!(arg)
|
38
|
+
def is_string!(arg, world)
|
28
39
|
return if arg.is_a?(::String)
|
29
40
|
|
30
|
-
|
41
|
+
fail!("String expected, got #{arg.class}", world)
|
31
42
|
end
|
32
43
|
|
33
|
-
def is_hash!(arg)
|
44
|
+
def is_hash!(arg, world)
|
34
45
|
return if arg.is_a?(::Hash)
|
35
46
|
|
36
|
-
|
47
|
+
fail!("Hash expected, got #{arg.class}", world)
|
37
48
|
end
|
38
49
|
|
39
|
-
def is_enumerable!(arg)
|
50
|
+
def is_enumerable!(arg, world)
|
40
51
|
return if arg.is_a?(::Enumerable)
|
41
52
|
|
42
|
-
|
53
|
+
fail!("Enumerable expected, got #{arg.class}", world)
|
43
54
|
end
|
44
55
|
|
45
|
-
def is_array!(arg)
|
56
|
+
def is_array!(arg, world)
|
46
57
|
return if arg.is_a?(::Array)
|
47
58
|
|
48
|
-
|
59
|
+
fail!("Array expected, got #{arg.class}", world)
|
60
|
+
end
|
61
|
+
|
62
|
+
def fail!(msg, world)
|
63
|
+
location = world[:location]&.to_a || []
|
64
|
+
raise Monolens::LensError.new(msg, location)
|
49
65
|
end
|
50
66
|
end
|
51
67
|
end
|
data/lib/monolens/object/keys.rb
CHANGED
@@ -3,19 +3,17 @@ module Monolens
|
|
3
3
|
class Keys
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
@lens = Monolens.lens(lens)
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(arg, *rest)
|
12
|
-
is_hash!(arg)
|
6
|
+
def call(arg, world = {})
|
7
|
+
is_hash!(arg, world)
|
13
8
|
|
9
|
+
lenses = option(:lenses)
|
14
10
|
dup = {}
|
15
11
|
arg.each_pair do |attr, value|
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
deeper(world, attr) do |w|
|
13
|
+
lensed = lenses.call(attr, w)
|
14
|
+
lensed = lensed.to_sym if lensed && attr.is_a?(Symbol)
|
15
|
+
dup[lensed] = value
|
16
|
+
end
|
19
17
|
end
|
20
18
|
dup
|
21
19
|
end
|
@@ -3,11 +3,11 @@ module Monolens
|
|
3
3
|
class Rename
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def call(arg,
|
7
|
-
is_hash!(arg)
|
6
|
+
def call(arg, world = {})
|
7
|
+
is_hash!(arg, world)
|
8
8
|
|
9
9
|
dup = arg.dup
|
10
|
-
|
10
|
+
option(:defn).each_pair do |oldname, newname|
|
11
11
|
actual_name, value = fetch_on(oldname, arg)
|
12
12
|
newname = actual_name.is_a?(Symbol) ? newname.to_sym : newname.to_s
|
13
13
|
dup.delete(actual_name)
|
@@ -3,28 +3,46 @@ module Monolens
|
|
3
3
|
class Select
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
@selection = selection
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(arg, *rest)
|
12
|
-
is_hash!(arg)
|
6
|
+
def call(arg, world = {})
|
7
|
+
is_hash!(arg, world)
|
13
8
|
|
14
9
|
result = {}
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
is_symbol = arg.keys.any?{|k| k.is_a?(Symbol) }
|
11
|
+
option(:defn, {}).each_pair do |new_attr, selector|
|
12
|
+
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
|
26
|
+
end
|
22
27
|
end
|
23
|
-
new_attr = is_symbol ? new_attr.to_sym : new_attr.to_s
|
24
|
-
result[new_attr] = is_array ? values : values.first
|
25
28
|
end
|
26
29
|
result
|
27
30
|
end
|
31
|
+
|
32
|
+
def on_missing(attr, values, world)
|
33
|
+
strategy = option(:on_missing, :fail)
|
34
|
+
case strategy.to_sym
|
35
|
+
when :fail
|
36
|
+
fail!("Expected `#{attr}` to be defined", world)
|
37
|
+
when :null
|
38
|
+
values << nil
|
39
|
+
when :skip
|
40
|
+
nil
|
41
|
+
else
|
42
|
+
raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
private :on_missing
|
28
46
|
end
|
29
47
|
end
|
30
48
|
end
|
@@ -3,23 +3,45 @@ module Monolens
|
|
3
3
|
class Transform
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
super(
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
ts = option(:defn, {})
|
9
|
+
ts.each_pair do |k,v|
|
10
|
+
ts[k] = Monolens.lens(v)
|
11
|
+
end
|
11
12
|
end
|
12
13
|
|
13
|
-
def call(arg,
|
14
|
-
is_hash!(arg)
|
14
|
+
def call(arg, world = {})
|
15
|
+
is_hash!(arg, world)
|
16
|
+
|
17
|
+
result = arg.dup
|
18
|
+
option(:defn, {}).each_pair do |attr, sub_lens|
|
19
|
+
deeper(world, attr) do |w|
|
20
|
+
actual_attr, fetched = fetch_on(attr, arg)
|
21
|
+
if actual_attr
|
22
|
+
result[actual_attr] = sub_lens.call(fetched, w)
|
23
|
+
else
|
24
|
+
on_missing(result, attr, world)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
15
30
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
31
|
+
def on_missing(result, attr, world)
|
32
|
+
strategy = option(:on_missing, :fail)
|
33
|
+
case strategy.to_sym
|
34
|
+
when :fail
|
35
|
+
fail!("Expected `#{attr}` to be defined", world)
|
36
|
+
when :null
|
37
|
+
result[attr] = nil
|
38
|
+
when :skip
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
|
20
42
|
end
|
21
|
-
dup
|
22
43
|
end
|
44
|
+
private :on_missing
|
23
45
|
end
|
24
46
|
end
|
25
47
|
end
|