remap 2.0.3 → 2.1.0
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 +4 -4
- data/lib/remap/base.rb +229 -75
- data/lib/remap/compiler.rb +127 -37
- data/lib/remap/constructor/argument.rb +20 -6
- data/lib/remap/constructor/keyword.rb +20 -4
- data/lib/remap/constructor/none.rb +3 -4
- data/lib/remap/constructor.rb +12 -5
- data/lib/remap/contract.rb +27 -0
- data/lib/remap/extensions/enumerable.rb +48 -0
- data/lib/remap/extensions/hash.rb +13 -0
- data/lib/remap/extensions/object.rb +37 -0
- data/lib/remap/failure.rb +25 -15
- data/lib/remap/iteration/array.rb +20 -11
- data/lib/remap/iteration/hash.rb +21 -13
- data/lib/remap/iteration/other.rb +7 -7
- data/lib/remap/iteration.rb +8 -2
- data/lib/remap/mapper/and.rb +29 -7
- data/lib/remap/mapper/binary.rb +3 -6
- data/lib/remap/mapper/or.rb +29 -6
- data/lib/remap/mapper/support/operations.rb +40 -0
- data/lib/remap/mapper/xor.rb +29 -7
- data/lib/remap/mapper.rb +1 -48
- data/lib/remap/notice/traced.rb +19 -0
- data/lib/remap/notice/untraced.rb +11 -0
- data/lib/remap/notice.rb +34 -0
- data/lib/remap/operation.rb +26 -13
- data/lib/remap/path/input.rb +37 -0
- data/lib/remap/path/output.rb +26 -0
- data/lib/remap/path.rb +22 -0
- data/lib/remap/path_error.rb +13 -0
- data/lib/remap/proxy.rb +18 -0
- data/lib/remap/rule/each.rb +25 -24
- data/lib/remap/rule/embed.rb +33 -28
- data/lib/remap/rule/map/optional.rb +42 -0
- data/lib/remap/rule/map/required.rb +35 -0
- data/lib/remap/rule/map.rb +176 -55
- data/lib/remap/rule/set.rb +23 -33
- data/lib/remap/rule/support/collection/empty.rb +7 -7
- data/lib/remap/rule/support/collection/filled.rb +21 -8
- data/lib/remap/rule/support/collection.rb +11 -3
- data/lib/remap/rule/support/enum.rb +44 -21
- data/lib/remap/rule/void.rb +17 -18
- data/lib/remap/rule/wrap.rb +25 -17
- data/lib/remap/rule.rb +8 -1
- data/lib/remap/selector/all.rb +29 -7
- data/lib/remap/selector/index.rb +24 -16
- data/lib/remap/selector/key.rb +31 -16
- data/lib/remap/selector.rb +17 -0
- data/lib/remap/state/extension.rb +182 -208
- data/lib/remap/state/schema.rb +1 -1
- data/lib/remap/state.rb +30 -4
- data/lib/remap/static/fixed.rb +14 -3
- data/lib/remap/static/option.rb +21 -6
- data/lib/remap/static.rb +13 -0
- data/lib/remap/struct.rb +1 -0
- data/lib/remap/types.rb +13 -28
- data/lib/remap.rb +15 -19
- metadata +91 -89
- data/lib/remap/result.rb +0 -11
- data/lib/remap/rule/support/path.rb +0 -45
- data/lib/remap/state/types.rb +0 -11
- data/lib/remap/success.rb +0 -29
- data/lib/remap/version.rb +0 -5
data/lib/remap/mapper.rb
CHANGED
@@ -3,54 +3,7 @@
|
|
3
3
|
module Remap
|
4
4
|
# @abstract
|
5
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
6
|
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
|
7
|
+
include Operations
|
55
8
|
end
|
56
9
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Notice
|
5
|
+
class Traced < Concrete
|
6
|
+
attribute? :backtrace, Types::Backtrace, default: EMPTY_ARRAY
|
7
|
+
|
8
|
+
def traced(backtrace)
|
9
|
+
Notice.call(**attributes, backtrace: backtrace)
|
10
|
+
end
|
11
|
+
|
12
|
+
def exception
|
13
|
+
return super if backtrace.blank?
|
14
|
+
|
15
|
+
super.tap { _1.set_backtrace(backtrace.map(&:to_s)) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/remap/notice.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
using Extensions::Hash
|
5
|
+
|
6
|
+
class Notice < Dry::Interface
|
7
|
+
attribute? :value, Types::Any
|
8
|
+
attribute :reason, String
|
9
|
+
attribute :path, Array
|
10
|
+
|
11
|
+
class Error < Remap::Error
|
12
|
+
extend Dry::Initializer
|
13
|
+
|
14
|
+
param :notice, type: Notice
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"#<%s %s>" % [self.class, to_hash.formated]
|
19
|
+
end
|
20
|
+
alias to_s inspect
|
21
|
+
|
22
|
+
# Hash representation of the notice
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
def to_hash
|
26
|
+
super.except(:backtrace).compact_blank
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Error]
|
30
|
+
def exception
|
31
|
+
Error.new(self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/remap/operation.rb
CHANGED
@@ -1,26 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Remap
|
4
|
-
|
5
|
-
using State::Extension
|
6
|
-
include State
|
4
|
+
using State::Extension
|
7
5
|
|
6
|
+
# Class interface for {Remap::Base} and instance interface for {Mapper}
|
7
|
+
module Operation
|
8
|
+
# Public interface for mappers
|
9
|
+
#
|
10
|
+
# @param input [Any] Data to be mapped
|
11
|
+
# @param options [Hash] Mapper arguments
|
12
|
+
#
|
13
|
+
# @yield [Failure] if mapper fails
|
14
|
+
#
|
15
|
+
# @return [Success] if mapper succeeds
|
8
16
|
def call(input, **options, &error)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
17
|
+
unless error
|
18
|
+
return call(input, **options) do |failure|
|
19
|
+
raise failure.exception
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
|
-
|
16
|
-
|
23
|
+
other = State.call(input, options: options, mapper: self).then do |state|
|
24
|
+
call!(state) do |failure|
|
25
|
+
return error[failure]
|
26
|
+
end
|
17
27
|
end
|
18
28
|
|
19
|
-
|
20
|
-
|
29
|
+
case other
|
30
|
+
in { value: }
|
31
|
+
value
|
32
|
+
in { notices: [] }
|
33
|
+
error[other.failure("No return value")]
|
34
|
+
in { notices: }
|
35
|
+
error[Failure.call(failures: notices)]
|
21
36
|
end
|
22
|
-
|
23
|
-
Success.new(problems: new_state.problems, result: value)
|
24
37
|
end
|
25
38
|
end
|
26
39
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Path
|
5
|
+
using State::Extension
|
6
|
+
|
7
|
+
# Returns the value at a given path
|
8
|
+
#
|
9
|
+
# @example Select "A" from { a: { b: { c: ["A"] } } }
|
10
|
+
# state = Remap::State.call({ a: { b: { c: ["A"] } } })
|
11
|
+
# first = Remap::Selector::Index.new(index: 0)
|
12
|
+
# path = Remap::Path::Input.new([:a, :b, :c, first])
|
13
|
+
#
|
14
|
+
# path.call(state) do |state|
|
15
|
+
# state.fetch(:value)
|
16
|
+
# end
|
17
|
+
class Input < Unit
|
18
|
+
# @return [Array<Selector>]
|
19
|
+
attribute :segments, [Selector]
|
20
|
+
|
21
|
+
# Selects the value at the path {#segments}
|
22
|
+
#
|
23
|
+
# @param state [State]
|
24
|
+
#
|
25
|
+
# @return [State]
|
26
|
+
def call(state, &iterator)
|
27
|
+
unless block_given?
|
28
|
+
raise ArgumentError, "Input path requires an iterator block"
|
29
|
+
end
|
30
|
+
|
31
|
+
segments.reverse.reduce(iterator) do |inner_iterator, selector|
|
32
|
+
-> inner_state { selector.call(inner_state, &inner_iterator) }
|
33
|
+
end.call(state)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Path
|
5
|
+
using Extensions::Enumerable
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
# Sets the value to a given path
|
9
|
+
#
|
10
|
+
# @example Maps "A" to { a: { b: { c: "A" } } }
|
11
|
+
# state = Remap::State.call("A")
|
12
|
+
# result = Remap::Path::Output.new([:a, :b, :c]).call(state)
|
13
|
+
#
|
14
|
+
# result.fetch(:value) # => { a: { b: { c: "A" } } }
|
15
|
+
class Output < Unit
|
16
|
+
attribute :segments, [Types::Key]
|
17
|
+
|
18
|
+
# @return [State]
|
19
|
+
def call(state)
|
20
|
+
state.fmap do |value|
|
21
|
+
segments.hide(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/remap/path.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
# Represents a sequence of keys and selects or maps a value given a path
|
5
|
+
class Path < Dry::Interface
|
6
|
+
attribute :segments, Types::Array
|
7
|
+
|
8
|
+
delegate :>>, to: :to_proc
|
9
|
+
|
10
|
+
# @return [State]
|
11
|
+
#
|
12
|
+
# @abstract
|
13
|
+
def call(state)
|
14
|
+
raise NotImplementedError, "#{self.class}#call not implemented"
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Proc]
|
18
|
+
def to_proc
|
19
|
+
method(:call).to_proc
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/remap/proxy.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Proxy < ActiveSupport::ProxyObject
|
5
|
+
def self.const_missing(name)
|
6
|
+
::Object.const_get(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
include Dry::Core::Constants
|
10
|
+
extend Dry::Initializer
|
11
|
+
|
12
|
+
# See Object#tap
|
13
|
+
def tap(&block)
|
14
|
+
block[self]
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/remap/rule/each.rb
CHANGED
@@ -2,34 +2,35 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Rule
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
8
|
-
|
7
|
+
# Iterates over a rule, even if the rule is not a collection
|
8
|
+
#
|
9
|
+
# @example Upcase each value in an array
|
10
|
+
# state = Remap::State.call(["John", "Jane"])
|
11
|
+
# upcase = Remap::Rule::Map.call({}).then(&:upcase)
|
12
|
+
# each = Remap::Rule::Each.call(rule: upcase)
|
13
|
+
# error = -> failure { raise failure.exception }
|
14
|
+
# each.call(state, &error).fetch(:value) # => ["JOHN", "JANE"]
|
15
|
+
class Each < Unit
|
16
|
+
# @return [Rule]
|
17
|
+
attribute :rule, Types::Rule
|
9
18
|
|
10
|
-
# Iterates over
|
11
|
-
# Restores
|
19
|
+
# Iterates over state and passes each value to rule
|
20
|
+
# Restores element, key & index before returning state
|
12
21
|
#
|
22
|
+
# @param state [State<Enumerable>]
|
13
23
|
#
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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)
|
24
|
+
# @return [State<Enumerable>]
|
25
|
+
def call(state, &error)
|
26
|
+
unless error
|
27
|
+
raise ArgumentError, "Each#call(state, &error) requires a block"
|
28
|
+
end
|
29
|
+
|
30
|
+
state.map do |inner_state|
|
31
|
+
rule.call(inner_state) do |failure|
|
32
|
+
return error[failure]
|
33
|
+
end
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
data/lib/remap/rule/embed.rb
CHANGED
@@ -2,40 +2,45 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Rule
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Embed mappers into each other
|
8
|
+
#
|
9
|
+
# @example Embed Mapper A into B
|
10
|
+
# class Car < Remap::Base
|
11
|
+
# define do
|
12
|
+
# map :name, to: :model
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Person < Remap::Base
|
17
|
+
# define do
|
18
|
+
# to :person do
|
19
|
+
# to :car do
|
20
|
+
# embed Car
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Person.call({name: "Volvo"}) # => { person: { car: { model: "Volvo" } } }
|
27
|
+
class Embed < Unit
|
28
|
+
# @return [#call!]
|
8
29
|
attribute :mapper, Types::Mapper
|
9
30
|
|
10
|
-
# Evaluates
|
31
|
+
# Evaluates input against mapper and returns the result
|
11
32
|
#
|
12
|
-
# @param state [State]
|
33
|
+
# @param state [State<T>]
|
13
34
|
#
|
14
|
-
# @
|
15
|
-
|
16
|
-
|
17
|
-
|
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)
|
35
|
+
# @return [State<U>]
|
36
|
+
def call(state, &error)
|
37
|
+
unless error
|
38
|
+
raise ArgumentError, "A block is required to evaluate the embed"
|
38
39
|
end
|
40
|
+
|
41
|
+
mapper.call!(state.set(mapper: mapper)) do |failure|
|
42
|
+
return error[failure]
|
43
|
+
end.except(:mapper, :scope)
|
39
44
|
end
|
40
45
|
end
|
41
46
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Rule
|
5
|
+
class Map
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
class Optional < Concrete
|
9
|
+
# Represents an optional mapping rule
|
10
|
+
# When the mapping fails, the value is ignored
|
11
|
+
#
|
12
|
+
# @param state [State]
|
13
|
+
#
|
14
|
+
# @return [State]
|
15
|
+
def call(state, &error)
|
16
|
+
unless error
|
17
|
+
raise ArgumentError, "map.call(state, &error) requires a block"
|
18
|
+
end
|
19
|
+
|
20
|
+
fatal(state) do
|
21
|
+
return ignore(state) do
|
22
|
+
return notice(state) do
|
23
|
+
return super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Catches :ignore exceptions and re-package them as a state
|
32
|
+
#
|
33
|
+
# @param state [State]
|
34
|
+
#
|
35
|
+
# @return [State]
|
36
|
+
def ignore(state, &block)
|
37
|
+
state.set(notice: catch(:ignore, &block).traced(backtrace)).except(:value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Rule
|
5
|
+
class Map
|
6
|
+
using State::Extension
|
7
|
+
|
8
|
+
class Required < Concrete
|
9
|
+
attribute :backtrace, Types::Backtrace
|
10
|
+
|
11
|
+
# Represents a required mapping rule
|
12
|
+
# When it fails, the entire mapping is marked as failed
|
13
|
+
#
|
14
|
+
# @param state [State]
|
15
|
+
#
|
16
|
+
# @return [State]
|
17
|
+
def call(state, &error)
|
18
|
+
unless block_given?
|
19
|
+
raise ArgumentError, "Required.call(state, &error) requires a block"
|
20
|
+
end
|
21
|
+
|
22
|
+
notice = catch :ignore do
|
23
|
+
return fatal(state) do
|
24
|
+
return notice(state) do
|
25
|
+
return super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
error[state.failure(notice)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|