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
data/lib/remap/mapper.rb
ADDED
@@ -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,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
|
data/lib/remap/result.rb
ADDED
@@ -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,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,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
|