remap 2.2.40 → 2.2.44
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/remap/base.rb +39 -15
- data/lib/remap/catchable.rb +27 -0
- data/lib/remap/compiler.rb +36 -9
- data/lib/remap/config.rb +9 -0
- data/lib/remap/constructor/keyword.rb +2 -9
- data/lib/remap/extensions/array.rb +16 -0
- data/lib/remap/extensions/hash.rb +1 -1
- data/lib/remap/extensions/object.rb +6 -0
- data/lib/remap/failure/error.rb +20 -0
- data/lib/remap/failure.rb +7 -5
- data/lib/remap/iteration/array.rb +3 -6
- data/lib/remap/iteration/hash.rb +3 -6
- data/lib/remap/mapper/and.rb +6 -0
- data/lib/remap/mapper/binary.rb +6 -1
- data/lib/remap/mapper/or.rb +6 -0
- data/lib/remap/mapper/support/api.rb +48 -0
- data/lib/remap/mapper/xor.rb +6 -0
- data/lib/remap/notice/error.rb +0 -21
- data/lib/remap/notice.rb +0 -14
- data/lib/remap/rule/block.rb +19 -12
- data/lib/remap/rule/map/optional.rb +2 -8
- data/lib/remap/rule/map/required.rb +1 -34
- data/lib/remap/rule/map.rb +16 -25
- data/lib/remap/rule/void.rb +1 -3
- data/lib/remap/rule.rb +15 -0
- data/lib/remap/selector/index.rb +1 -1
- data/lib/remap/selector/key.rb +1 -1
- data/lib/remap/state/extension.rb +134 -45
- data/lib/remap/state/schema.rb +2 -0
- data/lib/remap/state.rb +2 -0
- data/lib/remap.rb +1 -0
- metadata +7 -3
- data/lib/remap/operation.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9c58b85124adc855131d9efee5ce1ff8653cf4828882a5ad1b60ebb0d1ea99b
|
4
|
+
data.tar.gz: ace57dfce51c7b820d2db5c2f742e5e44828bc998cc719a817bb040e4d08f139
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d868e87397958d9c68f2048a7888e306c3764dbac799ff5c34c2d8d00e08463fe5abacc708dcc232605c33f319b1cca0b34252108cfb8f4ca1f7831718956d9e
|
7
|
+
data.tar.gz: 3880cde868fbfa68702db58d6db914825f34614b140c3284c264f6b4303338dfda22f1a0f151419d610225c4cd029a776c149b4901607915988acf3c6598386a
|
data/lib/remap/base.rb
CHANGED
@@ -106,11 +106,13 @@ module Remap
|
|
106
106
|
class Base < Mapper
|
107
107
|
include ActiveSupport::Configurable
|
108
108
|
include Dry::Core::Constants
|
109
|
+
include Catchable
|
110
|
+
extend Mapper::API
|
109
111
|
using State::Extension
|
110
|
-
extend Operation
|
111
112
|
|
112
113
|
with_options instance_accessor: true do |scope|
|
113
114
|
scope.config_accessor(:contract) { Dry::Schema.define {} }
|
115
|
+
scope.config_accessor(:config_options) { Config.new }
|
114
116
|
scope.config_accessor(:constructor) { IDENTITY }
|
115
117
|
scope.config_accessor(:options) { EMPTY_ARRAY }
|
116
118
|
scope.config_accessor(:option) { EMPTY_HASH }
|
@@ -235,26 +237,50 @@ module Remap
|
|
235
237
|
# Mapper.call({name: "John"}).first_name # => "John"
|
236
238
|
#
|
237
239
|
# @return [void]
|
238
|
-
|
240
|
+
# rubocop:disable Layout/LineLength
|
241
|
+
def self.define(target = Nothing, method: :new, strategy: :argument, backtrace: caller, &context)
|
239
242
|
unless block_given?
|
240
243
|
raise ArgumentError, "#{self}.define requires a block"
|
241
244
|
end
|
242
245
|
|
243
246
|
self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
|
244
|
-
self.context = Compiler.call(&context)
|
247
|
+
self.context = Compiler.call(backtrace: backtrace, &context)
|
245
248
|
end
|
249
|
+
# rubocop:enable Layout/LineLength
|
246
250
|
|
247
|
-
# Similar to {::call}, but takes a state
|
248
|
-
#
|
249
251
|
# @param state [State]
|
250
252
|
#
|
251
|
-
# @yield [Failure]
|
253
|
+
# @yield [Failure]
|
254
|
+
# when a non-critical error occurs
|
255
|
+
# @yieldreturn T
|
252
256
|
#
|
253
|
-
# @return [
|
257
|
+
# @return [State, T]
|
258
|
+
# when request is a success
|
259
|
+
# @raise [Remap::Error]
|
260
|
+
# when a fatal error occurs
|
254
261
|
#
|
255
262
|
# @private
|
256
263
|
def self.call!(state, &error)
|
257
|
-
new(state.options).call(state
|
264
|
+
new(state.options).call(state, &error)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Configuration options for the mapper
|
268
|
+
#
|
269
|
+
# @yield [Config]
|
270
|
+
# @yieldreturn [void]
|
271
|
+
#
|
272
|
+
# @return [void]
|
273
|
+
def self.configuration(&block)
|
274
|
+
config = Config.new
|
275
|
+
block[config]
|
276
|
+
self.config_options = config
|
277
|
+
end
|
278
|
+
|
279
|
+
# @see Mapper::API
|
280
|
+
#
|
281
|
+
# @private
|
282
|
+
def self.validate?
|
283
|
+
config_options.validation
|
258
284
|
end
|
259
285
|
|
260
286
|
# Mappers state according to the mapper rules
|
@@ -267,8 +293,8 @@ module Remap
|
|
267
293
|
#
|
268
294
|
# @private
|
269
295
|
def call(state, &error)
|
270
|
-
|
271
|
-
raise ArgumentError, "
|
296
|
+
state._ do |reason|
|
297
|
+
raise ArgumentError, "Invalid state due to #{reason.formatted}"
|
272
298
|
end
|
273
299
|
|
274
300
|
state.tap do |input|
|
@@ -279,13 +305,11 @@ module Remap
|
|
279
305
|
end
|
280
306
|
end
|
281
307
|
|
282
|
-
|
283
|
-
return context.call(state)
|
284
|
-
return error[failure]
|
285
|
-
end.then(&constructor)
|
308
|
+
s1 = catch_ignored do |id|
|
309
|
+
return context.call(state.set(id: id)).then(&constructor).remove_id
|
286
310
|
end
|
287
311
|
|
288
|
-
|
312
|
+
Failure.new(failures: s1.notices).then(&error)
|
289
313
|
end
|
290
314
|
|
291
315
|
private
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
module Catchable
|
5
|
+
# @yieldparam id [Symbol]
|
6
|
+
# @yieldreturn [T]
|
7
|
+
#
|
8
|
+
# @return [T]
|
9
|
+
def catch_ignored(&block)
|
10
|
+
catch(to_id(:ignored), &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @yieldparam id [Symbol]
|
14
|
+
# @yieldreturn [T]
|
15
|
+
#
|
16
|
+
# @return [T]
|
17
|
+
def catch_fatal(&block)
|
18
|
+
catch(to_id(:fatal), &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def to_id(value)
|
24
|
+
[value, self.class.name&.downcase || :unknown].join("::")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/remap/compiler.rb
CHANGED
@@ -5,6 +5,8 @@ module Remap
|
|
5
5
|
|
6
6
|
# Constructs a {Rule} from the block passed to {Remap::Base.define}
|
7
7
|
class Compiler < Proxy
|
8
|
+
extend Catchable
|
9
|
+
include Catchable
|
8
10
|
# @return [Array<Rule>]
|
9
11
|
param :rules, type: Types.Array(Rule)
|
10
12
|
|
@@ -29,7 +31,7 @@ module Remap
|
|
29
31
|
# rule.call(state, &error).fetch(:value) # => { name: "John", age: 50 }
|
30
32
|
#
|
31
33
|
# @return [Rule]
|
32
|
-
def self.call(&block)
|
34
|
+
def self.call(backtrace: caller, &block)
|
33
35
|
unless block_given?
|
34
36
|
return Rule::VOID
|
35
37
|
end
|
@@ -38,7 +40,7 @@ module Remap
|
|
38
40
|
compiler.instance_exec(&block)
|
39
41
|
end.rules
|
40
42
|
|
41
|
-
Rule::Block.new(rules)
|
43
|
+
Rule::Block.new(backtrace: backtrace, rules: rules)
|
42
44
|
end
|
43
45
|
|
44
46
|
# Maps input path [input] to output path [to]
|
@@ -181,13 +183,15 @@ module Remap
|
|
181
183
|
raise ArgumentError, "#embed does not take a block"
|
182
184
|
end
|
183
185
|
|
184
|
-
|
185
|
-
|
186
|
-
next error[failure]
|
187
|
-
end.except(:mapper, :scope)
|
186
|
+
Types::Mapper[mapper] do
|
187
|
+
raise ArgumentError, "Argument to #embed must be a mapper, got #{mapper.class}"
|
188
188
|
end
|
189
189
|
|
190
|
-
add
|
190
|
+
result = rule(backtrace: backtrace).add do |s0|
|
191
|
+
build_embed(s0, mapper, backtrace)
|
192
|
+
end
|
193
|
+
|
194
|
+
add result
|
191
195
|
end
|
192
196
|
|
193
197
|
# Set a static value
|
@@ -230,6 +234,10 @@ module Remap
|
|
230
234
|
raise ArgumentError, "#set does not take a block"
|
231
235
|
end
|
232
236
|
|
237
|
+
unless to.is_a?(Static)
|
238
|
+
raise ArgumentError, "Argument to #set must be a static value, got #{to.class}"
|
239
|
+
end
|
240
|
+
|
233
241
|
add rule(to: path, backtrace: backtrace).add { to.call(_1) }
|
234
242
|
end
|
235
243
|
|
@@ -346,6 +354,10 @@ module Remap
|
|
346
354
|
raise ArgumentError, "#wrap requires a block"
|
347
355
|
end
|
348
356
|
|
357
|
+
unless type == :array
|
358
|
+
raise ArgumentError, "Argument to #wrap must equal :array, got [#{type}] (#{type.class})"
|
359
|
+
end
|
360
|
+
|
349
361
|
add rule(backtrace: backtrace, &block).then { Array.wrap(_1) }
|
350
362
|
end
|
351
363
|
|
@@ -526,7 +538,7 @@ module Remap
|
|
526
538
|
input: path.flatten
|
527
539
|
},
|
528
540
|
backtrace: backtrace,
|
529
|
-
rule: call(&block)
|
541
|
+
rule: call(backtrace: backtrace, &block)
|
530
542
|
})
|
531
543
|
end
|
532
544
|
|
@@ -536,8 +548,23 @@ module Remap
|
|
536
548
|
output: [to].flatten,
|
537
549
|
input: path.flatten
|
538
550
|
},
|
539
|
-
rule: call(&block)
|
551
|
+
rule: call(backtrace: backtrace, &block)
|
540
552
|
})
|
541
553
|
end
|
554
|
+
|
555
|
+
def build_embed(s0, mapper, backtrace)
|
556
|
+
f0 = catch_fatal do |fatal_id|
|
557
|
+
s1 = s0.set(fatal_id: fatal_id)
|
558
|
+
s2 = s1.set(mapper: mapper)
|
559
|
+
old_mapper = s0.fetch(:mapper)
|
560
|
+
|
561
|
+
return mapper.call!(s2) do |f1|
|
562
|
+
s3 = s2.set(notices: f1.notices + f1.failures)
|
563
|
+
s3.return!
|
564
|
+
end.except(:scope).merge(mapper: old_mapper)
|
565
|
+
end
|
566
|
+
|
567
|
+
raise f0.exception(backtrace)
|
568
|
+
end
|
542
569
|
end
|
543
570
|
end
|
data/lib/remap/config.rb
ADDED
@@ -27,19 +27,12 @@ module Remap
|
|
27
27
|
#
|
28
28
|
# @return [State]
|
29
29
|
def call(state)
|
30
|
-
super.fmap do |input
|
30
|
+
super.fmap do |input|
|
31
31
|
unless input.is_a?(Hash)
|
32
|
-
|
32
|
+
raise ArgumentError, "Expected Hash, got #{input.class}"
|
33
33
|
end
|
34
34
|
|
35
35
|
target.public_send(id, **input)
|
36
|
-
rescue ArgumentError => e
|
37
|
-
raise e.exception("Failed to create [%p] with input [%s] (%s}) using method %s" % [
|
38
|
-
target,
|
39
|
-
input,
|
40
|
-
input.class,
|
41
|
-
id
|
42
|
-
])
|
43
36
|
end
|
44
37
|
end
|
45
38
|
end
|
@@ -4,6 +4,11 @@ module Remap
|
|
4
4
|
module Extensions
|
5
5
|
module Object
|
6
6
|
refine ::Object do
|
7
|
+
# @return [Any]
|
8
|
+
def to_hash
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
7
12
|
# Fallback validation method
|
8
13
|
#
|
9
14
|
# @yield if block is provided
|
@@ -43,6 +48,7 @@ module Remap
|
|
43
48
|
end
|
44
49
|
alias_method :fetch, :get
|
45
50
|
|
51
|
+
# return [Any]
|
46
52
|
def formatted
|
47
53
|
self
|
48
54
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Failure
|
5
|
+
using Extensions::Hash
|
6
|
+
|
7
|
+
class Error < Error
|
8
|
+
extend Dry::Initializer
|
9
|
+
|
10
|
+
option :failure, type: Types.Instance(Failure)
|
11
|
+
delegate_missing_to :failure
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
def inspect
|
15
|
+
"#<%s %s>" % [self.class, to_hash.formatted]
|
16
|
+
end
|
17
|
+
alias to_s inspect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/remap/failure.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Remap
|
4
|
-
using Extensions::Hash
|
5
|
-
|
6
4
|
class Failure < Dry::Concrete
|
7
5
|
attribute :failures, Types.Array(Types::Notice), min_size: 1
|
8
6
|
attribute? :notices, Types.Array(Types::Notice), default: EMPTY_ARRAY
|
@@ -29,9 +27,13 @@ module Remap
|
|
29
27
|
new(failure)
|
30
28
|
end
|
31
29
|
|
32
|
-
# @
|
33
|
-
|
34
|
-
|
30
|
+
# @param backtrace [Array<String>] Backtrace from Kernel.caller
|
31
|
+
#
|
32
|
+
# @return [Failure::Error]
|
33
|
+
def exception(backtrace)
|
34
|
+
e = Error.new(failure: self)
|
35
|
+
e.set_backtrace(backtrace)
|
36
|
+
e
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
@@ -26,12 +26,9 @@ module Remap
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def reduce(state, value, index, &block)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
state.set(notice: notice)
|
29
|
+
s0 = block[value, index: index]
|
30
|
+
s1 = s0.set(**state.only(:ids, :fatal_id))
|
31
|
+
state.combine(s1.fmap { [_1] })
|
35
32
|
end
|
36
33
|
end
|
37
34
|
end
|
data/lib/remap/iteration/hash.rb
CHANGED
@@ -22,12 +22,9 @@ module Remap
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def reduce(state, key, value, &block)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
state.set(notice: notice)
|
25
|
+
s0 = block[value, key: key]
|
26
|
+
s1 = s0.set(fatal_id: state.fatal_id, ids: state.ids)
|
27
|
+
state.combine(s1.fmap { { key => _1 } })
|
31
28
|
end
|
32
29
|
|
33
30
|
def init
|
data/lib/remap/mapper/and.rb
CHANGED
data/lib/remap/mapper/binary.rb
CHANGED
@@ -4,10 +4,15 @@ module Remap
|
|
4
4
|
class Mapper
|
5
5
|
# @abstract
|
6
6
|
class Binary < self
|
7
|
-
include
|
7
|
+
include API
|
8
8
|
|
9
9
|
attribute :left, Types::Mapper
|
10
10
|
attribute :right, Types::Mapper
|
11
|
+
|
12
|
+
# @return [Bool]
|
13
|
+
def validate?
|
14
|
+
left.validate? && right.validate?
|
15
|
+
end
|
11
16
|
end
|
12
17
|
end
|
13
18
|
end
|
data/lib/remap/mapper/or.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
using State::Extension
|
6
|
+
|
7
|
+
module API
|
8
|
+
# @return [Boolean]
|
9
|
+
#
|
10
|
+
# @abstract
|
11
|
+
# @private
|
12
|
+
def validate?
|
13
|
+
raise NotImplementedError, "`validate?` is not implemented for #{self}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param input [Any]
|
17
|
+
# @param backtrace [Array<String>]
|
18
|
+
# @param options [Hash]
|
19
|
+
#
|
20
|
+
# @yieldparam [Failure]
|
21
|
+
# @yieldreturn [T]
|
22
|
+
#
|
23
|
+
# @return [Any, T]
|
24
|
+
def call(input, backtrace: caller, **options, &error)
|
25
|
+
unless block_given?
|
26
|
+
return call(input, **options) do |failure|
|
27
|
+
raise failure.exception(backtrace)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
s0 = State.call(input, options: options, mapper: self)._
|
32
|
+
|
33
|
+
s1 = call!(s0) do |failure|
|
34
|
+
return error[failure]
|
35
|
+
end
|
36
|
+
|
37
|
+
case s1
|
38
|
+
in { value: value }
|
39
|
+
value
|
40
|
+
in { notices: [] }
|
41
|
+
error[s1.failure("No data could be mapped")]
|
42
|
+
in { notices: }
|
43
|
+
error[Failure.new(failures: notices)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/remap/mapper/xor.rb
CHANGED
data/lib/remap/notice/error.rb
CHANGED
@@ -11,27 +11,6 @@ module Remap
|
|
11
11
|
delegate_missing_to :notice
|
12
12
|
delegate :inspect, :to_s, to: :notice
|
13
13
|
option :notice, type: Types.Instance(Notice)
|
14
|
-
|
15
|
-
def inspect
|
16
|
-
"#<%s %s>" % [self.class, to_hash.formatted]
|
17
|
-
end
|
18
|
-
|
19
|
-
def undefined(state)
|
20
|
-
state.set(notice: notice).except(:value)
|
21
|
-
end
|
22
|
-
|
23
|
-
def failure(state)
|
24
|
-
Failure.new(failures: [notice], notices: state.fetch(:notices))
|
25
|
-
end
|
26
|
-
|
27
|
-
def traced(backtrace)
|
28
|
-
e = Traced.new(notice: notice)
|
29
|
-
e.set_backtrace(backtrace)
|
30
|
-
e
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class Traced < Error
|
35
14
|
end
|
36
15
|
end
|
37
16
|
end
|
data/lib/remap/notice.rb
CHANGED
@@ -20,19 +20,5 @@ module Remap
|
|
20
20
|
def to_hash
|
21
21
|
super.except(:backtrace).compact_blank
|
22
22
|
end
|
23
|
-
|
24
|
-
# Used by State to skip mapping rules
|
25
|
-
#
|
26
|
-
# @raise [Notice::Ignore]
|
27
|
-
def ignore!
|
28
|
-
raise Ignore.new(notice: self)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Used by the state to halt mappers
|
32
|
-
#
|
33
|
-
# @raise [Notice::Fatal]
|
34
|
-
def fatal!
|
35
|
-
raise Fatal.new(notice: self)
|
36
|
-
end
|
37
23
|
end
|
38
24
|
end
|
data/lib/remap/rule/block.rb
CHANGED
@@ -6,6 +6,7 @@ module Remap
|
|
6
6
|
|
7
7
|
class Block < Unit
|
8
8
|
# @return [Array<Rule>]
|
9
|
+
attribute :backtrace, [String], min_size: 1
|
9
10
|
attribute :rules, [Types::Rule]
|
10
11
|
|
11
12
|
# Represents a non-empty define block with one or more rules
|
@@ -14,24 +15,30 @@ module Remap
|
|
14
15
|
# @param state [State]
|
15
16
|
#
|
16
17
|
# @return [State]
|
17
|
-
def call(state
|
18
|
-
|
19
|
-
raise ArgumentError, "Block#call(state, &error) requires a block"
|
20
|
-
end
|
18
|
+
def call(state)
|
19
|
+
s0 = state.except(:value)
|
21
20
|
|
22
21
|
if rules.empty?
|
23
|
-
return
|
22
|
+
return s0
|
24
23
|
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
failure = catch_fatal do |fatal_id|
|
26
|
+
s1 = s0.set(fatal_id: fatal_id)
|
27
|
+
s4 = state.set(fatal_id: fatal_id)
|
28
|
+
|
29
|
+
return catch_ignored do |id|
|
30
|
+
s2 = s1.set(id: id)
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
rules.reduce(s2) do |s3, rule|
|
33
|
+
s5 = s3
|
34
|
+
s6 = rule.call(s4)
|
35
|
+
s7 = s6.set(id: id)
|
36
|
+
s5.combine(s7)
|
37
|
+
end
|
38
|
+
end.remove_id.remove_fatal_id
|
34
39
|
end
|
40
|
+
|
41
|
+
raise failure.exception(backtrace)
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
@@ -30,14 +30,8 @@ module Remap
|
|
30
30
|
# @param state [State]
|
31
31
|
#
|
32
32
|
# @return [State]
|
33
|
-
def call(state
|
34
|
-
|
35
|
-
raise ArgumentError, "map.call(state, &error) requires a block"
|
36
|
-
end
|
37
|
-
|
38
|
-
super
|
39
|
-
rescue Notice::Ignore => e
|
40
|
-
e.undefined(state)
|
33
|
+
def call(state)
|
34
|
+
catch { super(state.set(id: _1)).except(:id) }
|
41
35
|
end
|
42
36
|
end
|
43
37
|
end
|
@@ -8,40 +8,7 @@ module Remap
|
|
8
8
|
class Required < Concrete
|
9
9
|
attribute :backtrace, Types::Backtrace
|
10
10
|
|
11
|
-
#
|
12
|
-
# When it fails, the entire mapping is marked as failed
|
13
|
-
#
|
14
|
-
# @example Map [:name] to [:nickname]
|
15
|
-
# map = Map::Required.call({
|
16
|
-
# backtrace: caller,
|
17
|
-
# path: {
|
18
|
-
# input: [:name],
|
19
|
-
# output: [:nickname]
|
20
|
-
# }
|
21
|
-
# })
|
22
|
-
#
|
23
|
-
# state = Remap::State.call({
|
24
|
-
# name: "John"
|
25
|
-
# })
|
26
|
-
#
|
27
|
-
# output = map.call(state) do |failure|
|
28
|
-
# # ...
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# output.fetch(:value) # => { nickname: "John" }
|
32
|
-
#
|
33
|
-
# @param state [State]
|
34
|
-
#
|
35
|
-
# @return [State]
|
36
|
-
def call(state, &error)
|
37
|
-
unless block_given?
|
38
|
-
raise ArgumentError, "Required.call(state, &error) requires a block"
|
39
|
-
end
|
40
|
-
|
41
|
-
super
|
42
|
-
rescue Notice::Ignore => e
|
43
|
-
error[e.failure(state)]
|
44
|
-
end
|
11
|
+
# TODO: Remove
|
45
12
|
end
|
46
13
|
end
|
47
14
|
end
|
data/lib/remap/rule/map.rb
CHANGED
@@ -42,29 +42,28 @@ module Remap
|
|
42
42
|
# @return [State]
|
43
43
|
#
|
44
44
|
# @abstract
|
45
|
-
def call(state
|
46
|
-
|
47
|
-
|
48
|
-
end
|
45
|
+
def call(state)
|
46
|
+
failure = catch_fatal do |fatal_id|
|
47
|
+
s0 = state.set(fatal_id: fatal_id)
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
s2 = path.input.call(s0) do |s1|
|
50
|
+
s2 = rule.call(s1)
|
51
|
+
callback(s2)
|
53
52
|
end
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
s3 = s2.then(&path.output)
|
55
|
+
s4 = s3.set(path: state.path)
|
56
|
+
s5 = s4.except(:key)
|
57
|
+
|
58
|
+
return s5.remove_fatal_id
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
rescue Notice::Fatal => e
|
62
|
-
raise e.traced(backtrace)
|
61
|
+
raise failure.exception(backtrace)
|
63
62
|
end
|
64
63
|
|
65
64
|
# A post-processor method
|
66
65
|
#
|
67
|
-
# @example
|
66
|
+
# @example Up-case mapped value
|
68
67
|
# state = Remap::State.call("Hello World")
|
69
68
|
# map = Remap::Rule::Map.call({})
|
70
69
|
# upcase = map.adjust(&:upcase)
|
@@ -198,16 +197,8 @@ module Remap
|
|
198
197
|
end
|
199
198
|
|
200
199
|
# @return [Proc]
|
201
|
-
def callback(state
|
202
|
-
|
203
|
-
raise ArgumentError, "Map#callback(state, &error) requires error handler block"
|
204
|
-
end
|
205
|
-
|
206
|
-
fn.reduce(state) do |inner, fn|
|
207
|
-
fn[inner] do |failure|
|
208
|
-
return error[failure]
|
209
|
-
end
|
210
|
-
end
|
200
|
+
def callback(state)
|
201
|
+
fn.reduce(state) { |s1, f1| f1[s1] }
|
211
202
|
end
|
212
203
|
end
|
213
204
|
end
|
data/lib/remap/rule/void.rb
CHANGED
data/lib/remap/rule.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Rule < Dry::Interface
|
5
|
+
using Extensions::Hash
|
6
|
+
using Extensions::Array
|
7
|
+
using Extensions::Object
|
8
|
+
include Catchable
|
5
9
|
defines :requirement
|
6
10
|
requirement Types::Any
|
7
11
|
|
@@ -13,5 +17,16 @@ module Remap
|
|
13
17
|
def call(state)
|
14
18
|
raise NotImplementedError, "#{self.class}#call not implemented"
|
15
19
|
end
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
def inspect
|
23
|
+
"#<#{self.class} #{to_hash.formatted}>"
|
24
|
+
end
|
25
|
+
alias to_s inspect
|
26
|
+
|
27
|
+
# @return [Hash]
|
28
|
+
def to_hash
|
29
|
+
attributes.transform_values(&:to_hash).except(:backtrace)
|
30
|
+
end
|
16
31
|
end
|
17
32
|
end
|
data/lib/remap/selector/index.rb
CHANGED
data/lib/remap/selector/key.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
module State
|
5
|
+
# @api private
|
5
6
|
module Extension
|
6
7
|
using Extensions::Enumerable
|
7
8
|
using Extensions::Object
|
@@ -54,7 +55,11 @@ module Remap
|
|
54
55
|
|
55
56
|
# Throws :fatal containing a Notice
|
56
57
|
def fatal!(...)
|
57
|
-
|
58
|
+
fatal_id = fetch(:fatal_id) do
|
59
|
+
raise ArgumentError, "Missing :fatal_id in %s" % formatted
|
60
|
+
end
|
61
|
+
|
62
|
+
throw fatal_id, Failure.new(failures: [notice(...)], notices: notices)
|
58
63
|
end
|
59
64
|
|
60
65
|
# Throws :warn containing a Notice
|
@@ -64,7 +69,7 @@ module Remap
|
|
64
69
|
|
65
70
|
# Throws :ignore containing a Notice
|
66
71
|
def ignore!(...)
|
67
|
-
notice(...).
|
72
|
+
set(notice: notice(...)).except(:value).return!
|
68
73
|
end
|
69
74
|
|
70
75
|
# Creates a notice containing the given message
|
@@ -87,8 +92,12 @@ module Remap
|
|
87
92
|
#
|
88
93
|
# @return [self]
|
89
94
|
def _(&block)
|
95
|
+
return self unless mapper.validate?
|
96
|
+
|
90
97
|
unless block
|
91
|
-
return _
|
98
|
+
return _ do |reason|
|
99
|
+
raise ArgumentError, "[BUG] State: #{formatted} reason: #{reason.formatted}"
|
100
|
+
end
|
92
101
|
end
|
93
102
|
|
94
103
|
unless (result = Schema.call(self)).success?
|
@@ -98,11 +107,11 @@ module Remap
|
|
98
107
|
self
|
99
108
|
end
|
100
109
|
|
101
|
-
#
|
110
|
+
# Iterates over {#value}
|
102
111
|
#
|
103
112
|
# @yieldparam value [Any]
|
104
|
-
# @
|
105
|
-
# @
|
113
|
+
# @yieldparam key [Symbol]
|
114
|
+
# @yieldparam index [Integer]
|
106
115
|
#
|
107
116
|
# @yieldreturn [State]
|
108
117
|
#
|
@@ -131,7 +140,7 @@ module Remap
|
|
131
140
|
in [:value, Array => list1, Array => list2]
|
132
141
|
list1 + list2
|
133
142
|
in [:value, left, right]
|
134
|
-
fatal!(
|
143
|
+
other.fatal!(
|
135
144
|
"Could not merge [%s] (%s) with [%s] (%s)",
|
136
145
|
left.formatted,
|
137
146
|
left.class,
|
@@ -140,10 +149,47 @@ module Remap
|
|
140
149
|
)
|
141
150
|
in [:notices, Array => n1, Array => n2]
|
142
151
|
n1 + n2
|
152
|
+
in [:ids, i1, i2] if i1.all? { i2.include?(_1) }
|
153
|
+
i2
|
154
|
+
in [:ids, i1, i2] if i2.all? { i1.include?(_1) }
|
155
|
+
i1
|
156
|
+
in [:ids, i1, i2]
|
157
|
+
other.fatal!("Could not merge #ids [%s] (%s) with [%s] (%s)", i1, i1.class, i2,
|
158
|
+
i2.class)
|
143
159
|
in [Symbol, _, value]
|
144
160
|
value
|
145
161
|
end
|
146
|
-
end
|
162
|
+
end._
|
163
|
+
end
|
164
|
+
|
165
|
+
# @todo Merge with {#remove_fatal_id}
|
166
|
+
# @return [State]
|
167
|
+
def remove_id
|
168
|
+
case self
|
169
|
+
in { ids: [], id: }
|
170
|
+
except(:id)
|
171
|
+
in { ids:, id: }
|
172
|
+
merge(ids: ids[1...], id: ids[0])
|
173
|
+
in { ids: [] }
|
174
|
+
self
|
175
|
+
in { ids: }
|
176
|
+
raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
|
177
|
+
end._
|
178
|
+
end
|
179
|
+
|
180
|
+
# @todo Merge with {#remove_id}
|
181
|
+
# @return [State]
|
182
|
+
def remove_fatal_id
|
183
|
+
case self
|
184
|
+
in { fatal_ids: [], fatal_id: }
|
185
|
+
except(:fatal_id)
|
186
|
+
in { fatal_ids: ids, fatal_id: id }
|
187
|
+
merge(fatal_ids: ids[1...], fatal_id: ids[0])
|
188
|
+
in { fatal_ids: [] }
|
189
|
+
self
|
190
|
+
in { fatal_ids: }
|
191
|
+
raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
|
192
|
+
end._
|
147
193
|
end
|
148
194
|
|
149
195
|
# Creates a new state with params
|
@@ -168,6 +214,10 @@ module Remap
|
|
168
214
|
merge(path: path + [index], element: value, index: index, value: value).set(**rest)
|
169
215
|
in [{path:}, {index:, **rest}]
|
170
216
|
merge(path: path + [index], index: index).set(**rest)
|
217
|
+
in [{ids:, id: old_id}, {id: new_id, **rest}]
|
218
|
+
merge(ids: [old_id] + ids, id: new_id).set(**rest)
|
219
|
+
in [{fatal_ids:, fatal_id: old_id}, {fatal_id: new_id, **rest}]
|
220
|
+
merge(fatal_ids: [old_id] + fatal_ids, fatal_id: new_id).set(**rest)
|
171
221
|
else
|
172
222
|
merge(options)
|
173
223
|
end
|
@@ -185,8 +235,8 @@ module Remap
|
|
185
235
|
#
|
186
236
|
# @return [State<Y>]
|
187
237
|
def fmap(**options, &block)
|
188
|
-
bind(**options) do |input, state
|
189
|
-
state.set(block[input, state,
|
238
|
+
bind(**options) do |input, state:|
|
239
|
+
state.set(block[input, state, state: state])
|
190
240
|
end
|
191
241
|
end
|
192
242
|
|
@@ -203,28 +253,7 @@ module Remap
|
|
203
253
|
# end
|
204
254
|
|
205
255
|
def failure(reason = Undefined)
|
206
|
-
|
207
|
-
in [_, Notice => notice]
|
208
|
-
[notice]
|
209
|
-
in [path, Array => reasons]
|
210
|
-
reasons.map do |inner_reason|
|
211
|
-
Notice.call(path: path, reason: inner_reason, **only(:value))
|
212
|
-
end
|
213
|
-
in [path, String => reason]
|
214
|
-
[Notice.call(path: path, reason: reason, **only(:value))]
|
215
|
-
in [path, Hash => errors]
|
216
|
-
errors.paths.flat_map do |sufix|
|
217
|
-
Array.wrap(errors.dig(*sufix)).map do |inner_reason|
|
218
|
-
Notice.call(
|
219
|
-
reason: inner_reason,
|
220
|
-
path: path + sufix,
|
221
|
-
**only(:value)
|
222
|
-
)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
Failure.new(failures: failures, notices: notices)
|
256
|
+
raise NotImplementedError, "Not implemented"
|
228
257
|
end
|
229
258
|
|
230
259
|
# Passes {#value} to block, if defined
|
@@ -242,12 +271,10 @@ module Remap
|
|
242
271
|
raise ArgumentError, "State#bind requires a block"
|
243
272
|
end
|
244
273
|
|
245
|
-
|
274
|
+
s1 = set(**options)
|
246
275
|
|
247
|
-
fetch(:value) { return
|
248
|
-
block[value,
|
249
|
-
return s.set(**other).ignore!(reason)
|
250
|
-
end
|
276
|
+
fetch(:value) { return s1 }.then do |value|
|
277
|
+
block[value, s1, state: s1]
|
251
278
|
end
|
252
279
|
end
|
253
280
|
|
@@ -259,11 +286,11 @@ module Remap
|
|
259
286
|
#
|
260
287
|
# @return [State<U>]
|
261
288
|
def execute(&block)
|
262
|
-
bind do |value
|
263
|
-
result = context(value
|
289
|
+
bind do |value|
|
290
|
+
result = context(value).instance_exec(value, &block)
|
264
291
|
|
265
292
|
if result.equal?(Dry::Core::Constants::Undefined)
|
266
|
-
|
293
|
+
ignore!("Undefined returned, skipping!")
|
267
294
|
end
|
268
295
|
|
269
296
|
set(result)
|
@@ -290,6 +317,26 @@ module Remap
|
|
290
317
|
fetch(:path, EMPTY_ARRAY)
|
291
318
|
end
|
292
319
|
|
320
|
+
# @return [Symbol]
|
321
|
+
def id
|
322
|
+
fetch(:id)
|
323
|
+
end
|
324
|
+
|
325
|
+
# @return [Mapper::API]
|
326
|
+
def mapper
|
327
|
+
fetch(:mapper)
|
328
|
+
end
|
329
|
+
|
330
|
+
# @return [Array<Symbol>]
|
331
|
+
def ids
|
332
|
+
fetch(:ids)
|
333
|
+
end
|
334
|
+
|
335
|
+
# @return [Symbol]
|
336
|
+
def fatal_id
|
337
|
+
fetch(:fatal_id)
|
338
|
+
end
|
339
|
+
|
293
340
|
# Represents options to a mapper
|
294
341
|
#
|
295
342
|
# @see Rule::Embed
|
@@ -303,7 +350,7 @@ module Remap
|
|
303
350
|
#
|
304
351
|
# @return [Hash]
|
305
352
|
def to_hash
|
306
|
-
super.except(:options, :notices, :value)
|
353
|
+
super.except(:options, :notices, :value, :id, :ids, :fatal_id, :fatal_ids)
|
307
354
|
end
|
308
355
|
|
309
356
|
# @return [Any]
|
@@ -331,6 +378,48 @@ module Remap
|
|
331
378
|
fetch(:notices)
|
332
379
|
end
|
333
380
|
|
381
|
+
# Creates a failure from the current state
|
382
|
+
#
|
383
|
+
# @param reason [String, Hash, Undefined]
|
384
|
+
#
|
385
|
+
# @return [Failure]
|
386
|
+
def failure(reason = Undefined)
|
387
|
+
failures = case [path, reason]
|
388
|
+
in [_, Notice => notice]
|
389
|
+
[notice]
|
390
|
+
in [path, Array => reasons]
|
391
|
+
reasons.map do |inner_reason|
|
392
|
+
Notice.call(path: path, reason: inner_reason, **only(:value))
|
393
|
+
end
|
394
|
+
in [path, String => reason]
|
395
|
+
[Notice.call(path: path, reason: reason, **only(:value))]
|
396
|
+
in [path, Hash => errors]
|
397
|
+
errors.paths.flat_map do |sufix|
|
398
|
+
Array.wrap(errors.dig(*sufix)).map do |inner_reason|
|
399
|
+
Notice.call(
|
400
|
+
reason: inner_reason,
|
401
|
+
path: path + sufix,
|
402
|
+
**only(:value)
|
403
|
+
)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
Failure.new(failures: failures, notices: notices)
|
409
|
+
end
|
410
|
+
|
411
|
+
# @raise [ArgumentError]
|
412
|
+
# when {#id} is not defined
|
413
|
+
#
|
414
|
+
# @private
|
415
|
+
def return!
|
416
|
+
id = fetch(:id) do
|
417
|
+
raise ArgumentError, "#id not defined for state [%s]" % [formatted]
|
418
|
+
end
|
419
|
+
|
420
|
+
throw id, remove_id
|
421
|
+
end
|
422
|
+
|
334
423
|
private
|
335
424
|
|
336
425
|
# Creates a context containing {options} and {self}
|
@@ -340,13 +429,13 @@ module Remap
|
|
340
429
|
# @yieldparam reason [T]
|
341
430
|
#
|
342
431
|
# @return [Struct]
|
343
|
-
def context(value, context: self
|
344
|
-
::Struct.new(*keys, *options.keys, :state, keyword_init: true) do
|
432
|
+
def context(value, context: self)
|
433
|
+
::Struct.new(*except(:id).keys, *options.keys, :state, keyword_init: true) do
|
345
434
|
define_method :method_missing do |name, *|
|
346
|
-
|
435
|
+
context.fatal!("Method [%s] not defined", name)
|
347
436
|
end
|
348
437
|
|
349
|
-
define_method
|
438
|
+
define_method(:skip!) do |message = "Manual skip!"|
|
350
439
|
context.ignore!(message)
|
351
440
|
end
|
352
441
|
end.new(**to_hash, **options, value: value, state: self)
|
data/lib/remap/state/schema.rb
CHANGED
@@ -9,6 +9,8 @@ module Remap
|
|
9
9
|
required(:notices).array(Types.Instance(Notice))
|
10
10
|
required(:options).value(:hash)
|
11
11
|
required(:path).array(Types::Key)
|
12
|
+
required(:ids).value(:array)
|
13
|
+
required(:fatal_ids).value(:array)
|
12
14
|
|
13
15
|
optional(:index).filled(:integer)
|
14
16
|
optional(:element).filled
|
data/lib/remap/state.rb
CHANGED
@@ -31,9 +31,11 @@ module Remap
|
|
31
31
|
# @return [Hash] A valid state
|
32
32
|
def self.call(value, mapper: Dummy, options: EMPTY_HASH)
|
33
33
|
{
|
34
|
+
fatal_ids: EMPTY_ARRAY,
|
34
35
|
notices: EMPTY_ARRAY,
|
35
36
|
path: EMPTY_ARRAY,
|
36
37
|
options: options,
|
38
|
+
ids: EMPTY_ARRAY,
|
37
39
|
mapper: mapper,
|
38
40
|
values: value,
|
39
41
|
value: value,
|
data/lib/remap.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: remap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.44
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Linus Oleander
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -248,18 +248,22 @@ extra_rdoc_files: []
|
|
248
248
|
files:
|
249
249
|
- lib/remap.rb
|
250
250
|
- lib/remap/base.rb
|
251
|
+
- lib/remap/catchable.rb
|
251
252
|
- lib/remap/class_interface.rb
|
252
253
|
- lib/remap/compiler.rb
|
254
|
+
- lib/remap/config.rb
|
253
255
|
- lib/remap/constructor.rb
|
254
256
|
- lib/remap/constructor/argument.rb
|
255
257
|
- lib/remap/constructor/keyword.rb
|
256
258
|
- lib/remap/constructor/none.rb
|
257
259
|
- lib/remap/contract.rb
|
258
260
|
- lib/remap/error.rb
|
261
|
+
- lib/remap/extensions/array.rb
|
259
262
|
- lib/remap/extensions/enumerable.rb
|
260
263
|
- lib/remap/extensions/hash.rb
|
261
264
|
- lib/remap/extensions/object.rb
|
262
265
|
- lib/remap/failure.rb
|
266
|
+
- lib/remap/failure/error.rb
|
263
267
|
- lib/remap/iteration.rb
|
264
268
|
- lib/remap/iteration/array.rb
|
265
269
|
- lib/remap/iteration/hash.rb
|
@@ -268,6 +272,7 @@ files:
|
|
268
272
|
- lib/remap/mapper/and.rb
|
269
273
|
- lib/remap/mapper/binary.rb
|
270
274
|
- lib/remap/mapper/or.rb
|
275
|
+
- lib/remap/mapper/support/api.rb
|
271
276
|
- lib/remap/mapper/support/operations.rb
|
272
277
|
- lib/remap/mapper/xor.rb
|
273
278
|
- lib/remap/nothing.rb
|
@@ -275,7 +280,6 @@ files:
|
|
275
280
|
- lib/remap/notice/error.rb
|
276
281
|
- lib/remap/notice/fatal.rb
|
277
282
|
- lib/remap/notice/ignore.rb
|
278
|
-
- lib/remap/operation.rb
|
279
283
|
- lib/remap/path.rb
|
280
284
|
- lib/remap/path/input.rb
|
281
285
|
- lib/remap/path/output.rb
|
data/lib/remap/operation.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Remap
|
4
|
-
using State::Extension
|
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
|
16
|
-
def call(input, **options, &error)
|
17
|
-
unless error
|
18
|
-
return call(input, **options) do |failure|
|
19
|
-
raise failure.exception
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
other = State.call(input, options: options, mapper: self).then do |state|
|
24
|
-
call!(state) do |failure|
|
25
|
-
return error[failure]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
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)]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|