remap 2.0.2 → 2.1.5

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/remap/base.rb +229 -75
  3. data/lib/remap/compiler.rb +403 -37
  4. data/lib/remap/constructor/argument.rb +20 -6
  5. data/lib/remap/constructor/keyword.rb +20 -4
  6. data/lib/remap/constructor/none.rb +3 -4
  7. data/lib/remap/constructor.rb +12 -5
  8. data/lib/remap/contract.rb +27 -0
  9. data/lib/remap/extensions/enumerable.rb +48 -0
  10. data/lib/remap/extensions/hash.rb +13 -0
  11. data/lib/remap/extensions/object.rb +37 -0
  12. data/lib/remap/failure.rb +25 -15
  13. data/lib/remap/iteration/array.rb +20 -11
  14. data/lib/remap/iteration/hash.rb +21 -13
  15. data/lib/remap/iteration/other.rb +7 -7
  16. data/lib/remap/iteration.rb +8 -2
  17. data/lib/remap/mapper/and.rb +29 -7
  18. data/lib/remap/mapper/binary.rb +3 -6
  19. data/lib/remap/mapper/or.rb +29 -6
  20. data/lib/remap/mapper/support/operations.rb +40 -0
  21. data/lib/remap/mapper/xor.rb +29 -7
  22. data/lib/remap/mapper.rb +1 -48
  23. data/lib/remap/notice/traced.rb +19 -0
  24. data/lib/remap/notice/untraced.rb +11 -0
  25. data/lib/remap/notice.rb +34 -0
  26. data/lib/remap/operation.rb +26 -13
  27. data/lib/remap/path/input.rb +37 -0
  28. data/lib/remap/path/output.rb +26 -0
  29. data/lib/remap/path.rb +22 -0
  30. data/lib/remap/path_error.rb +13 -0
  31. data/lib/remap/proxy.rb +18 -0
  32. data/lib/remap/rule/each.rb +25 -24
  33. data/lib/remap/rule/embed.rb +33 -28
  34. data/lib/remap/rule/map/optional.rb +42 -0
  35. data/lib/remap/rule/map/required.rb +35 -0
  36. data/lib/remap/rule/map.rb +176 -55
  37. data/lib/remap/rule/set.rb +23 -33
  38. data/lib/remap/rule/support/collection/empty.rb +7 -7
  39. data/lib/remap/rule/support/collection/filled.rb +21 -8
  40. data/lib/remap/rule/support/collection.rb +11 -3
  41. data/lib/remap/rule/support/enum.rb +44 -21
  42. data/lib/remap/rule/void.rb +17 -18
  43. data/lib/remap/rule/wrap.rb +25 -17
  44. data/lib/remap/rule.rb +8 -1
  45. data/lib/remap/selector/all.rb +29 -7
  46. data/lib/remap/selector/index.rb +24 -16
  47. data/lib/remap/selector/key.rb +31 -16
  48. data/lib/remap/selector.rb +17 -0
  49. data/lib/remap/state/extension.rb +182 -208
  50. data/lib/remap/state/schema.rb +1 -1
  51. data/lib/remap/state.rb +30 -4
  52. data/lib/remap/static/fixed.rb +14 -3
  53. data/lib/remap/static/option.rb +21 -6
  54. data/lib/remap/static.rb +13 -0
  55. data/lib/remap/struct.rb +1 -0
  56. data/lib/remap/types.rb +13 -28
  57. data/lib/remap.rb +15 -19
  58. metadata +95 -93
  59. data/lib/remap/result.rb +0 -11
  60. data/lib/remap/rule/support/path.rb +0 -45
  61. data/lib/remap/state/types.rb +0 -11
  62. data/lib/remap/success.rb +0 -29
  63. data/lib/remap/version.rb +0 -5
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Contract < Dry::Validation::Contract
5
+ # Constructs a contract used to validate mapper input
6
+ #
7
+ # @param rules [Array<Proc>]
8
+ # @param options [Hash]
9
+ # @param contract [Proc]
10
+ # @param attributes [Hash]
11
+ #
12
+ # @return [Contract]
13
+ def self.call(rules:, options:, contract:, attributes:)
14
+ Class.new(self) do
15
+ rules.each do |rule|
16
+ class_eval(&rule)
17
+ end
18
+
19
+ options.each do |option|
20
+ class_eval(&option)
21
+ end
22
+
23
+ schema(contract)
24
+ end.new(**attributes)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Extensions
5
+ using Object
6
+
7
+ module Enumerable
8
+ refine ::Enumerable do
9
+ # Creates a hash using {self} as the {path} and {value} as the hash value
10
+ #
11
+ # @param value [Any] Hash value
12
+ #
13
+ # @example A hash from path
14
+ # [:a, :b].hide('value') # => { a: { b: 'value' } }
15
+ #
16
+ # @return [Hash]
17
+ def hide(value)
18
+ reverse.reduce(value) do |element, key|
19
+ { key => element }
20
+ end
21
+ end
22
+
23
+ # Fetches value at {path}
24
+ #
25
+ # @example Fetch value at path
26
+ # [[:a, :b], [:c, :d]].get(0, 1) # => :b
27
+ #
28
+ # @return [Any]
29
+ #
30
+ # @raise When path cannot be found
31
+ def get(*path, &error)
32
+ _, result = path.reduce([
33
+ EMPTY_ARRAY,
34
+ self
35
+ ]) do |(current_path, element), key|
36
+ value = element.fetch(key) do
37
+ raise PathError, current_path + [key]
38
+ end
39
+
40
+ [current_path + [key], value]
41
+ end
42
+
43
+ result
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Extensions
5
+ module Hash
6
+ refine ::Hash do
7
+ def formated
8
+ JSON.neat_generate(self, sort: true, wrap: 40, aligned: true, around_colon: 1)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Extensions
5
+ module Object
6
+ refine ::Object do
7
+ # Fallback validation method
8
+ #
9
+ # @yield if block is provided
10
+ #
11
+ # @raise unless block is provided
12
+ def _(&block)
13
+ unless block
14
+ return _ { raise _1 }
15
+ end
16
+
17
+ block["Expected a state, got [#{self}] (#{self.class})"]
18
+ end
19
+
20
+ # Fallback method used when #get is called on an object that does not respond to #get
21
+ #
22
+ # Block is invoked, if provided
23
+ # Otherwise a symbol is thrown
24
+ #
25
+ # @param path [Array<Key>]
26
+ def get(*path, &block)
27
+ raise PathError, []
28
+ end
29
+ alias_method :fetch, :get
30
+
31
+ def formated
32
+ self
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/remap/failure.rb CHANGED
@@ -1,27 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
- class Failure < Result
5
- attribute :reasons, Types::Hash
4
+ using Extensions::Hash
6
5
 
7
- def inspect
8
- format("Failure<[%<result>s]>", result: JSON.pretty_generate(to_h))
9
- end
6
+ class Failure < Dry::Concrete
7
+ attribute :failures, [Notice], min_size: 1
8
+ attribute? :notices, [Notice], default: EMPTY_ARRAY
10
9
 
11
- def to_hash
12
- { failure: reasons, problems: problems }
13
- end
10
+ # Merges two failures
11
+ #
12
+ # @param other [Failure]
13
+ #
14
+ # @return [Failure]
15
+ def merge(other)
16
+ unless other.is_a?(self.class)
17
+ raise ArgumentError, "can't merge #{self.class} with #{other.class}"
18
+ end
14
19
 
15
- def failure?(*)
16
- true
17
- end
20
+ failure = attributes.deep_merge(other.attributes) do |_, value1, value2|
21
+ case [value1, value2]
22
+ in [Array, Array]
23
+ value1 + value2
24
+ else
25
+ raise ArgumentError, "can't merge #{self.class} with #{other.class}"
26
+ end
27
+ end
18
28
 
19
- def success?(*)
20
- false
29
+ new(failure)
21
30
  end
22
31
 
23
- def fmap
24
- self
32
+ # @return [String]
33
+ def exception
34
+ Error.new(attributes.formated)
25
35
  end
26
36
  end
27
37
  end
@@ -2,28 +2,37 @@
2
2
 
3
3
  module Remap
4
4
  class Iteration
5
+ using State::Extension
6
+
7
+ # Implements an array iterator which defines index in state
5
8
  class Array < Concrete
6
- using State::Extension
9
+ # @return [Array<T>]
10
+ attribute :value, Types::Array, alias: :array
7
11
 
8
- attribute :value, Types::Array
12
+ # @return [State<Array<T>>]
9
13
  attribute :state, Types::State
10
14
 
11
- def map(&block)
12
- value.each_with_index.reduce(init) do |input_state, (value, index)|
13
- block[value, index: index]._.then do |new_state|
14
- new_state.fmap { [_1] }
15
- end.then do |new_array_state|
16
- input_state.merged(new_array_state)
17
- end
18
- end._
15
+ # @see Iteration#map
16
+ def call(&block)
17
+ array.each_with_index.reduce(init) do |state, (value, index)|
18
+ reduce(state, value, index, &block)
19
+ end
19
20
  end
20
- alias call map
21
21
 
22
22
  private
23
23
 
24
24
  def init
25
25
  state.set(EMPTY_ARRAY)
26
26
  end
27
+
28
+ def reduce(state, value, index, &block)
29
+ notice = catch :ignore do
30
+ other = block[value, index: index]
31
+ return state.combine(other.fmap { [_1] })
32
+ end
33
+
34
+ state.set(notice: notice)
35
+ end
27
36
  end
28
37
  end
29
38
  end
@@ -2,26 +2,34 @@
2
2
 
3
3
  module Remap
4
4
  class Iteration
5
+ using State::Extension
6
+
7
+ # Implements a hash iterator which defines key in state
5
8
  class Hash < Concrete
6
- attribute :value, Types::Hash
9
+ # @return [Hash]
10
+ attribute :value, Types::Hash, alias: :hash
11
+
12
+ # @return [State<Hash>]
7
13
  attribute :state, Types::State
8
14
 
9
- using State::Extension
10
-
11
- # @see Base#map
12
- def map(&block)
13
- value.reduce(init) do |input_state, (key, value)|
14
- block[value, key: key]._.then do |new_state|
15
- new_state.fmap { { key => _1 } }
16
- end.then do |new_hash_state|
17
- input_state.merged(new_hash_state)
18
- end
19
- end._
15
+ # @see Iteration#map
16
+ def call(&block)
17
+ hash.reduce(init) do |state, (key, value)|
18
+ reduce(state, key, value, &block)
19
+ end
20
20
  end
21
- alias call map
22
21
 
23
22
  private
24
23
 
24
+ def reduce(state, key, value, &block)
25
+ notice = catch :ignore do
26
+ other = block[value, key: key]
27
+ return state.combine(other.fmap { { key => _1 } })
28
+ end
29
+
30
+ state.set(notice: notice)
31
+ end
32
+
25
33
  def init
26
34
  state.set(EMPTY_HASH)
27
35
  end
@@ -2,17 +2,17 @@
2
2
 
3
3
  module Remap
4
4
  class Iteration
5
+ using State::Extension
6
+
7
+ # Default iterator which doesn't do anything
5
8
  class Other < Concrete
9
+ attribute :value, Types::Any, alias: :other
6
10
  attribute :state, Types::State
7
- attribute :value, Types::Any
8
-
9
- using State::Extension
10
11
 
11
- # @see Base#map
12
- def map(&block)
13
- block[value]._
12
+ # @see Iteration#map
13
+ def call(&block)
14
+ state.fatal!("Expected an enumerable, got %s (%s)", value, value.class)
14
15
  end
15
- alias call map
16
16
  end
17
17
  end
18
18
  end
@@ -2,10 +2,13 @@
2
2
 
3
3
  module Remap
4
4
  class Iteration < Dry::Interface
5
- attribute :value, Types::Value
5
+ # @return [State<T>]
6
6
  attribute :state, Types::State
7
7
 
8
- # Maps every element in {#value} to {#block}
8
+ # @return [T]
9
+ attribute :value, Types::Any
10
+
11
+ # Maps every element in {#value}
9
12
  #
10
13
  # @abstract
11
14
  #
@@ -14,6 +17,9 @@ module Remap
14
17
  # @yieldreturn [Array<V>, Hash<V, K>]
15
18
  #
16
19
  # @return [Array<V>, Hash<V, K>]
20
+ def call(state)
21
+ raise NotImplementedError, "#{self.class}#call not implemented"
22
+ end
17
23
 
18
24
  order :Hash, :Array, :Other
19
25
  end
@@ -2,14 +2,36 @@
2
2
 
3
3
  module Remap
4
4
  class Mapper
5
- class And < Binary
6
- using State::Extension
5
+ using State::Extension
7
6
 
7
+ # Represents two mappers that are combined with the & operator
8
+ #
9
+ # @example Combine two mappers
10
+ # class Mapper1 < Remap::Base
11
+ # contract do
12
+ # required(:a1)
13
+ # end
14
+ # end
15
+ #
16
+ # class Mapper2 < Remap::Base
17
+ # contract do
18
+ # required(:a2)
19
+ # end
20
+ # end
21
+ #
22
+ # state = Remap::State.call({ a2: 2, a1: 1 })
23
+ # output = (Mapper1 & Mapper2).call!(state)
24
+ # output.fetch(:value) # => { a2: 2, a1: 1 }
25
+ class And < Binary
26
+ # Succeeds if both left and right succeed
27
+ # Returns the combined result of left and right
28
+ #
29
+ # @param state [State]
30
+ #
31
+ # @yield [Failure] if mapper fails
32
+ #
33
+ # @return [Result]
8
34
  def call!(state, &error)
9
- unless error
10
- return call!(state, &exception)
11
- end
12
-
13
35
  state1 = left.call!(state) do |failure1|
14
36
  right.call!(state) do |failure2|
15
37
  return error[failure1.merge(failure2)]
@@ -22,7 +44,7 @@ module Remap
22
44
  return error[failure]
23
45
  end
24
46
 
25
- state1.merged(state2)
47
+ state1.combine(state2)
26
48
  end
27
49
  end
28
50
  end
@@ -2,15 +2,12 @@
2
2
 
3
3
  module Remap
4
4
  class Mapper
5
+ # @abstract
5
6
  class Binary < self
7
+ include Operation
8
+
6
9
  attribute :left, Types::Mapper
7
10
  attribute :right, Types::Mapper
8
-
9
- def exception
10
- ->(error) { raise error }
11
- end
12
-
13
- include Operation
14
11
  end
15
12
  end
16
13
  end
@@ -2,14 +2,37 @@
2
2
 
3
3
  module Remap
4
4
  class Mapper
5
- class Or < Binary
6
- using State::Extension
5
+ using State::Extension
7
6
 
7
+ # Represents two mappers that are combined with the | operator
8
+ #
9
+ # @example Combine two mappers
10
+ # class Mapper1 < Remap::Base
11
+ # contract do
12
+ # required(:a1)
13
+ # end
14
+ # end
15
+ #
16
+ # class Mapper2 < Remap::Base
17
+ # contract do
18
+ # required(:a2)
19
+ # end
20
+ # end
21
+ #
22
+ # state = Remap::State.call({ a2: 2 })
23
+ # result = (Mapper1 | Mapper2).call!(state)
24
+ # result.fetch(:value) # => { a2: 2 }
25
+ class Or < Binary
26
+ # Succeeds if left or right succeeds
27
+ # Returns which ever succeeds first
28
+ #
29
+ # @param state [State]
30
+ #
31
+ # @yieldparam [Failure] if mapper fails
32
+ # @yieldreturn [Failure]
33
+ #
34
+ # @return [Result]
8
35
  def call!(state, &error)
9
- unless error
10
- return call!(state, &exception)
11
- end
12
-
13
36
  left.call!(state) do |failure1|
14
37
  return right.call!(state) do |failure2|
15
38
  return error[failure1.merge(failure2)]
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Mapper
5
+ module Operations
6
+ # Tries self and other and returns the first successful result
7
+ #
8
+ # @param other [Mapper]
9
+ #
10
+ # @return [Mapper::Or]
11
+ def |(other)
12
+ Or.new(left: self, right: other)
13
+ rescue Dry::Struct::Error => e
14
+ raise ArgumentError, e.message
15
+ end
16
+
17
+ # Returns a successful result when self & other are successful
18
+ #
19
+ # @param other [Mapper]
20
+ #
21
+ # @return [Mapper::And]
22
+ def &(other)
23
+ And.new(left: self, right: other)
24
+ rescue Dry::Struct::Error => e
25
+ raise ArgumentError, e.message
26
+ end
27
+
28
+ # Returns a successful result when only one of self & other are successful
29
+ #
30
+ # @param other [Mapper]
31
+ #
32
+ # @return [Mapper:Xor]
33
+ def ^(other)
34
+ Xor.new(left: self, right: other)
35
+ rescue Dry::Struct::Error => e
36
+ raise ArgumentError, e.message
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,14 +2,36 @@
2
2
 
3
3
  module Remap
4
4
  class Mapper
5
- class Xor < Binary
6
- using State::Extension
5
+ using State::Extension
7
6
 
7
+ # Represents two mappers that are combined with the ^ operator
8
+ #
9
+ # @example Combine two mappers
10
+ # class Mapper1 < Remap::Base
11
+ # contract do
12
+ # required(:a1)
13
+ # end
14
+ # end
15
+ #
16
+ # class Mapper2 < Remap::Base
17
+ # contract do
18
+ # required(:a2)
19
+ # end
20
+ # end
21
+ #
22
+ # state = Remap::State.call({ a2: 2 })
23
+ # output = (Mapper1 ^ Mapper2).call!(state)
24
+ # output.fetch(:value) # => { a2: 2 }
25
+ class Xor < Binary
26
+ # Succeeds if left or right succeeds, but not both
27
+ #
28
+ # @param state [State]
29
+ #
30
+ # @yieldparam [Failure] if mapper fails
31
+ # @yieldreturn [Failure]
32
+ #
33
+ # @return [Result]
8
34
  def call!(state, &error)
9
- unless error
10
- return call!(state, &exception)
11
- end
12
-
13
35
  state1 = left.call!(state) do |failure1|
14
36
  return right.call!(state) do |failure2|
15
37
  return error[failure1.merge(failure2)]
@@ -20,7 +42,7 @@ module Remap
20
42
  return state1
21
43
  end
22
44
 
23
- error[state1.merged(state2).failure("Both left and right passed in xor")]
45
+ state1.combine(state2).failure("Both left and right passed xor operation").then(&error)
24
46
  end
25
47
  end
26
48
  end
data/lib/remap/mapper.rb CHANGED
@@ -3,54 +3,7 @@
3
3
  module Remap
4
4
  # @abstract
5
5
  class Mapper < Struct
6
- # Tries {self} and {other} and returns the first successful result
7
- #
8
- # @param other [Mapper]
9
- #
10
- # @return [Mapper::Or]
11
- module Operations
12
- def |(other)
13
- Or.new(left: self, right: other)
14
- rescue Dry::Struct::Error => e
15
- raise ArgumentError, e.message
16
- end
17
-
18
- # Returns a successful result when {self} & {other} are successful
19
- #
20
- # @param other [Mapper]
21
- #
22
- # @return [Mapper::And]
23
- def &(other)
24
- And.new(left: self, right: other)
25
- rescue Dry::Struct::Error => e
26
- raise ArgumentError, e.message
27
- end
28
-
29
- # Returns a successful result when only one of {self} & {other} are successful
30
- #
31
- # @param other [Mapper]
32
- #
33
- # @return [Mapper:Xor]
34
- def ^(other)
35
- Xor.new(left: self, right: other)
36
- rescue Dry::Struct::Error => e
37
- raise ArgumentError, e.message
38
- end
39
- end
40
-
41
- include Operations
42
6
  extend Operations
43
-
44
- # Creates a new mapper using {state}
45
- #
46
- # @param state [State]
47
- #
48
- # @yield [State]
49
- # If the call fails, the block is invoked with the state
50
- # @yieldreturn [State]
51
- #
52
- # @return [State]
53
- #
54
- # @private
7
+ include Operations
55
8
  end
56
9
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Notice
5
+ class Traced < Concrete
6
+ attribute? :backtrace, Types::Backtrace, default: EMPTY_ARRAY
7
+
8
+ def traced(backtrace)
9
+ Notice.call(**attributes, backtrace: backtrace)
10
+ end
11
+
12
+ def exception
13
+ return super if backtrace.blank?
14
+
15
+ super.tap { _1.set_backtrace(backtrace.map(&:to_s)) }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Notice
5
+ class Untraced < Concrete
6
+ def traced(backtrace)
7
+ Traced.call(**attributes, backtrace: backtrace)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ using Extensions::Hash
5
+
6
+ class Notice < Dry::Interface
7
+ attribute? :value, Types::Any
8
+ attribute :reason, String
9
+ attribute :path, Array
10
+
11
+ class Error < Remap::Error
12
+ extend Dry::Initializer
13
+
14
+ param :notice, type: Notice
15
+ end
16
+
17
+ def inspect
18
+ "#<%s %s>" % [self.class, to_hash.formated]
19
+ end
20
+ alias to_s inspect
21
+
22
+ # Hash representation of the notice
23
+ #
24
+ # @return [Hash]
25
+ def to_hash
26
+ super.except(:backtrace).compact_blank
27
+ end
28
+
29
+ # @return [Error]
30
+ def exception
31
+ Error.new(self)
32
+ end
33
+ end
34
+ end