remap 2.2.39 → 2.2.43
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 +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 +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/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/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 +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: 88fc0978315944ad791a089536840916db814b0fc7dad18cda8c19d1c9901d9c
|
4
|
+
data.tar.gz: 2b59e22a02ac486b04758c772082731b2e91cb0ba698e40e3502794236515ff5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41a5d60aa1fa66fb254162d1313611e441e239de54e59c4d7ebaa8a5334a6930317cb32703bd837e85212826b23286afa57e9d8662d8e9c78ab4fc3d569bcddd
|
7
|
+
data.tar.gz: 3c8b89c3a3cb5a4fae156a0757b2130afc95ced6911b2707e239e7a3dd791ed5036b46711fb4262b94006b027bc0e98f0882e5a2e4131a12c7730542699e7068
|
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/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
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.43
|
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
|