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.
- checksums.yaml +7 -0
- data/lib/remap/base.rb +146 -0
- data/lib/remap/compiler.rb +171 -0
- data/lib/remap/constructor/argument.rb +28 -0
- data/lib/remap/constructor/keyword.rb +31 -0
- data/lib/remap/constructor/none.rb +23 -0
- data/lib/remap/constructor.rb +30 -0
- data/lib/remap/error.rb +7 -0
- data/lib/remap/failure.rb +27 -0
- data/lib/remap/iteration/array.rb +29 -0
- data/lib/remap/iteration/hash.rb +30 -0
- data/lib/remap/iteration/other.rb +18 -0
- data/lib/remap/iteration.rb +20 -0
- data/lib/remap/mapper/and.rb +29 -0
- data/lib/remap/mapper/binary.rb +16 -0
- data/lib/remap/mapper/or.rb +21 -0
- data/lib/remap/mapper/xor.rb +27 -0
- data/lib/remap/mapper.rb +56 -0
- data/lib/remap/nothing.rb +7 -0
- data/lib/remap/operation.rb +26 -0
- data/lib/remap/result.rb +11 -0
- data/lib/remap/rule/each.rb +37 -0
- data/lib/remap/rule/embed.rb +42 -0
- data/lib/remap/rule/map.rb +109 -0
- data/lib/remap/rule/set.rb +43 -0
- data/lib/remap/rule/support/collection/empty.rb +23 -0
- data/lib/remap/rule/support/collection/filled.rb +27 -0
- data/lib/remap/rule/support/collection.rb +11 -0
- data/lib/remap/rule/support/enum.rb +63 -0
- data/lib/remap/rule/support/path.rb +45 -0
- data/lib/remap/rule/void.rb +29 -0
- data/lib/remap/rule/wrap.rb +30 -0
- data/lib/remap/rule.rb +8 -0
- data/lib/remap/selector/all.rb +21 -0
- data/lib/remap/selector/index.rb +39 -0
- data/lib/remap/selector/key.rb +38 -0
- data/lib/remap/selector.rb +14 -0
- data/lib/remap/state/extension.rb +393 -0
- data/lib/remap/state/schema.rb +21 -0
- data/lib/remap/state/types.rb +11 -0
- data/lib/remap/state.rb +22 -0
- data/lib/remap/static/fixed.rb +20 -0
- data/lib/remap/static/option.rb +22 -0
- data/lib/remap/static.rb +6 -0
- data/lib/remap/struct.rb +7 -0
- data/lib/remap/success.rb +29 -0
- data/lib/remap/types.rb +47 -0
- data/lib/remap/version.rb +5 -0
- data/lib/remap.rb +28 -0
- metadata +329 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e5df1ff079e3e2d14f9f35541efed74d87bbebe54deb39e8571998a2bdb11850
|
4
|
+
data.tar.gz: ad295f5b2967f8bff292a662fc2cb7db222c724567edd52657f7e88b83560f33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57b267a4b302721f874ca93ffbf924ec3b1a90ee8d33d7728091bc9bb2f0a4e14b84ca60f037319030f4c9a4e0945d25217a70e772edac161f49f8f5a66797fd
|
7
|
+
data.tar.gz: 4e664e07f2edb8865836265d5e36484887b1b206410e45f92d73ed4cfdbd5c0b8201cae1156478ac8503163fe0c65b16a36776138797d7b5682c295c626cb0fb
|
data/lib/remap/base.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads/all"
|
4
|
+
|
5
|
+
module Remap
|
6
|
+
class Base < Mapper
|
7
|
+
include Dry::Core::Memoizable
|
8
|
+
include Dry::Core::Constants
|
9
|
+
extend Dry::Monads[:result]
|
10
|
+
|
11
|
+
extend Dry::Configurable
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
using State::Extension
|
15
|
+
extend State
|
16
|
+
|
17
|
+
CONTRACT = Dry::Schema.JSON do
|
18
|
+
# NOP
|
19
|
+
end
|
20
|
+
|
21
|
+
setting :constructor, default: IDENTITY
|
22
|
+
setting :options, default: EMPTY_ARRAY
|
23
|
+
setting :rules, default: EMPTY_ARRAY
|
24
|
+
setting :contract, default: CONTRACT
|
25
|
+
setting :context, default: IDENTITY
|
26
|
+
|
27
|
+
delegate [:config] => self
|
28
|
+
|
29
|
+
schema schema.strict(false)
|
30
|
+
|
31
|
+
# Holds the current context
|
32
|
+
# @private
|
33
|
+
def self.contract(&context)
|
34
|
+
config.contract = Dry::Schema.JSON(&context)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @see Dry::Validation::Contract.rule
|
38
|
+
def self.rule(...)
|
39
|
+
config.rules << ->(*) { rule(...) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Defines a a constructor argument for the mapper
|
43
|
+
#
|
44
|
+
# @param name [Symbol]
|
45
|
+
# @param type [#call]
|
46
|
+
def self.option(field, type: Types::Any)
|
47
|
+
attribute(field, type)
|
48
|
+
|
49
|
+
unless (key = schema.keys.find { _1.name == field })
|
50
|
+
raise ArgumentError, "Could not locate [#{field}] in [#{self}]"
|
51
|
+
end
|
52
|
+
|
53
|
+
config.options << ->(*) { option(field, type: key) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Pretty print the mapper
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
def self.inspect
|
60
|
+
"<#{self.class} #{rule}, #{self}>"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Defines a mapper with a constructor used to wrap the output
|
64
|
+
#
|
65
|
+
# @param constructor [#call]
|
66
|
+
#
|
67
|
+
# @example A mapper from path :a to path :b
|
68
|
+
# class Mapper < Remap
|
69
|
+
# define do
|
70
|
+
# map :a, to: :b
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# Mapper.call(a: 1) # => { b: 1 }
|
75
|
+
def self.define(target = Nothing, method: :new, strategy: :argument, &context)
|
76
|
+
unless context
|
77
|
+
raise ArgumentError, "Missing block"
|
78
|
+
end
|
79
|
+
|
80
|
+
config.context = Compiler.call(&context)
|
81
|
+
config.constructor = Constructor.call(method: method, strategy: strategy, target: target)
|
82
|
+
rescue Dry::Struct::Error => e
|
83
|
+
raise ArgumentError, e.message
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates a new mapper
|
87
|
+
#
|
88
|
+
# @param input [Any]
|
89
|
+
# @param params [Hash]
|
90
|
+
|
91
|
+
# @return [Context]
|
92
|
+
|
93
|
+
extend Operation
|
94
|
+
|
95
|
+
def self.call!(state, &error)
|
96
|
+
new(state.options).call(state._.set(mapper: self), &error)
|
97
|
+
rescue Dry::Struct::Error => e
|
98
|
+
raise ArgumentError, "Option missing to mapper [#{self}]: #{e}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Creates a mapper tree using {#context} and uses {#state} as argument
|
102
|
+
#
|
103
|
+
# @return [State]
|
104
|
+
#
|
105
|
+
# @see .call!
|
106
|
+
#
|
107
|
+
# @private
|
108
|
+
def call(state, &error)
|
109
|
+
unless error
|
110
|
+
raise ArgumentError, "Missing block"
|
111
|
+
end
|
112
|
+
|
113
|
+
state.tap do |input|
|
114
|
+
contract.call(input, state.options).tap do |result|
|
115
|
+
unless result.success?
|
116
|
+
return error[state.failure(result.errors.to_h)]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
state.then(&config.context).then(&config.constructor)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def contract(scope: self)
|
127
|
+
Class.new(Dry::Validation::Contract) do |klass|
|
128
|
+
config = scope.class.config
|
129
|
+
|
130
|
+
config.rules.each do |rule|
|
131
|
+
klass.class_eval(&rule)
|
132
|
+
end
|
133
|
+
|
134
|
+
config.options.each do |option|
|
135
|
+
klass.class_eval(&option)
|
136
|
+
end
|
137
|
+
|
138
|
+
schema(config.contract)
|
139
|
+
end.new(**attributes)
|
140
|
+
end
|
141
|
+
|
142
|
+
def config
|
143
|
+
self.class.config
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Compiler
|
5
|
+
include Dry::Core::Constants
|
6
|
+
extend Dry::Initializer
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
param :rules, default: -> { EMPTY_ARRAY.dup }
|
10
|
+
|
11
|
+
# @return [Rule]
|
12
|
+
delegate call: self
|
13
|
+
|
14
|
+
# Constructs a rule tree given {block}
|
15
|
+
#
|
16
|
+
# @return [Rule]
|
17
|
+
def self.call(&block)
|
18
|
+
unless block
|
19
|
+
return Rule::Void.new
|
20
|
+
end
|
21
|
+
|
22
|
+
new.tap { _1.instance_eval(&block) }.rule
|
23
|
+
end
|
24
|
+
|
25
|
+
# Maps {path} to {to} with {block} inbetween
|
26
|
+
#
|
27
|
+
# @param path ([]) [Array<Segment>, Segment]
|
28
|
+
# @param to ([]) [Array<Symbol>, Symbol]
|
29
|
+
#
|
30
|
+
# @return [Rule::Map]
|
31
|
+
def map(*path, to: EMPTY_ARRAY, &block)
|
32
|
+
add Rule::Map.new(
|
33
|
+
path: {
|
34
|
+
map: path.flatten,
|
35
|
+
to: [to].flatten
|
36
|
+
},
|
37
|
+
rule: call(&block)
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Maps using {mapper}
|
42
|
+
#
|
43
|
+
# @param mapper [Remap]
|
44
|
+
#
|
45
|
+
# @return [Rule::Embed]
|
46
|
+
def embed(mapper)
|
47
|
+
add Rule::Embed.new(mapper: mapper)
|
48
|
+
rescue Dry::Struct::Error
|
49
|
+
raise ArgumentError, "Embeded mapper must be [Remap::Mapper], got [#{mapper}]"
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param *path ([]) [Symbol, Array<Symbol>]
|
53
|
+
# @option to [Remap::Static]
|
54
|
+
#
|
55
|
+
# @return [Rule::Set]
|
56
|
+
# @raise [ArgumentError]
|
57
|
+
# if no path given
|
58
|
+
# if path is not a Symbol or Array<Symbol>
|
59
|
+
def set(*path, to:)
|
60
|
+
add Rule::Set.new(path: { to: path.flatten, map: EMPTY_ARRAY }, value: to)
|
61
|
+
rescue Dry::Struct::Error => e
|
62
|
+
raise ArgumentError, e.message
|
63
|
+
end
|
64
|
+
|
65
|
+
# Maps to {path} from {map} with {block} inbetween
|
66
|
+
#
|
67
|
+
# @param path [Array<Symbol>, Symbol]
|
68
|
+
# @param map [Array<Segment>, Segment]
|
69
|
+
#
|
70
|
+
# @return [Rule::Map]
|
71
|
+
def to(*path, map: EMPTY_ARRAY, &block)
|
72
|
+
map(*map, to: path, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Iterates over the input value, passes each value
|
76
|
+
# to its block and merges the result back together
|
77
|
+
#
|
78
|
+
# @return [Rule::Each]]
|
79
|
+
# @raise [ArgumentError] if no block given
|
80
|
+
def each(&block)
|
81
|
+
unless block
|
82
|
+
raise ArgumentError, "no block given"
|
83
|
+
end
|
84
|
+
|
85
|
+
add Rule::Each.new(rule: call(&block))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Wraps output in {type}
|
89
|
+
#
|
90
|
+
# @param type [:array]
|
91
|
+
#
|
92
|
+
# @yieldreturn [Rule]
|
93
|
+
#
|
94
|
+
# @return [Rule::Wrap]
|
95
|
+
# @raise [ArgumentError] if type is not :array
|
96
|
+
def wrap(type, &block)
|
97
|
+
unless block
|
98
|
+
raise ArgumentError, "no block given"
|
99
|
+
end
|
100
|
+
|
101
|
+
add Rule::Wrap.new(type: type, rule: call(&block))
|
102
|
+
rescue Dry::Struct::Error => e
|
103
|
+
raise ArgumentError, e.message
|
104
|
+
end
|
105
|
+
|
106
|
+
# Selects all elements
|
107
|
+
#
|
108
|
+
# @return [Rule::Path::Segment::Quantifier::All]
|
109
|
+
def all
|
110
|
+
Selector::All.new(EMPTY_HASH)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Static value to be selected
|
114
|
+
#
|
115
|
+
# @param value [Any]
|
116
|
+
#
|
117
|
+
# @return [Rule::Static::Fixed]
|
118
|
+
def value(value)
|
119
|
+
Static::Fixed.new(value: value)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Static option to be selected
|
123
|
+
#
|
124
|
+
# @param id [Symbol]
|
125
|
+
#
|
126
|
+
# @return [Rule::Static::Option]
|
127
|
+
def option(id)
|
128
|
+
Static::Option.new(name: id)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Selects {index} element in input
|
132
|
+
#
|
133
|
+
# @param index [Integer]
|
134
|
+
#
|
135
|
+
# @return [Path::Segment::Key]
|
136
|
+
# @raise [ArgumentError] if index is not an Integer
|
137
|
+
def at(index)
|
138
|
+
Selector::Index.new(index: index)
|
139
|
+
rescue Dry::Struct::Error
|
140
|
+
raise ArgumentError, "Selector at(index) requires an integer argument, got [#{index}] (#{index.class})"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Selects first element in input
|
144
|
+
#
|
145
|
+
# @return [Path::Segment::Key]]
|
146
|
+
def first
|
147
|
+
at(0)
|
148
|
+
end
|
149
|
+
alias any first
|
150
|
+
|
151
|
+
# Selects last element in input
|
152
|
+
#
|
153
|
+
# @return [Path::Segment::Key]
|
154
|
+
def last
|
155
|
+
at(-1)
|
156
|
+
end
|
157
|
+
|
158
|
+
# The final rule
|
159
|
+
#
|
160
|
+
# @return [Rule]
|
161
|
+
def rule
|
162
|
+
Rule::Collection.call(rules: rules)
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def add(rule)
|
168
|
+
rule.tap { rules << rule }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Constructor
|
5
|
+
class Argument < Concrete
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
attribute :strategy, Value(:argument), default: :argument
|
9
|
+
|
10
|
+
# Uses the {#method} method to initialize {#target} with {state}
|
11
|
+
# Target is only called if {state} is defined
|
12
|
+
#
|
13
|
+
# Fails if {#target} does not respond to {#method}
|
14
|
+
# Fails if {#target} cannot be called with {state}
|
15
|
+
#
|
16
|
+
# @param state [State]
|
17
|
+
#
|
18
|
+
# @return [State]
|
19
|
+
def call(state)
|
20
|
+
super.fmap do |input|
|
21
|
+
target.public_send(id, input)
|
22
|
+
rescue ArgumentError => e
|
23
|
+
raise e.exception("Could not load target [#{target}] using the argument strategy with [#{input}] (#{input.class})")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Constructor
|
5
|
+
class Keyword < Concrete
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
attribute :strategy, Value(:keyword)
|
9
|
+
|
10
|
+
# Calls {#target} as with keyword arguments
|
11
|
+
#
|
12
|
+
# Fails if {#target} does not respond to {#method}
|
13
|
+
# Fails if {#target} cannot be called with {state}
|
14
|
+
#
|
15
|
+
# @param state [State]
|
16
|
+
#
|
17
|
+
# @return [State]
|
18
|
+
def call(state)
|
19
|
+
super.fmap do |input, &error|
|
20
|
+
unless input.is_a?(Hash)
|
21
|
+
return error["Input is not a hash"]
|
22
|
+
end
|
23
|
+
|
24
|
+
target.public_send(id, **input)
|
25
|
+
rescue ArgumentError => e
|
26
|
+
raise e.exception("Could not load target [#{target}] using the keyword strategy using [#{input}] (#{input.class})")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Constructor
|
5
|
+
class None < Concrete
|
6
|
+
attribute :target, Types::Nothing
|
7
|
+
attribute :strategy, Types::Any
|
8
|
+
attribute :method, Types::Any
|
9
|
+
|
10
|
+
# Just returns the input state
|
11
|
+
#
|
12
|
+
# Fails if {#target} does not respond to {#method}
|
13
|
+
# Fails if {#target} cannot be called with {state}
|
14
|
+
#
|
15
|
+
# @param state [State]
|
16
|
+
#
|
17
|
+
# @return [State]
|
18
|
+
def call(state)
|
19
|
+
state
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Constructor < Dry::Interface
|
5
|
+
attribute :method, Symbol, default: :new
|
6
|
+
attribute :target, Types::Any.constrained(not_eql: Nothing)
|
7
|
+
|
8
|
+
# Ensures {#target} responds to {#method}
|
9
|
+
# Returns an error state unless above is true
|
10
|
+
#
|
11
|
+
# @param state [State]
|
12
|
+
#
|
13
|
+
# @return [State]
|
14
|
+
def call(state)
|
15
|
+
state.tap do
|
16
|
+
unless target.respond_to?(id)
|
17
|
+
raise ArgumentError, "Target [#{target}] does not respond to [#{id}]"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def id
|
23
|
+
attributes.fetch(:method)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_proc
|
27
|
+
method(:call).to_proc
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/remap/error.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Failure < Result
|
5
|
+
attribute :reasons, Types::Hash
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
format("Failure<[%<result>s]>", result: JSON.pretty_generate(to_h))
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
{ failure: reasons, problems: problems }
|
13
|
+
end
|
14
|
+
|
15
|
+
def failure?(*)
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def success?(*)
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def fmap
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Iteration
|
5
|
+
class Array < Concrete
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
attribute :value, Types::Array
|
9
|
+
attribute :state, Types::State
|
10
|
+
|
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._
|
19
|
+
end
|
20
|
+
alias call map
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def init
|
25
|
+
state.set(EMPTY_ARRAY)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Iteration
|
5
|
+
class Hash < Concrete
|
6
|
+
attribute :value, Types::Hash
|
7
|
+
attribute :state, Types::State
|
8
|
+
|
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._
|
20
|
+
end
|
21
|
+
alias call map
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def init
|
26
|
+
state.set(EMPTY_HASH)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Iteration
|
5
|
+
class Other < Concrete
|
6
|
+
attribute :state, Types::State
|
7
|
+
attribute :value, Types::Any
|
8
|
+
|
9
|
+
using State::Extension
|
10
|
+
|
11
|
+
# @see Base#map
|
12
|
+
def map(&block)
|
13
|
+
block[value]._
|
14
|
+
end
|
15
|
+
alias call map
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Iteration < Dry::Interface
|
5
|
+
attribute :value, Types::Value
|
6
|
+
attribute :state, Types::State
|
7
|
+
|
8
|
+
# Maps every element in {#value} to {#block}
|
9
|
+
#
|
10
|
+
# @abstract
|
11
|
+
#
|
12
|
+
# @yieldparam element [V]
|
13
|
+
# @yieldparam key [K, Integer]
|
14
|
+
# @yieldreturn [Array<V>, Hash<V, K>]
|
15
|
+
#
|
16
|
+
# @return [Array<V>, Hash<V, K>]
|
17
|
+
|
18
|
+
order :Hash, :Array, :Other
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
class And < Binary
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
def call!(state, &error)
|
9
|
+
unless error
|
10
|
+
return call!(state, &exception)
|
11
|
+
end
|
12
|
+
|
13
|
+
state1 = left.call!(state) do |failure1|
|
14
|
+
right.call!(state) do |failure2|
|
15
|
+
return error[failure1.merge(failure2)]
|
16
|
+
end
|
17
|
+
|
18
|
+
return error[failure1]
|
19
|
+
end
|
20
|
+
|
21
|
+
state2 = right.call!(state) do |failure|
|
22
|
+
return error[failure]
|
23
|
+
end
|
24
|
+
|
25
|
+
state1.merged(state2)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
class Binary < self
|
6
|
+
attribute :left, Types::Mapper
|
7
|
+
attribute :right, Types::Mapper
|
8
|
+
|
9
|
+
def exception
|
10
|
+
->(error) { raise error }
|
11
|
+
end
|
12
|
+
|
13
|
+
include Operation
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
class Or < Binary
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
def call!(state, &error)
|
9
|
+
unless error
|
10
|
+
return call!(state, &exception)
|
11
|
+
end
|
12
|
+
|
13
|
+
left.call!(state) do |failure1|
|
14
|
+
return right.call!(state) do |failure2|
|
15
|
+
return error[failure1.merge(failure2)]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
class Xor < Binary
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
def call!(state, &error)
|
9
|
+
unless error
|
10
|
+
return call!(state, &exception)
|
11
|
+
end
|
12
|
+
|
13
|
+
state1 = left.call!(state) do |failure1|
|
14
|
+
return right.call!(state) do |failure2|
|
15
|
+
return error[failure1.merge(failure2)]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
state2 = right.call!(state) do
|
20
|
+
return state1
|
21
|
+
end
|
22
|
+
|
23
|
+
error[state1.merged(state2).failure("Both left and right passed in xor")]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|