remap 2.2.38 → 2.2.42
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 +21 -15
- data/lib/remap/catchable.rb +27 -0
- data/lib/remap/compiler.rb +45 -18
- data/lib/remap/constructor/keyword.rb +2 -9
- data/lib/remap/extensions/array.rb +14 -0
- 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/binary.rb +1 -1
- data/lib/remap/mapper/support/api.rb +32 -0
- data/lib/remap/rule/block.rb +19 -12
- data/lib/remap/rule/map/optional.rb +2 -19
- data/lib/remap/rule/map/required.rb +1 -34
- data/lib/remap/rule/map.rb +16 -49
- data/lib/remap/rule/void.rb +1 -3
- data/lib/remap/rule.rb +15 -0
- data/lib/remap/selector/index.rb +2 -2
- data/lib/remap/selector/key.rb +1 -1
- data/lib/remap/state/extension.rb +127 -45
- data/lib/remap/state/schema.rb +2 -0
- data/lib/remap/state.rb +2 -0
- data/lib/remap/static/option.rb +5 -5
- data/lib/remap.rb +1 -0
- metadata +6 -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: 791eca1356feec6a254c824130b798026851d4f0e56207b32144ba36f64d06a5
|
4
|
+
data.tar.gz: f7908a2f3dd0d4847860e342d4f11fa298ba5f9689bf8293d585122993ba40cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7eaed51a433437e884ea6af6485ad81752bfb5c7c388e678794c4e17447311b23aaa32d529451fbb70080617b9654f227fa7ac810d3dbff8e7c9c3e403f8de52
|
7
|
+
data.tar.gz: 3e14bdd50fa6e3ff3ad77589b969b294161a5878c007b9adba387614c3bcd86300e89ae72d9ea0252739269900757747294b7b89b8e0d2b6ad0ae162d363bd47
|
data/lib/remap/base.rb
CHANGED
@@ -106,8 +106,11 @@ module Remap
|
|
106
106
|
class Base < Mapper
|
107
107
|
include ActiveSupport::Configurable
|
108
108
|
include Dry::Core::Constants
|
109
|
+
include Catchable
|
110
|
+
|
111
|
+
extend Mapper::API
|
112
|
+
|
109
113
|
using State::Extension
|
110
|
-
extend Operation
|
111
114
|
|
112
115
|
with_options instance_accessor: true do |scope|
|
113
116
|
scope.config_accessor(:contract) { Dry::Schema.define {} }
|
@@ -235,26 +238,31 @@ module Remap
|
|
235
238
|
# Mapper.call({name: "John"}).first_name # => "John"
|
236
239
|
#
|
237
240
|
# @return [void]
|
238
|
-
|
241
|
+
# rubocop:disable Layout/LineLength
|
242
|
+
def self.define(target = Nothing, method: :new, strategy: :argument, backtrace: caller, &context)
|
239
243
|
unless block_given?
|
240
244
|
raise ArgumentError, "#{self}.define requires a block"
|
241
245
|
end
|
242
246
|
|
243
247
|
self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
|
244
|
-
self.context = Compiler.call(&context)
|
248
|
+
self.context = Compiler.call(backtrace: backtrace, &context)
|
245
249
|
end
|
250
|
+
# rubocop:enable Layout/LineLength
|
246
251
|
|
247
|
-
# Similar to {::call}, but takes a state
|
248
|
-
#
|
249
252
|
# @param state [State]
|
250
253
|
#
|
251
|
-
# @yield [Failure]
|
254
|
+
# @yield [Failure]
|
255
|
+
# when a non-critical error occurs
|
256
|
+
# @yieldreturn T
|
252
257
|
#
|
253
|
-
# @return [
|
258
|
+
# @return [State, T]
|
259
|
+
# when request is a success
|
260
|
+
# @raise [Remap::Error]
|
261
|
+
# when a fatal error occurs
|
254
262
|
#
|
255
263
|
# @private
|
256
264
|
def self.call!(state, &error)
|
257
|
-
new(state.options).call(state
|
265
|
+
new(state.options).call(state, &error)
|
258
266
|
end
|
259
267
|
|
260
268
|
# Mappers state according to the mapper rules
|
@@ -267,8 +275,8 @@ module Remap
|
|
267
275
|
#
|
268
276
|
# @private
|
269
277
|
def call(state, &error)
|
270
|
-
|
271
|
-
raise ArgumentError, "
|
278
|
+
state._ do |reason|
|
279
|
+
raise ArgumentError, "Invalid state due to #{reason.formatted}"
|
272
280
|
end
|
273
281
|
|
274
282
|
state.tap do |input|
|
@@ -279,13 +287,11 @@ module Remap
|
|
279
287
|
end
|
280
288
|
end
|
281
289
|
|
282
|
-
|
283
|
-
return context.call(state)
|
284
|
-
return error[failure]
|
285
|
-
end.then(&constructor)
|
290
|
+
s1 = catch_ignored do |id|
|
291
|
+
return context.call(state.set(id: id)).then(&constructor).remove_id
|
286
292
|
end
|
287
293
|
|
288
|
-
|
294
|
+
Failure.new(failures: s1.notices).then(&error)
|
289
295
|
end
|
290
296
|
|
291
297
|
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]
|
@@ -176,18 +178,20 @@ module Remap
|
|
176
178
|
# output.fetch(:value) # => { car: "Volvo" }
|
177
179
|
#
|
178
180
|
# @return [Rule::Embed]
|
179
|
-
def embed(mapper)
|
181
|
+
def embed(mapper, backtrace: caller)
|
180
182
|
if block_given?
|
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
|
@@ -225,12 +229,16 @@ module Remap
|
|
225
229
|
# @raise [ArgumentError]
|
226
230
|
# if no path given
|
227
231
|
# if path is not a Symbol or Array<Symbol>
|
228
|
-
def set(*path, to:)
|
232
|
+
def set(*path, to:, backtrace: caller)
|
229
233
|
if block_given?
|
230
234
|
raise ArgumentError, "#set does not take a block"
|
231
235
|
end
|
232
236
|
|
233
|
-
|
237
|
+
unless to.is_a?(Static)
|
238
|
+
raise ArgumentError, "Argument to #set must be a static value, got #{to.class}"
|
239
|
+
end
|
240
|
+
|
241
|
+
add rule(to: path, backtrace: backtrace).add { to.call(_1) }
|
234
242
|
end
|
235
243
|
|
236
244
|
# Maps to path from map with block in between
|
@@ -280,8 +288,8 @@ module Remap
|
|
280
288
|
# @see #to
|
281
289
|
#
|
282
290
|
# @return [Rule::Map::Optional]
|
283
|
-
def to?(*path, map: EMPTY_ARRAY, &block)
|
284
|
-
add rule?(*map, to: path, &block)
|
291
|
+
def to?(*path, map: EMPTY_ARRAY, backtrace: caller, &block)
|
292
|
+
add rule?(*map, to: path, backtrace: backtrace, &block)
|
285
293
|
end
|
286
294
|
|
287
295
|
# Iterates over the input value, passes each value
|
@@ -341,12 +349,16 @@ module Remap
|
|
341
349
|
#
|
342
350
|
# @return [Rule::Wrap]
|
343
351
|
# @raise [ArgumentError] if type is not :array
|
344
|
-
def wrap(type, &block)
|
352
|
+
def wrap(type, backtrace: caller, &block)
|
345
353
|
unless block_given?
|
346
354
|
raise ArgumentError, "#wrap requires a block"
|
347
355
|
end
|
348
356
|
|
349
|
-
|
357
|
+
unless type == :array
|
358
|
+
raise ArgumentError, "Argument to #wrap must equal :array, got [#{type}] (#{type.class})"
|
359
|
+
end
|
360
|
+
|
361
|
+
add rule(backtrace: backtrace, &block).then { Array.wrap(_1) }
|
350
362
|
end
|
351
363
|
|
352
364
|
# Selects all elements
|
@@ -394,12 +406,12 @@ module Remap
|
|
394
406
|
# output.fetch(:value) # => { api_key: "<SECRET>" }
|
395
407
|
#
|
396
408
|
# @return [Rule::Static::Fixed]
|
397
|
-
def value(value)
|
409
|
+
def value(value, backtrace: caller)
|
398
410
|
if block_given?
|
399
411
|
raise ArgumentError, "option selector does not take a block"
|
400
412
|
end
|
401
413
|
|
402
|
-
Static::Fixed.new(value: value)
|
414
|
+
Static::Fixed.new(value: value, backtrace: backtrace)
|
403
415
|
end
|
404
416
|
|
405
417
|
# Static option to be selected
|
@@ -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
|
@@ -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/binary.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
using State::Extension
|
6
|
+
|
7
|
+
module API
|
8
|
+
def call(input, backtrace: caller, **options, &error)
|
9
|
+
unless block_given?
|
10
|
+
return call(input, **options) do |failure|
|
11
|
+
raise failure.exception(backtrace)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
s0 = State.call(input, options: options, mapper: self)._
|
16
|
+
|
17
|
+
s1 = call!(s0) do |failure|
|
18
|
+
return error[failure]
|
19
|
+
end
|
20
|
+
|
21
|
+
case s1
|
22
|
+
in { value: value }
|
23
|
+
value
|
24
|
+
in { notices: [] }
|
25
|
+
error[s1.failure("No data could be mapped")]
|
26
|
+
in { notices: }
|
27
|
+
error[Failure.new(failures: notices)]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
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,25 +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)
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
# Catches :ignore exceptions and re-package them as a state
|
46
|
-
#
|
47
|
-
# @param state [State]
|
48
|
-
#
|
49
|
-
# @return [State]
|
50
|
-
def ignore(state, &block)
|
51
|
-
state.set(notice: catch(:ignore, &block).traced(backtrace)).except(:value)
|
33
|
+
def call(state)
|
34
|
+
catch { super(state.set(id: _1)).except(:id) }
|
52
35
|
end
|
53
36
|
end
|
54
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,40 +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
|
211
|
-
end
|
212
|
-
|
213
|
-
# Catches :fatal and raises {Notice::Error}
|
214
|
-
#
|
215
|
-
# @param state [State]
|
216
|
-
# @param id (:fatal) [:fatal, :notice, :ignore]
|
217
|
-
#
|
218
|
-
# raise [Notice::Error]
|
219
|
-
def fatal(state, id: :fatal, &block)
|
220
|
-
raise catch(id, &block).traced(backtrace).exception
|
221
|
-
end
|
222
|
-
|
223
|
-
# Catches :notice exceptions and repackages them as a state
|
224
|
-
#
|
225
|
-
# @param state [State]
|
226
|
-
#
|
227
|
-
# @return [State]
|
228
|
-
def notice(state, &block)
|
229
|
-
state.set(notice: catch(:notice, &block).traced(backtrace)).except(:value)
|
230
|
-
end
|
231
|
-
|
232
|
-
# @abstract
|
233
|
-
def ignore(...)
|
234
|
-
raise NotImplementedError, "#{self.class}#ignore"
|
200
|
+
def callback(state)
|
201
|
+
fn.reduce(state) { |s1, f1| f1[s1] }
|
235
202
|
end
|
236
203
|
end
|
237
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
@@ -24,7 +24,7 @@ module Remap
|
|
24
24
|
|
25
25
|
# Selects the {#index}th element from state and passes it to block
|
26
26
|
#
|
27
|
-
# @param
|
27
|
+
# @param state [State<Array<T>>]
|
28
28
|
#
|
29
29
|
# @yieldparam [State<T>]
|
30
30
|
# @yieldreturn [State<U>]
|
@@ -41,7 +41,7 @@ module Remap
|
|
41
41
|
end
|
42
42
|
|
43
43
|
element = array.fetch(index) do
|
44
|
-
s.ignore!("Index
|
44
|
+
s.ignore!("Index not found")
|
45
45
|
end
|
46
46
|
|
47
47
|
state.set(element, index: index).then(&block)
|
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
|
@@ -88,7 +93,9 @@ module Remap
|
|
88
93
|
# @return [self]
|
89
94
|
def _(&block)
|
90
95
|
unless block
|
91
|
-
return _
|
96
|
+
return _ do |reason|
|
97
|
+
raise ArgumentError, "[BUG] State: #{formatted} reason: #{reason.formatted}"
|
98
|
+
end
|
92
99
|
end
|
93
100
|
|
94
101
|
unless (result = Schema.call(self)).success?
|
@@ -98,11 +105,11 @@ module Remap
|
|
98
105
|
self
|
99
106
|
end
|
100
107
|
|
101
|
-
#
|
108
|
+
# Iterates over {#value}
|
102
109
|
#
|
103
110
|
# @yieldparam value [Any]
|
104
|
-
# @
|
105
|
-
# @
|
111
|
+
# @yieldparam key [Symbol]
|
112
|
+
# @yieldparam index [Integer]
|
106
113
|
#
|
107
114
|
# @yieldreturn [State]
|
108
115
|
#
|
@@ -131,7 +138,7 @@ module Remap
|
|
131
138
|
in [:value, Array => list1, Array => list2]
|
132
139
|
list1 + list2
|
133
140
|
in [:value, left, right]
|
134
|
-
fatal!(
|
141
|
+
other.fatal!(
|
135
142
|
"Could not merge [%s] (%s) with [%s] (%s)",
|
136
143
|
left.formatted,
|
137
144
|
left.class,
|
@@ -140,10 +147,47 @@ module Remap
|
|
140
147
|
)
|
141
148
|
in [:notices, Array => n1, Array => n2]
|
142
149
|
n1 + n2
|
150
|
+
in [:ids, i1, i2] if i1.all? { i2.include?(_1) }
|
151
|
+
i2
|
152
|
+
in [:ids, i1, i2] if i2.all? { i1.include?(_1) }
|
153
|
+
i1
|
154
|
+
in [:ids, i1, i2]
|
155
|
+
other.fatal!("Could not merge #ids [%s] (%s) with [%s] (%s)", i1, i1.class, i2,
|
156
|
+
i2.class)
|
143
157
|
in [Symbol, _, value]
|
144
158
|
value
|
145
159
|
end
|
146
|
-
end
|
160
|
+
end._
|
161
|
+
end
|
162
|
+
|
163
|
+
# @todo Merge with {#remove_fatal_id}
|
164
|
+
# @return [State]
|
165
|
+
def remove_id
|
166
|
+
case self
|
167
|
+
in { ids: [], id: }
|
168
|
+
except(:id)
|
169
|
+
in { ids:, id: }
|
170
|
+
merge(ids: ids[1...], id: ids[0])
|
171
|
+
in { ids: [] }
|
172
|
+
self
|
173
|
+
in { ids: }
|
174
|
+
raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
|
175
|
+
end._
|
176
|
+
end
|
177
|
+
|
178
|
+
# @todo Merge with {#remove_id}
|
179
|
+
# @return [State]
|
180
|
+
def remove_fatal_id
|
181
|
+
case self
|
182
|
+
in { fatal_ids: [], fatal_id: }
|
183
|
+
except(:fatal_id)
|
184
|
+
in { fatal_ids: ids, fatal_id: id }
|
185
|
+
merge(fatal_ids: ids[1...], fatal_id: ids[0])
|
186
|
+
in { fatal_ids: [] }
|
187
|
+
self
|
188
|
+
in { fatal_ids: }
|
189
|
+
raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
|
190
|
+
end._
|
147
191
|
end
|
148
192
|
|
149
193
|
# Creates a new state with params
|
@@ -168,6 +212,10 @@ module Remap
|
|
168
212
|
merge(path: path + [index], element: value, index: index, value: value).set(**rest)
|
169
213
|
in [{path:}, {index:, **rest}]
|
170
214
|
merge(path: path + [index], index: index).set(**rest)
|
215
|
+
in [{ids:, id: old_id}, {id: new_id, **rest}]
|
216
|
+
merge(ids: [old_id] + ids, id: new_id).set(**rest)
|
217
|
+
in [{fatal_ids:, fatal_id: old_id}, {fatal_id: new_id, **rest}]
|
218
|
+
merge(fatal_ids: [old_id] + fatal_ids, fatal_id: new_id).set(**rest)
|
171
219
|
else
|
172
220
|
merge(options)
|
173
221
|
end
|
@@ -185,8 +233,8 @@ module Remap
|
|
185
233
|
#
|
186
234
|
# @return [State<Y>]
|
187
235
|
def fmap(**options, &block)
|
188
|
-
bind(**options) do |input, state
|
189
|
-
state.set(block[input, state,
|
236
|
+
bind(**options) do |input, state:|
|
237
|
+
state.set(block[input, state, state: state])
|
190
238
|
end
|
191
239
|
end
|
192
240
|
|
@@ -203,28 +251,7 @@ module Remap
|
|
203
251
|
# end
|
204
252
|
|
205
253
|
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)
|
254
|
+
raise NotImplementedError, "Not implemented"
|
228
255
|
end
|
229
256
|
|
230
257
|
# Passes {#value} to block, if defined
|
@@ -242,12 +269,10 @@ module Remap
|
|
242
269
|
raise ArgumentError, "State#bind requires a block"
|
243
270
|
end
|
244
271
|
|
245
|
-
|
272
|
+
s1 = set(**options)
|
246
273
|
|
247
|
-
fetch(:value) { return
|
248
|
-
block[value,
|
249
|
-
return s.set(**other).ignore!(reason)
|
250
|
-
end
|
274
|
+
fetch(:value) { return s1 }.then do |value|
|
275
|
+
block[value, s1, state: s1]
|
251
276
|
end
|
252
277
|
end
|
253
278
|
|
@@ -259,11 +284,11 @@ module Remap
|
|
259
284
|
#
|
260
285
|
# @return [State<U>]
|
261
286
|
def execute(&block)
|
262
|
-
bind do |value
|
263
|
-
result = context(value
|
287
|
+
bind do |value|
|
288
|
+
result = context(value).instance_exec(value, &block)
|
264
289
|
|
265
290
|
if result.equal?(Dry::Core::Constants::Undefined)
|
266
|
-
|
291
|
+
ignore!("Undefined returned, skipping!")
|
267
292
|
end
|
268
293
|
|
269
294
|
set(result)
|
@@ -290,6 +315,21 @@ module Remap
|
|
290
315
|
fetch(:path, EMPTY_ARRAY)
|
291
316
|
end
|
292
317
|
|
318
|
+
# @return [Symbol]
|
319
|
+
def id
|
320
|
+
fetch(:id)
|
321
|
+
end
|
322
|
+
|
323
|
+
# @return [Array<Symbol>]
|
324
|
+
def ids
|
325
|
+
fetch(:ids)
|
326
|
+
end
|
327
|
+
|
328
|
+
# @return [Symbol]
|
329
|
+
def fatal_id
|
330
|
+
fetch(:fatal_id)
|
331
|
+
end
|
332
|
+
|
293
333
|
# Represents options to a mapper
|
294
334
|
#
|
295
335
|
# @see Rule::Embed
|
@@ -303,7 +343,7 @@ module Remap
|
|
303
343
|
#
|
304
344
|
# @return [Hash]
|
305
345
|
def to_hash
|
306
|
-
super.except(:options, :notices, :value)
|
346
|
+
super.except(:options, :notices, :value, :id, :ids, :fatal_id, :fatal_ids)
|
307
347
|
end
|
308
348
|
|
309
349
|
# @return [Any]
|
@@ -331,6 +371,48 @@ module Remap
|
|
331
371
|
fetch(:notices)
|
332
372
|
end
|
333
373
|
|
374
|
+
# Creates a failure from the current state
|
375
|
+
#
|
376
|
+
# @param reason [String, Hash, Undefined]
|
377
|
+
#
|
378
|
+
# @return [Failure]
|
379
|
+
def failure(reason = Undefined)
|
380
|
+
failures = case [path, reason]
|
381
|
+
in [_, Notice => notice]
|
382
|
+
[notice]
|
383
|
+
in [path, Array => reasons]
|
384
|
+
reasons.map do |inner_reason|
|
385
|
+
Notice.call(path: path, reason: inner_reason, **only(:value))
|
386
|
+
end
|
387
|
+
in [path, String => reason]
|
388
|
+
[Notice.call(path: path, reason: reason, **only(:value))]
|
389
|
+
in [path, Hash => errors]
|
390
|
+
errors.paths.flat_map do |sufix|
|
391
|
+
Array.wrap(errors.dig(*sufix)).map do |inner_reason|
|
392
|
+
Notice.call(
|
393
|
+
reason: inner_reason,
|
394
|
+
path: path + sufix,
|
395
|
+
**only(:value)
|
396
|
+
)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
Failure.new(failures: failures, notices: notices)
|
402
|
+
end
|
403
|
+
|
404
|
+
# @raise [ArgumentError]
|
405
|
+
# when {#id} is not defined
|
406
|
+
#
|
407
|
+
# @private
|
408
|
+
def return!
|
409
|
+
id = fetch(:id) do
|
410
|
+
raise ArgumentError, "#id not defined for state [%s]" % [formatted]
|
411
|
+
end
|
412
|
+
|
413
|
+
throw id, remove_id
|
414
|
+
end
|
415
|
+
|
334
416
|
private
|
335
417
|
|
336
418
|
# Creates a context containing {options} and {self}
|
@@ -340,13 +422,13 @@ module Remap
|
|
340
422
|
# @yieldparam reason [T]
|
341
423
|
#
|
342
424
|
# @return [Struct]
|
343
|
-
def context(value, context: self
|
344
|
-
::Struct.new(*keys, *options.keys, :state, keyword_init: true) do
|
425
|
+
def context(value, context: self)
|
426
|
+
::Struct.new(*except(:id).keys, *options.keys, :state, keyword_init: true) do
|
345
427
|
define_method :method_missing do |name, *|
|
346
|
-
|
428
|
+
context.fatal!("Method [%s] not defined", name)
|
347
429
|
end
|
348
430
|
|
349
|
-
define_method
|
431
|
+
define_method(:skip!) do |message = "Manual skip!"|
|
350
432
|
context.ignore!(message)
|
351
433
|
end
|
352
434
|
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/static/option.rb
CHANGED
@@ -26,11 +26,11 @@ module Remap
|
|
26
26
|
#
|
27
27
|
# @return [State]
|
28
28
|
def call(state)
|
29
|
-
state.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
value = state.options.fetch(name) do
|
30
|
+
raise ArgumentError, "Option [%s] not found" % [name], backtrace
|
31
|
+
end
|
32
|
+
|
33
|
+
state.set(value)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
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.42
|
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-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -248,6 +248,7 @@ 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
|
253
254
|
- lib/remap/constructor.rb
|
@@ -256,10 +257,12 @@ files:
|
|
256
257
|
- lib/remap/constructor/none.rb
|
257
258
|
- lib/remap/contract.rb
|
258
259
|
- lib/remap/error.rb
|
260
|
+
- lib/remap/extensions/array.rb
|
259
261
|
- lib/remap/extensions/enumerable.rb
|
260
262
|
- lib/remap/extensions/hash.rb
|
261
263
|
- lib/remap/extensions/object.rb
|
262
264
|
- lib/remap/failure.rb
|
265
|
+
- lib/remap/failure/error.rb
|
263
266
|
- lib/remap/iteration.rb
|
264
267
|
- lib/remap/iteration/array.rb
|
265
268
|
- lib/remap/iteration/hash.rb
|
@@ -268,6 +271,7 @@ files:
|
|
268
271
|
- lib/remap/mapper/and.rb
|
269
272
|
- lib/remap/mapper/binary.rb
|
270
273
|
- lib/remap/mapper/or.rb
|
274
|
+
- lib/remap/mapper/support/api.rb
|
271
275
|
- lib/remap/mapper/support/operations.rb
|
272
276
|
- lib/remap/mapper/xor.rb
|
273
277
|
- lib/remap/nothing.rb
|
@@ -275,7 +279,6 @@ files:
|
|
275
279
|
- lib/remap/notice/error.rb
|
276
280
|
- lib/remap/notice/fatal.rb
|
277
281
|
- lib/remap/notice/ignore.rb
|
278
|
-
- lib/remap/operation.rb
|
279
282
|
- lib/remap/path.rb
|
280
283
|
- lib/remap/path/input.rb
|
281
284
|
- 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
|