remap 2.0.2

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/lib/remap/base.rb +146 -0
  3. data/lib/remap/compiler.rb +171 -0
  4. data/lib/remap/constructor/argument.rb +28 -0
  5. data/lib/remap/constructor/keyword.rb +31 -0
  6. data/lib/remap/constructor/none.rb +23 -0
  7. data/lib/remap/constructor.rb +30 -0
  8. data/lib/remap/error.rb +7 -0
  9. data/lib/remap/failure.rb +27 -0
  10. data/lib/remap/iteration/array.rb +29 -0
  11. data/lib/remap/iteration/hash.rb +30 -0
  12. data/lib/remap/iteration/other.rb +18 -0
  13. data/lib/remap/iteration.rb +20 -0
  14. data/lib/remap/mapper/and.rb +29 -0
  15. data/lib/remap/mapper/binary.rb +16 -0
  16. data/lib/remap/mapper/or.rb +21 -0
  17. data/lib/remap/mapper/xor.rb +27 -0
  18. data/lib/remap/mapper.rb +56 -0
  19. data/lib/remap/nothing.rb +7 -0
  20. data/lib/remap/operation.rb +26 -0
  21. data/lib/remap/result.rb +11 -0
  22. data/lib/remap/rule/each.rb +37 -0
  23. data/lib/remap/rule/embed.rb +42 -0
  24. data/lib/remap/rule/map.rb +109 -0
  25. data/lib/remap/rule/set.rb +43 -0
  26. data/lib/remap/rule/support/collection/empty.rb +23 -0
  27. data/lib/remap/rule/support/collection/filled.rb +27 -0
  28. data/lib/remap/rule/support/collection.rb +11 -0
  29. data/lib/remap/rule/support/enum.rb +63 -0
  30. data/lib/remap/rule/support/path.rb +45 -0
  31. data/lib/remap/rule/void.rb +29 -0
  32. data/lib/remap/rule/wrap.rb +30 -0
  33. data/lib/remap/rule.rb +8 -0
  34. data/lib/remap/selector/all.rb +21 -0
  35. data/lib/remap/selector/index.rb +39 -0
  36. data/lib/remap/selector/key.rb +38 -0
  37. data/lib/remap/selector.rb +14 -0
  38. data/lib/remap/state/extension.rb +393 -0
  39. data/lib/remap/state/schema.rb +21 -0
  40. data/lib/remap/state/types.rb +11 -0
  41. data/lib/remap/state.rb +22 -0
  42. data/lib/remap/static/fixed.rb +20 -0
  43. data/lib/remap/static/option.rb +22 -0
  44. data/lib/remap/static.rb +6 -0
  45. data/lib/remap/struct.rb +7 -0
  46. data/lib/remap/success.rb +29 -0
  47. data/lib/remap/types.rb +47 -0
  48. data/lib/remap/version.rb +5 -0
  49. data/lib/remap.rb +28 -0
  50. metadata +329 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ # @abstract
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
+ 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
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Nothing
5
+ # NOP
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Operation
5
+ using State::Extension
6
+ include State
7
+
8
+ def call(input, **options, &error)
9
+ new_state = state(input, options: options, mapper: self)
10
+
11
+ new_state = call!(new_state) do |failure|
12
+ return Failure.new(reasons: failure, problems: new_state.problems)
13
+ end
14
+
15
+ if error
16
+ return error[new_state]
17
+ end
18
+
19
+ value = new_state.fetch(:value) do
20
+ return Failure.new(reasons: new_state.failure("No mapped data"), problems: new_state.problems)
21
+ end
22
+
23
+ Success.new(problems: new_state.problems, result: value)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Result < Dry::Struct
5
+ attribute :problems, Types::Hash
6
+
7
+ def has_problem?
8
+ !problems.blank?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Each < Value
6
+ using State::Extension
7
+
8
+ attribute :rule, Types.Interface(:call)
9
+
10
+ # Iterates over {state} and passes each value to {rule}
11
+ # Restores {path} before returning state
12
+ #
13
+ #
14
+ # # @example
15
+ # class Mapper < Remap::Base
16
+ # define do
17
+ # map :people, to: :names do
18
+ # each do
19
+ # map(:name)
20
+ # end
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # Mapper.call(people: [{ name: "John" }, { name: "Jane" }]) # => { names: ["John", "Jane"] }
26
+ #
27
+ # @param state [State]
28
+ #
29
+ # @return [State]
30
+ def call(state)
31
+ state.map do |state|
32
+ rule.call(state)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Embed < Value
6
+ using State::Extension
7
+
8
+ attribute :mapper, Types::Mapper
9
+
10
+ # Evaluates {input} against {mapper} and returns the result
11
+ #
12
+ # @param state [State]
13
+ #
14
+ # @example Embed Mapper A into B
15
+ # class Car < Remap::Base
16
+ # define do
17
+ # map :name, to: :model
18
+ # end
19
+ # end
20
+ #
21
+ # class Person < Remap::Base
22
+ # define do
23
+ # to :person do
24
+ # to :car do
25
+ # embed Car
26
+ # end
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # Person.call(name: "Volvo") # => { person: { car: { name: "Volvo" } } }
32
+ #
33
+ #
34
+ # @return [State]
35
+ def call(state)
36
+ mapper.call!(state.set(mapper: mapper)) do |error|
37
+ return state.problem(error)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Map < self
6
+ using State::Extension
7
+
8
+ attribute :rule, Types.Interface(:call)
9
+ attribute :path, Path
10
+
11
+ # Maps {input} in 4 steps
12
+ #
13
+ # 1. Extract value from {input} using {path}
14
+ # 2. For each yielded value
15
+ # 2.1. Map value using {#rule}
16
+ # 2.2. Map value using {#fn}
17
+
18
+ # @example Map :a, to :b and add 5
19
+ # map = Map.new({
20
+ # path: { map: :a, to: :b },
21
+ # rule: Void.new,
22
+ # })
23
+ #
24
+ # map.adjust do |value|
25
+ # value + 5
26
+ # end
27
+ #
28
+ # map.call(a: 10) # => Success({ b: 15 })
29
+ #
30
+ # @param input [Any] Value to be mapped
31
+ # @param state [State] Current state
32
+ #
33
+ # @return [Monad::Result]
34
+ def call(state)
35
+ path.call(state) do |inner_state|
36
+ rule.call(inner_state).then do |init|
37
+ fn.reduce(init) do |inner, fn|
38
+ fn[inner]
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # Post-processor for {#call}
45
+ #
46
+ # @example Add 5 to mapped value
47
+ # class Mapper < Remap
48
+ # define do
49
+ # map.adjust do |value|
50
+ # value + 5
51
+ # end
52
+ # end
53
+ # end
54
+ #
55
+ # Mapper.call(10) # => 15
56
+ #
57
+ # @yieldparam value [Any] Mapped value
58
+ # @yieldreturn [Monad::Result, Any]
59
+ #
60
+ # @return [void]
61
+ def adjust(&block)
62
+ add do |state|
63
+ state.execute(&block)
64
+ end
65
+ end
66
+ alias then adjust
67
+
68
+ def pending(reason = "Pending mapping")
69
+ add do |state|
70
+ state.problem(reason)
71
+ end
72
+ end
73
+
74
+ def enum(&block)
75
+ add do |state|
76
+ state.fmap do |id, &error|
77
+ Enum.call(&block).get(id, &error)
78
+ end
79
+ end
80
+ end
81
+
82
+ def if(&block)
83
+ add do |state|
84
+ state.execute(&block).fmap do |bool, &error|
85
+ bool ? state.value : error["#if returned false"]
86
+ end
87
+ end
88
+ end
89
+
90
+ def if_not(&block)
91
+ add do |state|
92
+ state.execute(&block).fmap do |bool, &error|
93
+ bool ? error["#if_not returned true"] : state.value
94
+ end
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def add(&block)
101
+ tap { fn << block }
102
+ end
103
+
104
+ def fn
105
+ @fn ||= []
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Set < self
6
+ using State::Extension
7
+
8
+ attribute :value, Types.Interface(:call)
9
+ attribute :path, Path
10
+
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
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Collection
6
+ class Empty < Unit
7
+ using State::Extension
8
+
9
+ attribute? :rules, Value(EMPTY_ARRAY).default { EMPTY_ARRAY }
10
+
11
+ # Represents an empty define block, without any rules
12
+ #
13
+ # @param input [Any]
14
+ # @param state [State]
15
+ #
16
+ # @return [Monad::Failure]
17
+ def call(state)
18
+ state.problem("No rules, empty block")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Collection
6
+ class Filled < Unit
7
+ using State::Extension
8
+
9
+ attribute :rules, [Types.Interface(:call)], min_size: 1
10
+
11
+ # Represents a non-empty define block with one or more rules
12
+ # Calls every {#rules} with {input} and merges the output
13
+ #
14
+ # @param state [State]
15
+ #
16
+ # @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)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Collection < Dry::Interface
6
+ def to_proc
7
+ method(:call).to_proc
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Enum
6
+ include Dry::Core::Constants
7
+ include Dry::Monads[:maybe]
8
+
9
+ extend Dry::Initializer
10
+
11
+ option :mappings, default: -> { Hash.new { default } }
12
+ option :default, default: -> { None() }
13
+
14
+ alias execute instance_eval
15
+
16
+ def self.call(&block)
17
+ unless block
18
+ raise ArgumentError, "no block given"
19
+ end
20
+
21
+ new.tap { _1.execute(&block) }
22
+ end
23
+
24
+ def [](key)
25
+ mappings[key]
26
+ end
27
+
28
+ def get(key, &error)
29
+ unless error
30
+ return get(key) { raise Error, _1 }
31
+ end
32
+
33
+ mappings[key].bind { return _1 }.or do
34
+ error["Enum key [#{key}] not found among [#{mappings.keys.inspect}]"]
35
+ end
36
+ end
37
+ alias call get
38
+
39
+ # Map all keys in {keys} to {to}
40
+ #
41
+ # @return [VOID]
42
+ def from(*keys, to:)
43
+ keys.each do |key|
44
+ mappings[key] = Some(to)
45
+ end
46
+ end
47
+
48
+ # Maps {var} to {var}
49
+ #
50
+ # @return [VOID]
51
+ def value(id)
52
+ from(id, to: id)
53
+ end
54
+
55
+ # Fallback value when {#call} fails
56
+ #
57
+ # @return [Void]
58
+ def otherwise(value)
59
+ mappings.default = Some(value)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Path < Struct
6
+ using State::Extension
7
+
8
+ # @example [:a, :b, :c]
9
+ attribute :to, Types.Array(Types::Key)
10
+
11
+ # @example [:a, 0, :b, ALL]
12
+ attribute :map, Types.Array(Selector)
13
+
14
+ # Maps {state} from {map} to {block} to {to}
15
+ #
16
+ # @param state [State]
17
+ #
18
+ # @yieldparam [State]
19
+ # @yieldreturn [State<T>]
20
+ #
21
+ # @return [State<T>]
22
+ def call(state, &block)
23
+ unless block
24
+ raise ArgumentError, "block required"
25
+ end
26
+
27
+ selector(state).then(&block).fmap do |value|
28
+ to.reverse.reduce(value) do |val, key|
29
+ { key => val }
30
+ end
31
+ end._
32
+ end
33
+
34
+ private
35
+
36
+ def selector(state)
37
+ stack = map.reverse.reduce(IDENTITY) do |fn, selector|
38
+ ->(st) { selector.call(st, &fn) }
39
+ end
40
+
41
+ stack.call(state)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Void < self
6
+ using State::Extension
7
+
8
+ # Returns its input
9
+ #
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]
24
+ def call(state)
25
+ state.bind { _2.set(_1) }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/wrap"
4
+
5
+ module Remap
6
+ class Rule
7
+ class Wrap < self
8
+ using State::Extension
9
+
10
+ attribute :type, Value(:array)
11
+ attribute :rule, Types::Any
12
+
13
+ # Wraps the output from {#rule} in a {#type}
14
+ #
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) }
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/remap/rule.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule < Dry::Concrete
5
+ defines :requirement
6
+ requirement Types::Any
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Selector
5
+ class All < Concrete
6
+ using State::Extension
7
+
8
+ requirement Types::Enumerable
9
+
10
+ def call(state, &block)
11
+ state.bind(quantifier: "*") do |enumerable, inner_state, &error|
12
+ requirement[enumerable] do
13
+ return error["Expected an enumeration"]
14
+ end
15
+
16
+ inner_state.map(&block)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Selector
5
+ class Index < Unit
6
+ using State::Extension
7
+
8
+ attribute :index, Integer
9
+
10
+ requirement Types::Array
11
+
12
+ # Fetches {#input[value]} and passes it to {block}
13
+ #
14
+ # @param [State] state
15
+ #
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
24
+
25
+ state.bind(index: index) do |array, inner_state, &error|
26
+ requirement[array] do
27
+ return error["Expected an array"]
28
+ end
29
+
30
+ element = array.fetch(index) do
31
+ return error["No element on index at index #{index}"]
32
+ end
33
+
34
+ block[inner_state.set(element, index: index)]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Selector
5
+ class Key < Unit
6
+ using State::Extension
7
+
8
+ attribute :key, Types::Hash.not
9
+ requirement Types::Hash.constrained(min_size: 1)
10
+
11
+ # Fetches {#input[value]} and passes it to {block}
12
+ #
13
+ # @param [State] state
14
+ #
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)
22
+ end
23
+
24
+ state.bind(key: key) do |hash, inner_state, &error|
25
+ requirement[hash] do
26
+ return error["Expected a hash"]
27
+ end
28
+
29
+ value = hash.fetch(key) do
30
+ return error["Key [#{key}] not found"]
31
+ end
32
+
33
+ block[inner_state.set(value, key: key)]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Selector < Dry::Interface
5
+ defines :requirement, type: Types::Any.constrained(type: Dry::Types::Type)
6
+ requirement Types::Any
7
+
8
+ private
9
+
10
+ def requirement
11
+ self.class.requirement
12
+ end
13
+ end
14
+ end