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