remap 2.2.37 → 2.2.41
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 +45 -15
- data/lib/remap/catchable.rb +27 -0
- data/lib/remap/compiler.rb +47 -20
- 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/notice/error.rb +6 -1
- data/lib/remap/operation.rb +20 -15
- data/lib/remap/rule/block.rb +21 -10
- 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 +128 -46
- data/lib/remap/state/schema.rb +2 -0
- data/lib/remap/state.rb +2 -0
- data/lib/remap/static/option.rb +5 -5
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a8ee7751985c548b272cb12bb0b08b655f52de3143075afafd8578f9e604eba
|
4
|
+
data.tar.gz: f333d2922e7d5ef7a6bd050eb2fc0261446097fdd17f2745eef1a3bc35eb96ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69b98a323c17562a06d4b83f3455c2b1c7e6a482280f5afe283f0d9570411391bb2d0de353d96250436d8aa51ad8443f1a3e25805dde0d6baebbc5286d019a9e
|
7
|
+
data.tar.gz: 86f03041d4cf3ef2ebd1c597269645ae83c55e5d3326077a9d55f51f3f0a47aea131523e9e314d635f34d60487dbba20350a28ffa3eca9b032f0aecd01bf006f
|
data/lib/remap/base.rb
CHANGED
@@ -106,8 +106,10 @@ module Remap
|
|
106
106
|
class Base < Mapper
|
107
107
|
include ActiveSupport::Configurable
|
108
108
|
include Dry::Core::Constants
|
109
|
+
|
110
|
+
include Catchable
|
111
|
+
|
109
112
|
using State::Extension
|
110
|
-
extend Operation
|
111
113
|
|
112
114
|
with_options instance_accessor: true do |scope|
|
113
115
|
scope.config_accessor(:contract) { Dry::Schema.define {} }
|
@@ -120,6 +122,31 @@ module Remap
|
|
120
122
|
|
121
123
|
schema schema.strict(false)
|
122
124
|
|
125
|
+
# extend Operation
|
126
|
+
|
127
|
+
def self.call(input, backtrace: caller, **options, &error)
|
128
|
+
unless block_given?
|
129
|
+
return call(input, **options) do |failure|
|
130
|
+
raise failure.exception(backtrace)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
s0 = State.call(input, options: options, mapper: self)._
|
135
|
+
|
136
|
+
s1 = call!(s0) do |failure|
|
137
|
+
return error[failure]
|
138
|
+
end
|
139
|
+
|
140
|
+
case s1
|
141
|
+
in { value: value }
|
142
|
+
value
|
143
|
+
in { notices: [] }
|
144
|
+
error[s1.failure("No data could be mapped")]
|
145
|
+
in { notices: }
|
146
|
+
error[Failure.new(failures: notices)]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
123
150
|
# Defines a schema for the mapper
|
124
151
|
# If the schema fail, the mapper will fail
|
125
152
|
#
|
@@ -235,26 +262,31 @@ module Remap
|
|
235
262
|
# Mapper.call({name: "John"}).first_name # => "John"
|
236
263
|
#
|
237
264
|
# @return [void]
|
238
|
-
|
265
|
+
# rubocop:disable Layout/LineLength
|
266
|
+
def self.define(target = Nothing, method: :new, strategy: :argument, backtrace: caller, &context)
|
239
267
|
unless block_given?
|
240
268
|
raise ArgumentError, "#{self}.define requires a block"
|
241
269
|
end
|
242
270
|
|
243
271
|
self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
|
244
|
-
self.context = Compiler.call(&context)
|
272
|
+
self.context = Compiler.call(backtrace: backtrace, &context)
|
245
273
|
end
|
274
|
+
# rubocop:enable Layout/LineLength
|
246
275
|
|
247
|
-
# Similar to {::call}, but takes a state
|
248
|
-
#
|
249
276
|
# @param state [State]
|
250
277
|
#
|
251
|
-
# @yield [Failure]
|
278
|
+
# @yield [Failure]
|
279
|
+
# when a non-critical error occurs
|
280
|
+
# @yieldreturn T
|
252
281
|
#
|
253
|
-
# @return [
|
282
|
+
# @return [State, T]
|
283
|
+
# when request is a success
|
284
|
+
# @raise [Remap::Error]
|
285
|
+
# when a fatal error occurs
|
254
286
|
#
|
255
287
|
# @private
|
256
288
|
def self.call!(state, &error)
|
257
|
-
new(state.options).call(state
|
289
|
+
new(state.options).call(state, &error)
|
258
290
|
end
|
259
291
|
|
260
292
|
# Mappers state according to the mapper rules
|
@@ -267,8 +299,8 @@ module Remap
|
|
267
299
|
#
|
268
300
|
# @private
|
269
301
|
def call(state, &error)
|
270
|
-
|
271
|
-
raise ArgumentError, "
|
302
|
+
state._ do |reason|
|
303
|
+
raise ArgumentError, "Invalid state due to #{reason.formatted}"
|
272
304
|
end
|
273
305
|
|
274
306
|
state.tap do |input|
|
@@ -279,13 +311,11 @@ module Remap
|
|
279
311
|
end
|
280
312
|
end
|
281
313
|
|
282
|
-
|
283
|
-
return context.call(state)
|
284
|
-
return error[failure]
|
285
|
-
end.then(&constructor)
|
314
|
+
s1 = catch_ignored do |id|
|
315
|
+
return context.call(state.set(id: id)).then(&constructor).remove_id
|
286
316
|
end
|
287
317
|
|
288
|
-
|
318
|
+
Failure.new(failures: s1.notices).then(&error)
|
289
319
|
end
|
290
320
|
|
291
321
|
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
|
@@ -308,12 +316,12 @@ module Remap
|
|
308
316
|
#
|
309
317
|
# @return [Rule::Each]]
|
310
318
|
# @raise [ArgumentError] if no block given
|
311
|
-
def each(&block)
|
319
|
+
def each(backtrace: caller, &block)
|
312
320
|
unless block_given?
|
313
321
|
raise ArgumentError, "#each requires a block"
|
314
322
|
end
|
315
323
|
|
316
|
-
add rule(all, &block)
|
324
|
+
add rule(all, backtrace: backtrace, &block)
|
317
325
|
end
|
318
326
|
|
319
327
|
# Wraps output in type
|
@@ -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/notice/error.rb
CHANGED
data/lib/remap/operation.rb
CHANGED
@@ -7,32 +7,37 @@ module Remap
|
|
7
7
|
module Operation
|
8
8
|
# Public interface for mappers
|
9
9
|
#
|
10
|
-
# @param input [Any]
|
11
|
-
# @param options [Hash]
|
10
|
+
# @param input [Any] data to be mapped
|
11
|
+
# @param options [Hash] mapper options
|
12
12
|
#
|
13
|
-
# @yield [Failure]
|
13
|
+
# @yield [Failure]
|
14
|
+
# when a non-critical error occurs
|
15
|
+
# @yieldreturn T
|
14
16
|
#
|
15
|
-
# @return [
|
16
|
-
|
17
|
-
|
17
|
+
# @return [Any, T]
|
18
|
+
# when request is a success
|
19
|
+
# @raise [Remap::Error]
|
20
|
+
# when a fatal error occurs
|
21
|
+
def call(input, backtrace: caller, **options, &error)
|
22
|
+
unless block_given?
|
18
23
|
return call(input, **options) do |failure|
|
19
|
-
raise failure.exception
|
24
|
+
raise failure.exception(backtrace)
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
s0 = State.call(input, options: options, mapper: self)._
|
29
|
+
|
30
|
+
s1 = call!(s0) do |failure|
|
31
|
+
return error[failure]
|
27
32
|
end
|
28
33
|
|
29
|
-
case
|
30
|
-
in { value: }
|
34
|
+
case s1
|
35
|
+
in { value: value }
|
31
36
|
value
|
32
37
|
in { notices: [] }
|
33
|
-
error[
|
38
|
+
error[s1.failure("No data could be mapped")]
|
34
39
|
in { notices: }
|
35
|
-
error[Failure.
|
40
|
+
error[Failure.new(failures: notices)]
|
36
41
|
end
|
37
42
|
end
|
38
43
|
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,20 +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
|
23
|
+
end
|
24
|
+
|
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)
|
31
|
+
|
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
|
24
39
|
end
|
25
40
|
|
26
|
-
|
27
|
-
rule.call(state) do |failure|
|
28
|
-
return error[failure]
|
29
|
-
end
|
30
|
-
end.reduce(&:combine)
|
41
|
+
raise failure.exception(backtrace)
|
31
42
|
end
|
32
43
|
end
|
33
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,8 +138,8 @@ module Remap
|
|
131
138
|
in [:value, Array => list1, Array => list2]
|
132
139
|
list1 + list2
|
133
140
|
in [:value, left, right]
|
134
|
-
fatal!(
|
135
|
-
"Could not merge [%
|
141
|
+
other.fatal!(
|
142
|
+
"Could not merge [%s] (%s) with [%s] (%s)",
|
136
143
|
left.formatted,
|
137
144
|
left.class,
|
138
145
|
right.formatted,
|
@@ -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
|
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.41
|
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
|