monolens 0.2.0 → 0.3.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/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
|