monolens 0.1.0 → 0.4.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 +5 -5
- data/README.md +14 -4
- data/bin/monolens +11 -0
- data/lib/monolens/array/compact.rb +2 -2
- data/lib/monolens/array/join.rb +13 -0
- data/lib/monolens/array/map.rb +57 -0
- data/lib/monolens/array.rb +12 -0
- data/lib/monolens/coerce/date.rb +22 -6
- data/lib/monolens/coerce/date_time.rb +30 -6
- data/lib/monolens/coerce/integer.rb +15 -0
- data/lib/monolens/coerce/string.rb +13 -0
- data/lib/monolens/coerce.rb +12 -3
- data/lib/monolens/command.rb +87 -0
- data/lib/monolens/core/chain.rb +2 -2
- data/lib/monolens/core/dig.rb +52 -0
- data/lib/monolens/core/mapping.rb +15 -0
- data/lib/monolens/core.rb +10 -4
- data/lib/monolens/error.rb +9 -2
- data/lib/monolens/error_handler.rb +21 -0
- data/lib/monolens/file.rb +2 -7
- data/lib/monolens/lens/fetch_support.rb +19 -0
- data/lib/monolens/lens/location.rb +17 -0
- data/lib/monolens/lens/options.rb +41 -0
- data/lib/monolens/lens.rb +41 -18
- data/lib/monolens/object/extend.rb +53 -0
- data/lib/monolens/object/keys.rb +8 -10
- data/lib/monolens/object/rename.rb +3 -3
- data/lib/monolens/object/select.rb +58 -0
- data/lib/monolens/object/transform.rb +34 -12
- data/lib/monolens/object/values.rb +34 -10
- data/lib/monolens/object.rb +12 -0
- data/lib/monolens/skip/null.rb +1 -1
- data/lib/monolens/str/downcase.rb +2 -2
- data/lib/monolens/str/split.rb +14 -0
- data/lib/monolens/str/strip.rb +3 -1
- data/lib/monolens/str/upcase.rb +2 -2
- data/lib/monolens/str.rb +12 -6
- data/lib/monolens/version.rb +1 -1
- data/lib/monolens.rb +7 -1
- data/spec/fixtures/coerce.yml +3 -2
- data/spec/fixtures/transform.yml +5 -4
- data/spec/monolens/array/test_compact.rb +15 -0
- data/spec/monolens/array/test_join.rb +27 -0
- data/spec/monolens/array/test_map.rb +96 -0
- data/spec/monolens/coerce/test_date.rb +34 -4
- data/spec/monolens/coerce/test_datetime.rb +70 -7
- data/spec/monolens/coerce/test_integer.rb +46 -0
- data/spec/monolens/coerce/test_string.rb +15 -0
- data/spec/monolens/command/map-upcase.lens.yml +5 -0
- data/spec/monolens/command/names-with-null.json +5 -0
- data/spec/monolens/command/names.json +4 -0
- data/spec/monolens/command/robust-map-upcase.lens.yml +7 -0
- data/spec/monolens/core/test_dig.rb +78 -0
- data/spec/monolens/core/test_mapping.rb +76 -0
- data/spec/monolens/lens/test_options.rb +73 -0
- data/spec/monolens/object/test_extend.rb +94 -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 +202 -0
- data/spec/monolens/object/test_transform.rb +93 -6
- data/spec/monolens/object/test_values.rb +110 -12
- data/spec/monolens/skip/test_null.rb +2 -2
- data/spec/monolens/str/test_downcase.rb +13 -0
- data/spec/monolens/str/test_split.rb +39 -0
- data/spec/monolens/str/test_strip.rb +13 -0
- data/spec/monolens/str/test_upcase.rb +13 -0
- data/spec/monolens/test_command.rb +128 -0
- data/spec/monolens/test_error_traceability.rb +60 -0
- data/spec/monolens/test_lens.rb +1 -1
- data/spec/test_readme.rb +8 -6
- metadata +39 -5
- data/lib/monolens/core/map.rb +0 -18
- data/spec/monolens/core/test_map.rb +0 -11
data/lib/monolens/lens.rb
CHANGED
@@ -1,44 +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
|
-
|
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))
|
17
24
|
end
|
18
25
|
end
|
19
26
|
|
20
|
-
|
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
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_string!(arg, world)
|
21
39
|
return if arg.is_a?(::String)
|
22
40
|
|
23
|
-
|
41
|
+
fail!("String expected, got #{arg.class}", world)
|
24
42
|
end
|
25
43
|
|
26
|
-
def is_hash!(arg)
|
44
|
+
def is_hash!(arg, world)
|
27
45
|
return if arg.is_a?(::Hash)
|
28
46
|
|
29
|
-
|
47
|
+
fail!("Hash expected, got #{arg.class}", world)
|
30
48
|
end
|
31
49
|
|
32
|
-
def is_enumerable!(arg)
|
50
|
+
def is_enumerable!(arg, world)
|
33
51
|
return if arg.is_a?(::Enumerable)
|
34
52
|
|
35
|
-
|
53
|
+
fail!("Enumerable expected, got #{arg.class}", world)
|
36
54
|
end
|
37
55
|
|
38
|
-
def is_array!(arg)
|
56
|
+
def is_array!(arg, world)
|
39
57
|
return if arg.is_a?(::Array)
|
40
58
|
|
41
|
-
|
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)
|
42
65
|
end
|
43
66
|
end
|
44
67
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Object
|
3
|
+
class Extend
|
4
|
+
include Lens
|
5
|
+
|
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
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(arg, world = {})
|
15
|
+
is_hash!(arg, world)
|
16
|
+
|
17
|
+
result = arg.dup
|
18
|
+
is_symbol = arg.keys.any?{|k| k.is_a?(Symbol) }
|
19
|
+
option(:defn, {}).each_pair do |attr, lens|
|
20
|
+
deeper(world, attr) do |w|
|
21
|
+
actual = is_symbol ? attr.to_sym : attr.to_s
|
22
|
+
begin
|
23
|
+
result[actual] = lens.call(arg, w)
|
24
|
+
rescue Monolens::LensError => ex
|
25
|
+
strategy = option(:on_error, :fail)
|
26
|
+
handle_error(strategy, ex, result, actual, world)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_error(strategy, ex, result, attr, world)
|
34
|
+
strategy = strategy.to_sym unless strategy.is_a?(::Array)
|
35
|
+
case strategy
|
36
|
+
when ::Array
|
37
|
+
strategy.each{|s| handle_error(s, ex, result, attr, world) }
|
38
|
+
when :handler
|
39
|
+
error_handler!(world).call(ex)
|
40
|
+
when :fail
|
41
|
+
raise
|
42
|
+
when :null
|
43
|
+
result[attr] = nil
|
44
|
+
when :skip
|
45
|
+
nil
|
46
|
+
else
|
47
|
+
raise Monolens::Error, "Unexpected error strategy `#{strategy}`"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private :handle_error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Object
|
3
|
+
class Select
|
4
|
+
include Lens
|
5
|
+
|
6
|
+
def call(arg, world = {})
|
7
|
+
is_hash!(arg, world)
|
8
|
+
|
9
|
+
result = {}
|
10
|
+
is_symbol = arg.keys.any?{|k| k.is_a?(Symbol) }
|
11
|
+
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
|
27
|
+
end
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def defn
|
35
|
+
defn = option(:defn, {})
|
36
|
+
defn = defn.each_with_object({}) do |attr, memo|
|
37
|
+
memo[attr] = attr
|
38
|
+
end if defn.is_a?(::Array)
|
39
|
+
defn
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_missing(attr, values, world)
|
43
|
+
strategy = option(:on_missing, :fail)
|
44
|
+
case strategy.to_sym
|
45
|
+
when :fail
|
46
|
+
fail!("Expected `#{attr}` to be defined", world)
|
47
|
+
when :null
|
48
|
+
values << nil
|
49
|
+
when :skip
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
private :on_missing
|
56
|
+
end
|
57
|
+
end
|
58
|
+
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
|
@@ -3,20 +3,44 @@ module Monolens
|
|
3
3
|
class Values
|
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
|
|
14
|
-
|
9
|
+
lenses = option(:lenses)
|
10
|
+
result = arg.dup
|
15
11
|
arg.each_pair do |attr, value|
|
16
|
-
|
12
|
+
deeper(world, attr) do |w|
|
13
|
+
begin
|
14
|
+
result[attr] = lenses.call(value, w)
|
15
|
+
rescue Monolens::LensError => ex
|
16
|
+
strategy = option(:on_error, :fail)
|
17
|
+
handle_error(strategy, ex, result, attr, value, world)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_error(strategy, ex, result, attr, value, world)
|
25
|
+
strategy = strategy.to_sym unless strategy.is_a?(::Array)
|
26
|
+
case strategy
|
27
|
+
when ::Array
|
28
|
+
strategy.each{|s| handle_error(s, ex, result, attr, value, world) }
|
29
|
+
when :handler
|
30
|
+
error_handler!(world).call(ex)
|
31
|
+
when :fail
|
32
|
+
raise
|
33
|
+
when :null
|
34
|
+
result[attr] = nil
|
35
|
+
when :skip
|
36
|
+
result.delete(attr)
|
37
|
+
when :keep
|
38
|
+
result[attr] = value
|
39
|
+
else
|
40
|
+
raise Monolens::Error, "Unexpected error strategy `#{strategy}`"
|
17
41
|
end
|
18
|
-
dup
|
19
42
|
end
|
43
|
+
private :handle_error
|
20
44
|
end
|
21
45
|
end
|
22
46
|
end
|
data/lib/monolens/object.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
module Monolens
|
2
2
|
module Object
|
3
|
+
def extend(options)
|
4
|
+
Extend.new(options)
|
5
|
+
end
|
6
|
+
module_function :extend
|
7
|
+
|
3
8
|
def rename(parts)
|
4
9
|
Rename.new(parts)
|
5
10
|
end
|
@@ -20,6 +25,11 @@ module Monolens
|
|
20
25
|
end
|
21
26
|
module_function :values
|
22
27
|
|
28
|
+
def select(lens)
|
29
|
+
Select.new(lens)
|
30
|
+
end
|
31
|
+
module_function :select
|
32
|
+
|
23
33
|
Monolens.define_namespace 'object', self
|
24
34
|
end
|
25
35
|
end
|
@@ -27,3 +37,5 @@ require_relative 'object/rename'
|
|
27
37
|
require_relative 'object/transform'
|
28
38
|
require_relative 'object/keys'
|
29
39
|
require_relative 'object/values'
|
40
|
+
require_relative 'object/select'
|
41
|
+
require_relative 'object/extend'
|
data/lib/monolens/skip/null.rb
CHANGED
data/lib/monolens/str/strip.rb
CHANGED
data/lib/monolens/str/upcase.rb
CHANGED
data/lib/monolens/str.rb
CHANGED
@@ -1,23 +1,29 @@
|
|
1
1
|
module Monolens
|
2
2
|
module Str
|
3
|
+
def downcase(options = {})
|
4
|
+
Downcase.new(options)
|
5
|
+
end
|
6
|
+
module_function :downcase
|
7
|
+
|
3
8
|
def strip(options = {})
|
4
9
|
Strip.new(options)
|
5
10
|
end
|
6
11
|
module_function :strip
|
7
12
|
|
13
|
+
def split(options = {})
|
14
|
+
Split.new(options)
|
15
|
+
end
|
16
|
+
module_function :split
|
17
|
+
|
8
18
|
def upcase(options = {})
|
9
19
|
Upcase.new(options)
|
10
20
|
end
|
11
21
|
module_function :upcase
|
12
22
|
|
13
|
-
def downcase(options = {})
|
14
|
-
Downcase.new(options)
|
15
|
-
end
|
16
|
-
module_function :downcase
|
17
|
-
|
18
23
|
Monolens.define_namespace 'str', self
|
19
24
|
end
|
20
25
|
end
|
26
|
+
require_relative 'str/downcase'
|
21
27
|
require_relative 'str/strip'
|
28
|
+
require_relative 'str/split'
|
22
29
|
require_relative 'str/upcase'
|
23
|
-
require_relative 'str/downcase'
|
data/lib/monolens/version.rb
CHANGED
data/lib/monolens.rb
CHANGED
@@ -5,6 +5,7 @@ module Monolens
|
|
5
5
|
class << self
|
6
6
|
require_relative 'monolens/version'
|
7
7
|
require_relative 'monolens/error'
|
8
|
+
require_relative 'monolens/error_handler'
|
8
9
|
require_relative 'monolens/lens'
|
9
10
|
|
10
11
|
def define_namespace(name, impl_module)
|
@@ -24,7 +25,7 @@ module Monolens
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def load_yaml(yaml)
|
27
|
-
Monolens::File.new(YAML.
|
28
|
+
Monolens::File.new(YAML.safe_load(yaml))
|
28
29
|
end
|
29
30
|
|
30
31
|
def lens(arg)
|
@@ -43,6 +44,10 @@ module Monolens
|
|
43
44
|
end
|
44
45
|
private :chain
|
45
46
|
|
47
|
+
def file_lens(arg)
|
48
|
+
File.new(arg)
|
49
|
+
end
|
50
|
+
|
46
51
|
def leaf_lens(arg)
|
47
52
|
namespace_name, lens_name = arg.to_s.split('.')
|
48
53
|
factor_lens(namespace_name, lens_name, {})
|
@@ -50,6 +55,7 @@ module Monolens
|
|
50
55
|
private :leaf_lens
|
51
56
|
|
52
57
|
def hash_lens(arg)
|
58
|
+
return file_lens(arg) if arg['version'] || arg[:version]
|
53
59
|
raise "Invalid lens #{arg}" unless arg.size == 1
|
54
60
|
|
55
61
|
name, options = arg.to_a.first
|
data/spec/fixtures/coerce.yml
CHANGED
data/spec/fixtures/transform.yml
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Monolens, 'array.compact' do
|
4
|
+
subject do
|
5
|
+
Monolens.lens('array.compact')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'removes nils' do
|
9
|
+
expect(subject.call([nil, 'notnil'])).to eql(['notnil'])
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'supports empty arrays' do
|
13
|
+
expect(subject.call([])).to eql([])
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Monolens, 'array.join' do
|
4
|
+
context 'when used without options' do
|
5
|
+
subject do
|
6
|
+
Monolens.lens('array.join')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'joins values with spaces' do
|
10
|
+
expect(subject.call(['hello', 'world'])).to eql('hello world')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'supports empty arrays' do
|
14
|
+
expect(subject.call([])).to eql('')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when specifying the separator' do
|
19
|
+
subject do
|
20
|
+
Monolens.lens('array.join' => { separator: ', ' })
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'joins values with it' do
|
24
|
+
expect(subject.call(['hello', 'world'])).to eql('hello, world')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|