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
@@ -2,41 +2,31 @@
2
2
 
3
3
  module Remap
4
4
  class Rule
5
- class Set < self
6
- using State::Extension
5
+ using State::Extension
7
6
 
8
- attribute :value, Types.Interface(:call)
9
- attribute :path, Path
7
+ # Set path to a static value
8
+ #
9
+ # @example Set path [:a, :b] to value "C"
10
+ # value = Remap::Static::Fixed.new(value: "a value")
11
+ # set = Remap::Rule::Set.new(value: value, path: [:a, :b])
12
+ # state = Remap::State.call("ANY VALUE")
13
+ # set.call(state).fetch(:value) # => { a: { b: "a value" } }
14
+ #
15
+ # @example Set path [:a, :b] to option :c
16
+ # value = Remap::Static::Option.new(name: :c)
17
+ # set = Remap::Rule::Set.new(value: value, path: [:a, :b])
18
+ # state = Remap::State.call("ANY VALUE", options: { c: "C" })
19
+ # set.call(state).fetch(:value) # => { a: { b: "C" } }
20
+ class Set < Concrete
21
+ # @return [Static]
22
+ attribute :value, Static, alias: :rule
10
23
 
11
- # Returns {value} mapped to {path} regardless of input
12
- #
13
- # @param state [State]
14
- #
15
- # @example Given an option
16
- # class Mapper < Remap::Base
17
- # option :name
18
- #
19
- # define do
20
- # set [:person, :name], to: option(:name)
21
- # end
22
- # end
23
- #
24
- # Mapper.call(input, name: "John") # => { person: { name: "John" } }
25
- #
26
- # @example Given a value
27
- # class Mapper < Remap::Base
28
- # define do
29
- # set [:api_key], to: value("ABC-123")
30
- # end
31
- # end
32
- #
33
- # Mapper.call(input) # => { api_key: "ABC-123" }
34
- #
35
- # @return [State]
36
- def call(state)
37
- path.call(state) do
38
- value.call(state)
39
- end
24
+ # @return [Path::Output]
25
+ attribute :path, Path::Output
26
+
27
+ # @see Rule#call
28
+ def call(...)
29
+ rule.call(...).then(&path)
40
30
  end
41
31
  end
42
32
  end
@@ -3,19 +3,19 @@
3
3
  module Remap
4
4
  class Rule
5
5
  class Collection
6
- class Empty < Unit
7
- using State::Extension
6
+ using State::Extension
8
7
 
9
- attribute? :rules, Value(EMPTY_ARRAY).default { EMPTY_ARRAY }
8
+ # Represents an empty rule block
9
+ class Empty < Unit
10
+ attribute? :rules, Value(EMPTY_ARRAY), default: EMPTY_ARRAY
10
11
 
11
12
  # Represents an empty define block, without any rules
12
13
  #
13
- # @param input [Any]
14
- # @param state [State]
14
+ # @param state [State<T>]
15
15
  #
16
- # @return [Monad::Failure]
16
+ # @return [State<T>]
17
17
  def call(state)
18
- state.problem("No rules, empty block")
18
+ state.notice!("No rules, empty block")
19
19
  end
20
20
  end
21
21
  end
@@ -3,23 +3,36 @@
3
3
  module Remap
4
4
  class Rule
5
5
  class Collection
6
- class Filled < Unit
7
- using State::Extension
6
+ using State::Extension
8
7
 
8
+ # Represents a non-empty rule block
9
+ #
10
+ # @example A collection containing a single rule
11
+ # state = Remap::State.call("A")
12
+ # void = Remap::Rule::Void.call({})
13
+ # rule = Remap::Rule::Collection.call([void])
14
+ # error = -> failure { raise failure.exception }
15
+ # rule.call(state, &error).fetch(:value) # => "A"
16
+ class Filled < Unit
17
+ # @return [Array<Rule>]
9
18
  attribute :rules, [Types.Interface(:call)], min_size: 1
10
19
 
11
20
  # Represents a non-empty define block with one or more rules
12
- # Calls every {#rules} with {input} and merges the output
21
+ # Calls every {#rules} with state and merges the output
13
22
  #
14
23
  # @param state [State]
15
24
  #
16
25
  # @return [State]
17
- def call(state)
18
- rules.map do |rule|
19
- Thread.new(rule, state) { _1.call(_2) }
20
- end.map(&:value).reduce do |acc, inner_state|
21
- acc.merged(inner_state)
26
+ def call(state, &error)
27
+ unless error
28
+ raise ArgumentError, "Collection::Filled#call(state, &error) requires a block"
22
29
  end
30
+
31
+ rules.map do |rule|
32
+ rule.call(state) do |failure|
33
+ return error[failure]
34
+ end
35
+ end.reduce(&:combine)
23
36
  end
24
37
  end
25
38
  end
@@ -2,9 +2,17 @@
2
2
 
3
3
  module Remap
4
4
  class Rule
5
- class Collection < Dry::Interface
6
- def to_proc
7
- method(:call).to_proc
5
+ # Represents a block defined by a rule
6
+ class Collection < Abstract
7
+ attribute :rules, Array
8
+
9
+ # @param state [State]
10
+ #
11
+ # @return [State]
12
+ #
13
+ # @abstract
14
+ def call(state)
15
+ raise NotImplementedError, "#{self.class}#call not implemented"
8
16
  end
9
17
  end
10
18
  end
@@ -2,17 +2,32 @@
2
2
 
3
3
  module Remap
4
4
  class Rule
5
- class Enum
6
- include Dry::Core::Constants
5
+ class Enum < Proxy
7
6
  include Dry::Monads[:maybe]
8
7
 
9
- extend Dry::Initializer
10
-
8
+ # @return [Hash]
11
9
  option :mappings, default: -> { Hash.new { default } }
10
+
11
+ # @return [Maybe]
12
12
  option :default, default: -> { None() }
13
13
 
14
14
  alias execute instance_eval
15
15
 
16
+ # Builds an enumeration using the block as context
17
+ #
18
+ # @example
19
+ # enum = Remap::Rule::Enum.call do
20
+ # from "B", to: "C"
21
+ # value "A"
22
+ # otherwise "D"
23
+ # end
24
+ #
25
+ # enum.get("A") # => "A"
26
+ # enum.get("B") # => "C"
27
+ # enum.get("C") # => "C"
28
+ # enum.get("MISSING") # => "D"
29
+ #
30
+ # @return [Any]
16
31
  def self.call(&block)
17
32
  unless block
18
33
  raise ArgumentError, "no block given"
@@ -21,40 +36,48 @@ module Remap
21
36
  new.tap { _1.execute(&block) }
22
37
  end
23
38
 
24
- def [](key)
25
- mappings[key]
26
- end
27
-
39
+ # Translates key into a value using predefined mappings
40
+ #
41
+ # @param key [#hash]
42
+ #
43
+ # @yield [String]
44
+ # If the key is not found & no default value is set
45
+ #
46
+ # @return [Any]
28
47
  def get(key, &error)
29
48
  unless error
30
49
  return get(key) { raise Error, _1 }
31
50
  end
32
51
 
33
- mappings[key].bind { return _1 }.or do
52
+ self[key].bind { return _1 }.or do
34
53
  error["Enum key [#{key}] not found among [#{mappings.keys.inspect}]"]
35
54
  end
36
55
  end
37
56
  alias call get
38
57
 
39
- # Map all keys in {keys} to {to}
40
- #
41
- # @return [VOID]
58
+ # @return [Maybe]
59
+ def [](key)
60
+ mappings[key]
61
+ end
62
+
63
+ # @return [void]
42
64
  def from(*keys, to:)
65
+ value = Some(to)
66
+
43
67
  keys.each do |key|
44
- mappings[key] = Some(to)
68
+ mappings[key] = value
69
+ mappings[to] = value
45
70
  end
46
71
  end
47
72
 
48
- # Maps {var} to {var}
49
- #
50
- # @return [VOID]
51
- def value(id)
52
- from(id, to: id)
73
+ # @return [void]
74
+ def value(*ids)
75
+ ids.each do |id|
76
+ from(id, to: id)
77
+ end
53
78
  end
54
79
 
55
- # Fallback value when {#call} fails
56
- #
57
- # @return [Void]
80
+ # @return [void]
58
81
  def otherwise(value)
59
82
  mappings.default = Some(value)
60
83
  end
@@ -2,27 +2,26 @@
2
2
 
3
3
  module Remap
4
4
  class Rule
5
- class Void < self
6
- using State::Extension
5
+ using State::Extension
7
6
 
8
- # Returns its input
7
+ # Represents a mapping without block
8
+ #
9
+ # @example Maps "A" to "A"
10
+ # class Mapper < Remap::Base
11
+ # define do
12
+ # map
13
+ # end
14
+ # end
15
+ #
16
+ # Mapper.call("A") # => "A"
17
+ class Void < Concrete
18
+ # @param state [State<T>]
9
19
  #
10
- # @param state [State]
11
- #
12
- # @example An empty rule
13
- # class Mapper < Remap::Base
14
- # define do
15
- # map do
16
- # # Empty ...
17
- # end
18
- # end
19
- # end
20
- #
21
- # Mapper.call(input) # => input
22
- #
23
- # @return [State]
20
+ # @return [State<T>]
24
21
  def call(state)
25
- state.bind { _2.set(_1) }
22
+ state.bind do |value, inner_state|
23
+ inner_state.set(value)
24
+ end
26
25
  end
27
26
  end
28
27
  end
@@ -1,29 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/array/wrap"
4
-
5
3
  module Remap
6
4
  class Rule
7
- class Wrap < self
8
- using State::Extension
5
+ using State::Extension
9
6
 
7
+ # Wraps rule in a type
8
+ #
9
+ # @example Maps { name: "Ford" } to { cars: ["Ford"] }
10
+ # class Mapper < Remap::Base
11
+ # define do
12
+ # to :cars do
13
+ # wrap(:array) do
14
+ # map :name
15
+ # end
16
+ # end
17
+ # end
18
+ # end
19
+ #
20
+ # Mapper.call({ name: "Ford" }) # => { cars: ["Ford"] }
21
+ class Wrap < Concrete
22
+ # @return [:array]
10
23
  attribute :type, Value(:array)
11
- attribute :rule, Types::Any
24
+
25
+ # @return [Rule]
26
+ attribute :rule, Types::Rule
12
27
 
13
28
  # Wraps the output from {#rule} in a {#type}
14
29
  #
15
- # @param state [State]
16
- #
17
- # @example mapps { car: "Volvo" } to { cars: ["Volvo"] }
18
- # to :cars do
19
- # wrap(:array) do
20
- # map :car
21
- # end
22
- # end
23
- #
24
- # @return [State]
25
- def call(state)
26
- rule.call(state).fmap { Array.wrap(_1) }
30
+ # @see Rule#call
31
+ def call(...)
32
+ rule.call(...).fmap do |value|
33
+ Array.wrap(value)
34
+ end
27
35
  end
28
36
  end
29
37
  end
data/lib/remap/rule.rb CHANGED
@@ -1,8 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
- class Rule < Dry::Concrete
4
+ class Rule < Dry::Interface
5
5
  defines :requirement
6
6
  requirement Types::Any
7
+
8
+ # @param state [State]
9
+ #
10
+ # @abstract
11
+ def call(state)
12
+ raise NotImplementedError, "#{self.class}#call not implemented"
13
+ end
7
14
  end
8
15
  end
@@ -2,18 +2,40 @@
2
2
 
3
3
  module Remap
4
4
  class Selector
5
- class All < Concrete
6
- using State::Extension
5
+ using State::Extension
7
6
 
7
+ # Selects all elements from a state
8
+ #
9
+ # @example Select all keys from array hash
10
+ # state = Remap::State.call([{a: "A1"}, {a: "A2"}])
11
+ # all = Remap::Selector::All.new
12
+ # result = all.call(state) do |other_state|
13
+ # value = other_state.fetch(:value).class
14
+ # other_state.merge(value: value)
15
+ # end
16
+ # result.fetch(:value) # => [Hash, Hash]
17
+ class All < Concrete
8
18
  requirement Types::Enumerable
9
19
 
10
- def call(state, &block)
11
- state.bind(quantifier: "*") do |enumerable, inner_state, &error|
12
- requirement[enumerable] do
13
- return error["Expected an enumeration"]
20
+ # Iterates over state and passes each value to block
21
+ #
22
+ # @param outer_state [State<Enumerable<T>>]
23
+ #
24
+ # @yieldparam [State<T>]
25
+ # @yieldreturn [State<U>]
26
+ #
27
+ # @return [State<U>]
28
+ def call(outer_state, &block)
29
+ unless block_given?
30
+ raise ArgumentError, "All selector requires an iteration block"
31
+ end
32
+
33
+ outer_state.bind(quantifier: "*") do |enum, state|
34
+ requirement[enum] do
35
+ state.fatal!("Expected enumeration but got %p (%s)", enum, enum.class)
14
36
  end
15
37
 
16
- inner_state.map(&block)
38
+ state.map(&block)
17
39
  end
18
40
  end
19
41
  end
@@ -2,36 +2,44 @@
2
2
 
3
3
  module Remap
4
4
  class Selector
5
+ using State::Extension
6
+
7
+ # Selects value at given index
8
+ #
9
+ # @example Select the value at index 1 from a array
10
+ # state = Remap::State.call([:one, :two, :tree])
11
+ # result = Remap::Selector::Index.new(1).call(state)
12
+ # result.fetch(:value) # => :two
5
13
  class Index < Unit
6
- using State::Extension
7
-
14
+ # @return [Integer]
8
15
  attribute :index, Integer
9
16
 
10
17
  requirement Types::Array
11
18
 
12
- # Fetches {#input[value]} and passes it to {block}
19
+ # Selects the {#index}th element from state and passes it to block
13
20
  #
14
- # @param [State] state
21
+ # @param outer_state [State<Array<T>>]
15
22
  #
16
- # @yieldparam [State]
17
- # @yieldreturn [State<T>]
18
-
19
- # @return [State<T>]
20
- def call(state, &block)
21
- unless block
22
- raise ArgumentError, "no block given"
23
- end
23
+ # @yieldparam [State<T>]
24
+ # @yieldreturn [State<U>]
25
+ #
26
+ # @return [State<U>]
27
+ def call(outer_state, &block)
28
+ return call(outer_state, &:itself) unless block
24
29
 
25
- state.bind(index: index) do |array, inner_state, &error|
30
+ outer_state.bind(index: index) do |array, state|
26
31
  requirement[array] do
27
- return error["Expected an array"]
32
+ state.fatal!("Expected array but got %p (%s)", array, array.class)
28
33
  end
29
34
 
30
35
  element = array.fetch(index) do
31
- return error["No element on index at index #{index}"]
36
+ state.ignore!("Index %s in array %p (%s) not found",
37
+ index,
38
+ array,
39
+ array.class)
32
40
  end
33
41
 
34
- block[inner_state.set(element, index: index)]
42
+ state.set(element, index: index).then(&block)
35
43
  end
36
44
  end
37
45
  end
@@ -2,35 +2,50 @@
2
2
 
3
3
  module Remap
4
4
  class Selector
5
+ using State::Extension
6
+
7
+ # Selects value at key from state
8
+ #
9
+ # @example Select the value at key :name from a hash
10
+ # state = Remap::State.call({ name: "John" })
11
+ # selector = Remap::Selector::Key.new(:name)
12
+ #
13
+ # selector.call(state) do |state|
14
+ # state.fetch(:value)
15
+ # end
5
16
  class Key < Unit
6
- using State::Extension
17
+ # @return [#hash
18
+ attribute :key, Types::Key
7
19
 
8
- attribute :key, Types::Hash.not
9
- requirement Types::Hash.constrained(min_size: 1)
20
+ requirement Types::Hash
10
21
 
11
- # Fetches {#input[value]} and passes it to {block}
22
+ # Selects {#key} from state and passes it to block
12
23
  #
13
- # @param [State] state
24
+ # @param outer_state [State<Hash<K, V>>]
14
25
  #
15
- # @yieldparam [State]
16
- # @yieldreturn [State<T>]
17
-
18
- # @return [State<T>]
19
- def call(state, &block)
20
- unless block
21
- return call(state, &:itself)
26
+ # @yieldparam [State<V>]
27
+ # @yieldreturn [State<U>]
28
+ #
29
+ # @return [State<U>]
30
+ def call(outer_state, &block)
31
+ unless block_given?
32
+ raise ArgumentError, "The key selector requires an iteration block"
22
33
  end
23
34
 
24
- state.bind(key: key) do |hash, inner_state, &error|
35
+ outer_state.bind(key: key) do |hash, state|
25
36
  requirement[hash] do
26
- return error["Expected a hash"]
37
+ state.fatal!("Expected hash but got %p (%s)", hash, hash.class)
27
38
  end
28
39
 
29
40
  value = hash.fetch(key) do
30
- return error["Key [#{key}] not found"]
41
+ state.ignore!("Key %p (%s) not found in hash %p (%s)",
42
+ key,
43
+ key.class,
44
+ hash,
45
+ hash.class)
31
46
  end
32
47
 
33
- block[inner_state.set(value, key: key)]
48
+ state.set(value, key: key).then(&block)
34
49
  end
35
50
  end
36
51
  end
@@ -1,12 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
+ # Defines how a path element, or selector
5
+ # Specifies how a value is extracted from a state
4
6
  class Selector < Dry::Interface
5
7
  defines :requirement, type: Types::Any.constrained(type: Dry::Types::Type)
6
8
  requirement Types::Any
7
9
 
10
+ # Selects value from state, package it as a state and passes it to block
11
+ #
12
+ # @param state [State]
13
+ #
14
+ # @yieldparam [State]
15
+ # @yieldreturn [State]
16
+ #
17
+ # @return [State]
18
+ #
19
+ # @abstract
20
+ def call(state)
21
+ raise NotImplementedError, "#{self.class}#call not implemented"
22
+ end
23
+
8
24
  private
9
25
 
26
+ # @return [Dry::Types::Type]
10
27
  def requirement
11
28
  self.class.requirement
12
29
  end