remap 2.0.3 → 2.1.6

Sign up to get free protection for your applications and to get access to all the features.
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 +91 -89
  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