monolens 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -82
  3. data/lib/monolens/command.rb +111 -14
  4. data/lib/monolens/error.rb +2 -0
  5. data/lib/monolens/file.rb +6 -0
  6. data/lib/monolens/jsonpath.rb +76 -0
  7. data/lib/monolens/lens/options.rb +26 -12
  8. data/lib/monolens/lens/signature/missing.rb +11 -0
  9. data/lib/monolens/lens/signature.rb +60 -0
  10. data/lib/monolens/lens.rb +25 -4
  11. data/lib/monolens/macros.rb +28 -0
  12. data/lib/monolens/namespace.rb +11 -0
  13. data/lib/monolens/registry.rb +77 -0
  14. data/lib/monolens/{array → stdlib/array}/compact.rb +2 -0
  15. data/lib/monolens/{array → stdlib/array}/join.rb +4 -0
  16. data/lib/monolens/{array → stdlib/array}/map.rb +13 -19
  17. data/lib/monolens/{array.rb → stdlib/array.rb} +8 -6
  18. data/lib/monolens/stdlib/check/not_empty.rb +30 -0
  19. data/lib/monolens/stdlib/check.rb +13 -0
  20. data/lib/monolens/{coerce → stdlib/coerce}/date.rb +6 -1
  21. data/lib/monolens/{coerce → stdlib/coerce}/date_time.rb +7 -2
  22. data/lib/monolens/{coerce → stdlib/coerce}/integer.rb +4 -0
  23. data/lib/monolens/{coerce → stdlib/coerce}/string.rb +2 -0
  24. data/lib/monolens/{coerce.rb → stdlib/coerce.rb} +10 -8
  25. data/lib/monolens/{core → stdlib/core}/chain.rb +5 -3
  26. data/lib/monolens/{core → stdlib/core}/dig.rb +5 -0
  27. data/lib/monolens/stdlib/core/literal.rb +68 -0
  28. data/lib/monolens/{core → stdlib/core}/mapping.rb +15 -5
  29. data/lib/monolens/stdlib/core.rb +31 -0
  30. data/lib/monolens/stdlib/object/allbut.rb +22 -0
  31. data/lib/monolens/{object → stdlib/object}/extend.rb +10 -5
  32. data/lib/monolens/{object → stdlib/object}/keys.rb +4 -0
  33. data/lib/monolens/stdlib/object/merge.rb +56 -0
  34. data/lib/monolens/{object → stdlib/object}/rename.rb +5 -1
  35. data/lib/monolens/{object → stdlib/object}/select.rb +9 -0
  36. data/lib/monolens/{object → stdlib/object}/transform.rb +8 -3
  37. data/lib/monolens/{object → stdlib/object}/values.rb +9 -4
  38. data/lib/monolens/stdlib/object.rb +55 -0
  39. data/lib/monolens/{skip → stdlib/skip}/null.rb +2 -0
  40. data/lib/monolens/{skip.rb → stdlib/skip.rb} +4 -2
  41. data/lib/monolens/{str → stdlib/str}/downcase.rb +2 -0
  42. data/lib/monolens/{str → stdlib/str}/split.rb +5 -1
  43. data/lib/monolens/{str → stdlib/str}/strip.rb +2 -0
  44. data/lib/monolens/{str → stdlib/str}/upcase.rb +2 -0
  45. data/lib/monolens/{str.rb → stdlib/str.rb} +10 -8
  46. data/lib/monolens/stdlib.rb +7 -0
  47. data/lib/monolens/type/any.rb +39 -0
  48. data/lib/monolens/type/array.rb +27 -0
  49. data/lib/monolens/type/boolean.rb +17 -0
  50. data/lib/monolens/type/callback.rb +17 -0
  51. data/lib/monolens/type/coercible.rb +10 -0
  52. data/lib/monolens/type/diggable.rb +9 -0
  53. data/lib/monolens/type/emptyable.rb +9 -0
  54. data/lib/monolens/type/integer.rb +18 -0
  55. data/lib/monolens/type/lenses.rb +17 -0
  56. data/lib/monolens/type/map.rb +30 -0
  57. data/lib/monolens/type/object.rb +17 -0
  58. data/lib/monolens/type/responding.rb +25 -0
  59. data/lib/monolens/type/strategy.rb +56 -0
  60. data/lib/monolens/type/string.rb +18 -0
  61. data/lib/monolens/type/symbol.rb +20 -0
  62. data/lib/monolens/type.rb +33 -0
  63. data/lib/monolens/version.rb +2 -2
  64. data/lib/monolens.rb +22 -65
  65. data/spec/fixtures/macro.yml +13 -0
  66. data/spec/fixtures/recursive.yml +15 -0
  67. data/spec/monolens/command/literal.yml +2 -0
  68. data/spec/monolens/command/literal2.yml +2 -0
  69. data/spec/monolens/command/upcase.lens.yml +4 -0
  70. data/spec/monolens/lens/test_options.rb +2 -14
  71. data/spec/monolens/lens/test_signature.rb +38 -0
  72. data/spec/monolens/{array → stdlib/array}/test_compact.rb +8 -0
  73. data/spec/monolens/{array → stdlib/array}/test_join.rb +0 -0
  74. data/spec/monolens/{array → stdlib/array}/test_map.rb +15 -0
  75. data/spec/monolens/stdlib/check/test_not_empty.rb +50 -0
  76. data/spec/monolens/{coerce → stdlib/coerce}/test_date.rb +0 -0
  77. data/spec/monolens/{coerce → stdlib/coerce}/test_datetime.rb +1 -1
  78. data/spec/monolens/{coerce → stdlib/coerce}/test_integer.rb +0 -0
  79. data/spec/monolens/{coerce → stdlib/coerce}/test_string.rb +0 -0
  80. data/spec/monolens/{core → stdlib/core}/test_dig.rb +0 -0
  81. data/spec/monolens/stdlib/core/test_literal.rb +73 -0
  82. data/spec/monolens/{core → stdlib/core}/test_mapping.rb +37 -1
  83. data/spec/monolens/stdlib/object/test_allbut.rb +31 -0
  84. data/spec/monolens/{object → stdlib/object}/test_extend.rb +0 -0
  85. data/spec/monolens/{object → stdlib/object}/test_keys.rb +0 -0
  86. data/spec/monolens/stdlib/object/test_merge.rb +133 -0
  87. data/spec/monolens/{object → stdlib/object}/test_rename.rb +0 -0
  88. data/spec/monolens/{object → stdlib/object}/test_select.rb +0 -0
  89. data/spec/monolens/{object → stdlib/object}/test_transform.rb +0 -0
  90. data/spec/monolens/{object → stdlib/object}/test_values.rb +0 -0
  91. data/spec/monolens/{skip → stdlib/skip}/test_null.rb +0 -0
  92. data/spec/monolens/{str → stdlib/str}/test_downcase.rb +0 -0
  93. data/spec/monolens/{str → stdlib/str}/test_split.rb +0 -0
  94. data/spec/monolens/{str → stdlib/str}/test_strip.rb +0 -0
  95. data/spec/monolens/{str → stdlib/str}/test_upcase.rb +0 -0
  96. data/spec/monolens/test_command.rb +145 -0
  97. data/spec/monolens/test_error_traceability.rb +1 -1
  98. data/spec/monolens/test_jsonpath.rb +88 -0
  99. data/spec/monolens/test_lens.rb +1 -1
  100. data/spec/test_documentation.rb +52 -0
  101. data/spec/test_monolens.rb +20 -0
  102. data/tasks/test.rake +1 -1
  103. metadata +91 -50
  104. data/lib/monolens/core.rb +0 -23
  105. data/lib/monolens/object.rb +0 -41
@@ -0,0 +1,77 @@
1
+ module Monolens
2
+ class Registry
3
+ LENS_NAME_RX = /^[a-z]+\.[a-z][a-zA-Z\-_]+$/
4
+
5
+ def initialize(registry = {}, default_namespace = 'core')
6
+ @registry = registry
7
+ @default_namespace = default_namespace
8
+ end
9
+
10
+ def define_namespace(name, impl_module)
11
+ @registry[name] = impl_module
12
+ end
13
+
14
+ def load_file(file, registry = self)
15
+ load_yaml(::File.read(file), registry)
16
+ end
17
+
18
+ def load_yaml(yaml, registry = self)
19
+ Monolens::File.new(YAML.safe_load(yaml), registry)
20
+ end
21
+
22
+ def lens(arg, registry = self)
23
+ case arg
24
+ when Lens then arg
25
+ when ::Array then chain(arg, registry)
26
+ when ::String, ::Symbol then leaf_lens(arg, registry)
27
+ when ::Hash then hash_lens(arg, registry)
28
+ else
29
+ raise Error, "No such lens #{arg} (#{arg.class})"
30
+ end
31
+ end
32
+
33
+ def fork(default_namespace = 'self')
34
+ Registry.new(@registry.dup, default_namespace)
35
+ end
36
+
37
+ private
38
+
39
+ def chain(lenses, registry)
40
+ Core::Chain.new(lenses.map{|l| lens(l) }, registry)
41
+ end
42
+
43
+ def file_lens(arg, registry)
44
+ File.new(arg, registry)
45
+ end
46
+
47
+ def leaf_lens(arg, registry)
48
+ namespace_name, lens_name = split_lens_name(arg)
49
+ factor_lens(namespace_name, lens_name, {}, registry)
50
+ end
51
+
52
+ def hash_lens(arg, registry)
53
+ return file_lens(arg, registry) if arg['version'] || arg[:version]
54
+ raise Error, "Invalid lens #{arg}" unless arg.size == 1
55
+
56
+ name, options = arg.to_a.first
57
+ namespace_name, lens_name = split_lens_name(name)
58
+ factor_lens(namespace_name, lens_name, options, registry)
59
+ end
60
+
61
+ def factor_lens(namespace_name, lens_name, options, registry)
62
+ if namespace = @registry[namespace_name]
63
+ namespace.factor_lens(namespace_name, lens_name, options, registry)
64
+ else
65
+ raise Error, "No such namespace #{namespace_name}"
66
+ end
67
+ end
68
+
69
+ def split_lens_name(name)
70
+ if name =~ LENS_NAME_RX
71
+ name.to_s.split('.')
72
+ else
73
+ [@default_namespace, name]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -3,6 +3,8 @@ module Monolens
3
3
  class Compact
4
4
  include Lens
5
5
 
6
+ signature(Type::Array, Type::String)
7
+
6
8
  def call(arg, world = {})
7
9
  is_array!(arg, world)
8
10
 
@@ -3,6 +3,10 @@ module Monolens
3
3
  class Join
4
4
  include Lens
5
5
 
6
+ signature(Type::Array, Type::String, {
7
+ separator: [Type::String, false]
8
+ })
9
+
6
10
  def call(arg, world = {})
7
11
  is_array!(arg, world)
8
12
 
@@ -3,44 +3,38 @@ module Monolens
3
3
  class Map
4
4
  include Lens
5
5
 
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)
18
- end
6
+ signature(Type::Array, Type::Array, {
7
+ on_error: [Type::Strategy.error(%w{handler keep fail null skip}), false],
8
+ lenses: [Type::Lenses, true]
9
+ })
19
10
 
20
11
  def call(arg, world = {})
21
- is_enumerable!(arg, world)
12
+ is_array!(arg, world)
22
13
 
14
+ lenses = option(:lenses)
23
15
  result = []
24
- arg.each_with_index do |a, i|
16
+ arg.each_with_index do |member, i|
25
17
  deeper(world, i) do |w|
26
18
  begin
27
- result << @lenses.call(a, w)
19
+ result << lenses.call(member, w)
28
20
  rescue Monolens::LensError => ex
29
21
  strategy = option(:on_error, :fail)
30
- handle_error(strategy, ex, result, world)
22
+ handle_error(strategy, member, ex, result, world)
31
23
  end
32
24
  end
33
25
  end
34
26
  result
35
27
  end
36
28
 
37
- def handle_error(strategy, ex, result, world)
29
+ def handle_error(strategy, member, ex, result, world)
38
30
  strategy = strategy.to_sym unless strategy.is_a?(::Array)
39
31
  case strategy
40
32
  when ::Array
41
- strategy.each{|s| handle_error(s, ex, result, world) }
33
+ strategy.each{|s| handle_error(s, member, ex, result, world) }
42
34
  when :handler
43
35
  error_handler!(world).call(ex)
36
+ when :keep
37
+ result << member
44
38
  when :fail
45
39
  raise
46
40
  when :null
@@ -1,17 +1,19 @@
1
1
  module Monolens
2
2
  module Array
3
- def compact(options = {})
4
- Compact.new(options)
3
+ extend Namespace
4
+
5
+ def compact(options, registry)
6
+ Compact.new(options, registry)
5
7
  end
6
8
  module_function :compact
7
9
 
8
- def join(options = {})
9
- Join.new(options)
10
+ def join(options, registry)
11
+ Join.new(options, registry)
10
12
  end
11
13
  module_function :join
12
14
 
13
- def map(options)
14
- Map.new(options)
15
+ def map(options, registry)
16
+ Map.new(options, registry)
15
17
  end
16
18
  module_function :map
17
19
 
@@ -0,0 +1,30 @@
1
+ require 'date'
2
+
3
+ module Monolens
4
+ module Check
5
+ class NotEmpty
6
+ include Lens
7
+
8
+ signature(Type::Emptyable, Type::Emptyable, {
9
+ message: [Type::String, false]
10
+ })
11
+
12
+ def call(arg, world = {})
13
+ if arg.nil?
14
+ do_fail!(arg, world)
15
+ elsif arg.respond_to?(:empty?) && arg.empty?
16
+ do_fail!(arg, world)
17
+ else
18
+ arg
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def do_fail!(arg, world)
25
+ message = option(:message, 'Input may not be empty')
26
+ fail!(message, world)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Monolens
2
+ module Check
3
+ extend Namespace
4
+
5
+ def notEmpty(options, registry)
6
+ NotEmpty.new(options, registry)
7
+ end
8
+ module_function :notEmpty
9
+
10
+ Monolens.define_namespace 'check', self
11
+ end
12
+ end
13
+ require_relative 'check/not_empty'
@@ -9,6 +9,11 @@ module Monolens
9
9
  nil
10
10
  ]
11
11
 
12
+ signature(Type::Coercible.to(Type::String), Type::Date, {
13
+ parser: [Type::DateTimeParser, false],
14
+ formats: [Type::Array.of(Type::String), false]
15
+ })
16
+
12
17
  def call(arg, world = {})
13
18
  return arg if arg.is_a?(::Date)
14
19
 
@@ -27,7 +32,7 @@ module Monolens
27
32
  end
28
33
  end
29
34
 
30
- fail!(first_error.message, world)
35
+ fail!("Invalid date `#{arg}`", world) if first_error
31
36
  end
32
37
 
33
38
  def strptime(arg, format = nil)
@@ -9,6 +9,11 @@ module Monolens
9
9
  nil
10
10
  ]
11
11
 
12
+ signature(Type::Coercible.to(Type::String), Type::DateTime, {
13
+ parser: [Type::DateTimeParser, false],
14
+ formats: [Type::Array.of(Type::String), false]
15
+ })
16
+
12
17
  def call(arg, world = {})
13
18
  return arg if arg.is_a?(::DateTime)
14
19
 
@@ -16,7 +21,7 @@ module Monolens
16
21
 
17
22
  date = nil
18
23
  first_error = nil
19
- formats = @options.fetch(:formats, DEFAULT_FORMATS)
24
+ formats = option(:formats, DEFAULT_FORMATS)
20
25
  formats.each do |format|
21
26
  begin
22
27
  return date = strptime(arg, format)
@@ -27,7 +32,7 @@ module Monolens
27
32
  end
28
33
  end
29
34
 
30
- fail!(first_error.message, world)
35
+ fail!("Invalid DateTime `#{arg}`", world) if first_error
31
36
  end
32
37
 
33
38
  private
@@ -5,10 +5,14 @@ module Monolens
5
5
  class Integer
6
6
  include Lens
7
7
 
8
+ signature(Type::Coercible.to(Type::Integer), Type::Integer)
9
+
8
10
  def call(arg, world = {})
9
11
  Integer(arg)
10
12
  rescue => ex
11
13
  fail!(ex.message, world)
14
+ rescue ArgumentError => ex
15
+ fail!(ex.message, world)
12
16
  end
13
17
  end
14
18
  end
@@ -5,6 +5,8 @@ module Monolens
5
5
  class String
6
6
  include Lens
7
7
 
8
+ signature(Type::Any, Type::String)
9
+
8
10
  def call(arg, world = {})
9
11
  arg.to_s
10
12
  end
@@ -1,22 +1,24 @@
1
1
  module Monolens
2
2
  module Coerce
3
- def date(options = {})
4
- Date.new(options)
3
+ extend Namespace
4
+
5
+ def date(options, registry)
6
+ Date.new(options, registry)
5
7
  end
6
8
  module_function :date
7
9
 
8
- def integer(options = {})
9
- Integer.new(options)
10
+ def integer(options, registry)
11
+ Integer.new(options, registry)
10
12
  end
11
13
  module_function :integer
12
14
 
13
- def datetime(options = {})
14
- DateTime.new(options)
15
+ def datetime(options, registry)
16
+ DateTime.new(options, registry)
15
17
  end
16
18
  module_function :datetime
17
19
 
18
- def string(options = {})
19
- String.new(options)
20
+ def string(options, registry)
21
+ String.new(options, registry)
20
22
  end
21
23
  module_function :string
22
24
 
@@ -3,9 +3,11 @@ module Monolens
3
3
  class Chain
4
4
  include Lens
5
5
 
6
- def initialize(lenses)
7
- super({})
8
- @lenses = lenses
6
+ signature(Type::Any, Type::Any)
7
+
8
+ def initialize(options, registry)
9
+ super({}, registry)
10
+ @lenses = options.map{|l| lens(l) }
9
11
  end
10
12
 
11
13
  def call(arg, world = {})
@@ -3,6 +3,11 @@ module Monolens
3
3
  class Dig
4
4
  include Lens
5
5
 
6
+ signature(Type::Diggable, Type::Any, {
7
+ defn: [Type::Array.of(Type::Any.of(Type::Integer, Type::String)), true],
8
+ on_missing: [Type::Strategy.missing(%w{fail null}), false]
9
+ })
10
+
6
11
  def call(arg, world = {})
7
12
  option(:defn, []).inject(arg) do |memo, part|
8
13
  dig_on(part, memo, world)
@@ -0,0 +1,68 @@
1
+ module Monolens
2
+ module Core
3
+ class Literal
4
+ include Lens
5
+
6
+ signature(Type::Any, Type::Any, {
7
+ defn: [Type::Any, true],
8
+ jsonpath: [Type::Object, false]
9
+ })
10
+
11
+ def initialize(options, registry)
12
+ super(options, registry)
13
+ @root_symbol = extract_jsonpath_root_symbol
14
+ @one_rx = Jsonpath.one_detect_rx(@root_symbol)
15
+ @interpolate_rx = Jsonpath.interpolate_detect_rx(@root_symbol)
16
+ end
17
+
18
+ def call(arg, world = {})
19
+ instantiate(option(:defn), arg, world)
20
+ end
21
+
22
+ private
23
+
24
+ def instantiate(obj, input, world)
25
+ case obj
26
+ when ::Array
27
+ obj.map {|item|
28
+ instantiate(item, input, world)
29
+ }
30
+ when ::Hash
31
+ obj.each_with_object({}){|(k,v),memo|
32
+ memo[k] = instantiate(v, input, world)
33
+ }
34
+ when @one_rx
35
+ Jsonpath.one(obj, input, jsonpath_options(input))
36
+ when @interpolate_rx
37
+ Jsonpath.interpolate(obj, input, jsonpath_options(input))
38
+ else
39
+ obj
40
+ end
41
+ end
42
+
43
+ def jsonpath_options(input)
44
+ {
45
+ use_symbols: use_symbols?(input),
46
+ root_symbol: @root_symbol
47
+ }
48
+ end
49
+
50
+ def extract_jsonpath_root_symbol
51
+ opts = option(:jsonpath, {})
52
+ _, symbol = fetch_on(:root_symbol, opts, '$')
53
+ symbol
54
+ end
55
+
56
+ def use_symbols?(input)
57
+ case input
58
+ when ::Hash
59
+ input.keys.any?{|s| s.is_a?(Symbol) }
60
+ when ::Array
61
+ input.any?{|x| use_symbols?(x) }
62
+ else
63
+ false
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -3,8 +3,16 @@ module Monolens
3
3
  class Mapping
4
4
  include Lens
5
5
 
6
+ signature(Type::Any, Type::Any, {
7
+ defn: [Type::Object, false],
8
+ values: [Type::Object, false], # deprecated
9
+ default: [Type::Any, false],
10
+ fallback: [Type::Callback, false],
11
+ on_missing: [Type::Strategy.missing(%w{default fail fallback keep null}), false]
12
+ })
13
+
6
14
  def call(arg, world = {})
7
- option(:values, {}).fetch(arg) do
15
+ option(:defn, option(:values, {})).fetch(arg) do
8
16
  on_missing(arg, world)
9
17
  end
10
18
  end
@@ -14,17 +22,19 @@ module Monolens
14
22
  def on_missing(arg, world)
15
23
  strategy = option(:on_missing, :fail)
16
24
  case strategy.to_sym
17
- when :fail
18
- fail!("Unrecognized value `#{arg}`", world)
19
25
  when :default
20
26
  option(:default, nil)
21
- when :null
22
- nil
27
+ when :fail
28
+ fail!("Unrecognized value `#{arg}`", world)
23
29
  when :fallback
24
30
  missing_fallback = ->(arg, world) do
25
31
  raise Monolens::Error, "Unexpected missing fallback handler"
26
32
  end
27
33
  option(:fallback, missing_fallback).call(self, arg, world)
34
+ when :keep
35
+ arg
36
+ when :null
37
+ nil
28
38
  else
29
39
  raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
30
40
  end
@@ -0,0 +1,31 @@
1
+ module Monolens
2
+ module Core
3
+ extend Namespace
4
+
5
+ def chain(options, registry)
6
+ Chain.new(options, registry)
7
+ end
8
+ module_function :chain
9
+
10
+ def dig(options, registry)
11
+ Dig.new(options, registry)
12
+ end
13
+ module_function :dig
14
+
15
+ def literal(options, registry)
16
+ Literal.new(options, registry)
17
+ end
18
+ module_function :literal
19
+
20
+ def mapping(options, registry)
21
+ Mapping.new(options, registry)
22
+ end
23
+ module_function :mapping
24
+
25
+ Monolens.define_namespace 'core', self
26
+ end
27
+ end
28
+ require_relative 'core/chain'
29
+ require_relative 'core/dig'
30
+ require_relative 'core/mapping'
31
+ require_relative 'core/literal'
@@ -0,0 +1,22 @@
1
+ module Monolens
2
+ module Object
3
+ class Allbut
4
+ include Lens
5
+
6
+ signature(Type::Object, Type::Object, {
7
+ defn: [Type::Array.of(Type::Name), false]
8
+ })
9
+
10
+ def call(arg, world = {})
11
+ is_hash!(arg, world)
12
+
13
+ allbut = option(:defn, [])
14
+ arg.delete_if{|k|
15
+ allbut.include?(k) || \
16
+ allbut.include?(k.to_s) || \
17
+ allbut.include?(k.to_sym)
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,11 +3,16 @@ module Monolens
3
3
  class Extend
4
4
  include Lens
5
5
 
6
- def initialize(options)
7
- super(options)
6
+ signature(Type::Object, Type::Object, {
7
+ defn: [Type::Map.of(Type::Name, Type::Lenses), false],
8
+ on_error: [Type::Strategy.error(%w{fail handler null skip}), false],
9
+ })
10
+
11
+ def initialize(options, registry)
12
+ super(options, registry)
8
13
  ts = option(:defn, {})
9
14
  ts.each_pair do |k,v|
10
- ts[k] = Monolens.lens(v)
15
+ ts[k] = lens(v)
11
16
  end
12
17
  end
13
18
 
@@ -35,10 +40,10 @@ module Monolens
35
40
  case strategy
36
41
  when ::Array
37
42
  strategy.each{|s| handle_error(s, ex, result, attr, world) }
38
- when :handler
39
- error_handler!(world).call(ex)
40
43
  when :fail
41
44
  raise
45
+ when :handler
46
+ error_handler!(world).call(ex)
42
47
  when :null
43
48
  result[attr] = nil
44
49
  when :skip
@@ -3,6 +3,10 @@ module Monolens
3
3
  class Keys
4
4
  include Lens
5
5
 
6
+ signature(Type::Object, Type::Object, {
7
+ lenses: [Type::Lenses, false],
8
+ })
9
+
6
10
  def call(arg, world = {})
7
11
  is_hash!(arg, world)
8
12
 
@@ -0,0 +1,56 @@
1
+ module Monolens
2
+ module Object
3
+ class Merge
4
+ include Lens
5
+
6
+ signature(Type::Object, Type::Object, {
7
+ priority: [Type::Strategy.priority(%w{input defn}), false],
8
+ deep: [Type::Boolean, false],
9
+ defn: [Type::Object, true]
10
+ })
11
+
12
+ def call(input, world = {})
13
+ is_hash!(input, world)
14
+
15
+ v1, v2 = input, option(:defn, {})
16
+ if deep?
17
+ deep_merge(v1, v2, world, priority_at_input?)
18
+ else
19
+ normal_merge(v1, v2, world, priority_at_input?)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def normal_merge(v1, v2, world, priority_at_input)
26
+ is_hash!(v1, world)
27
+ is_hash!(v2, world)
28
+
29
+ v1.merge(v2) do |k, v11, v22|
30
+ priority_at_input? ? v11 : v22
31
+ end
32
+ end
33
+
34
+ def deep_merge(v1, v2, world, priority_at_input)
35
+ case v1
36
+ when ::Hash
37
+ is_hash!(v2, world)
38
+
39
+ v1.merge(v2) do |k, v11, v22|
40
+ deep_merge(v11, v22, world, priority_at_input)
41
+ end
42
+ else
43
+ priority_at_input? ? v1 : v2
44
+ end
45
+ end
46
+
47
+ def deep?
48
+ option(:deep, false) == true
49
+ end
50
+
51
+ def priority_at_input?
52
+ @pati ||= (option(:priority, 'defn').to_s == 'input')
53
+ end
54
+ end
55
+ end
56
+ end
@@ -3,11 +3,15 @@ module Monolens
3
3
  class Rename
4
4
  include Lens
5
5
 
6
+ signature(Type::Object, Type::Object, {
7
+ defn: [Type::Map.of(Type::Name, Type::Name), false],
8
+ })
9
+
6
10
  def call(arg, world = {})
7
11
  is_hash!(arg, world)
8
12
 
9
13
  dup = arg.dup
10
- option(:defn).each_pair do |oldname, newname|
14
+ option(:defn, {}).each_pair do |oldname, newname|
11
15
  actual_name, value = fetch_on(oldname, arg)
12
16
  newname = actual_name.is_a?(Symbol) ? newname.to_sym : newname.to_s
13
17
  dup.delete(actual_name)
@@ -3,6 +3,15 @@ module Monolens
3
3
  class Select
4
4
  include Lens
5
5
 
6
+ signature(Type::Object, Type::Object, {
7
+ strategy: [Type::Strategy.selection(%w{all first}), false],
8
+ defn: [Type::Any.of(
9
+ Type::Array.of(Type::Name),
10
+ Type::Map.of(Type::Name, Type::Any.of(Type::Array.of(Type::Name), Type::Name))
11
+ ), true],
12
+ on_missing: [Type::Strategy.missing(%w{fail null skip}), false]
13
+ })
14
+
6
15
  def call(arg, world = {})
7
16
  is_hash!(arg, world)
8
17