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
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
|