remap 2.1.13 → 2.1.20

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 526ddded05ad56900a9f6d1276069c48297adc6f5cb32ef08b955949bf9ee426
4
- data.tar.gz: 239295549c9f0365453d19254fe21ff046755b6066f86a81596abaf102a98bbb
3
+ metadata.gz: 6349765f73b38ed07625ac4761cabbda481918282bcd8f081534a9c79847fb8a
4
+ data.tar.gz: cfe36187424239255361d7aec9c4a654e921aa4344abb6f3556fde267bd25640
5
5
  SHA512:
6
- metadata.gz: 1c8d02457838ffe470359e5b9ff97cd2a31288928d238362eb38d85c190160b430242d2cfb401e9f7dbb418be4838dd6cbb1c282b68a7eababf5ad2d48030440
7
- data.tar.gz: 191f1570f40e582c31e724e788882b54f83ff82e6d37931519451975355ceab5fb708260352523ddc10c849daa86d31c915221e9d539f2c069288462ca5dc45b
6
+ metadata.gz: f78149d927a42545f9078237a836f3c94cf7f4f9fd6068d2235352299e52c486c0528aca9752e85b6c69564db1e6c4388b9594902d1cc510edfc697b6aa5e8e8
7
+ data.tar.gz: 8f4121568ff54e8e0e780f5ec9703b5c4ebfbc946c550e207ed5b27026e8fb523173a28d0c9d98fd858478dd06b89786a11eba4c9dcd3a36a508fb4cc628966e
data/lib/remap/base.rb CHANGED
@@ -143,7 +143,7 @@ module Remap
143
143
  #
144
144
  # @return [void]
145
145
  def self.contract(&context)
146
- self.contract = Dry::Schema.JSON(&context)
146
+ self.contract = Dry::Schema.define(&context)
147
147
  end
148
148
 
149
149
  # Defines a rule for the mapper
@@ -236,14 +236,12 @@ module Remap
236
236
  #
237
237
  # @return [void]
238
238
  def self.define(target = Nothing, method: :new, strategy: :argument, &context)
239
- unless context
240
- raise ArgumentError, "Missing block"
239
+ unless block_given?
240
+ raise ArgumentError, "#{self}.define requires a block"
241
241
  end
242
242
 
243
- self.context = Compiler.call(&context)
244
243
  self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
245
- rescue Dry::Struct::Error => e
246
- raise ArgumentError, e.message
244
+ self.context = Compiler.call(&context)
247
245
  end
248
246
 
249
247
  # Similar to {::call}, but takes a state
@@ -269,7 +267,7 @@ module Remap
269
267
  #
270
268
  # @private
271
269
  def call(state, &error)
272
- unless error
270
+ unless block_given?
273
271
  raise ArgumentError, "Base#call(state, &error) requires block"
274
272
  end
275
273
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
+ using State::Extension
5
+
4
6
  # Constructs a {Rule} from the block passed to {Remap::Base.define}
5
7
  class Compiler < Proxy
6
8
  # @return [Array<Rule>]
@@ -28,13 +30,15 @@ module Remap
28
30
  #
29
31
  # @return [Rule]
30
32
  def self.call(&block)
31
- unless block
32
- return Rule::Void.new
33
+ unless block_given?
34
+ return Rule::VOID
33
35
  end
34
36
 
35
- new([]).tap do |compiler|
37
+ rules = new([]).tap do |compiler|
36
38
  compiler.instance_exec(&block)
37
- end.rule
39
+ end.rules
40
+
41
+ Rule::Block.new(rules)
38
42
  end
39
43
 
40
44
  # Maps input path [input] to output path [to]
@@ -58,14 +62,8 @@ module Remap
58
62
  # output.fetch(:value) # => { nickname: "John" }
59
63
  #
60
64
  # @return [Rule::Map::Required]
61
- def map(*path, to: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
62
- add Rule::Map::Required.call(
63
- path: {
64
- output: [to].flatten,
65
- input: path.flatten
66
- },
67
- backtrace: backtrace,
68
- rule: call(&block))
65
+ def map(*path, to: EMPTY_ARRAY, backtrace: caller, &block)
66
+ add rule(*path, to: to, backtrace: backtrace, &block)
69
67
  end
70
68
 
71
69
  # Optional version of {#map}
@@ -91,14 +89,8 @@ module Remap
91
89
  # @see #map
92
90
  #
93
91
  # @return [Rule::Map::Optional]
94
- def map?(*path, to: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
95
- add Rule::Map::Optional.call(
96
- path: {
97
- output: [to].flatten,
98
- input: path.flatten
99
- },
100
- backtrace: backtrace,
101
- rule: call(&block))
92
+ def map?(*path, to: EMPTY_ARRAY, backtrace: caller, &block)
93
+ add rule?(*path, to: to, backtrace: backtrace, &block)
102
94
  end
103
95
 
104
96
  # Select a path and uses the same path as output
@@ -121,8 +113,8 @@ module Remap
121
113
  # output.fetch(:value) # => { name: "John" }
122
114
  #
123
115
  # @return [Rule::Map::Required]
124
- def get(*path, backtrace: Kernel.caller, &block)
125
- map(path, to: path, backtrace: backtrace, &block)
116
+ def get(*path, backtrace: caller, &block)
117
+ add rule(path, to: path, backtrace: backtrace, &block)
126
118
  end
127
119
 
128
120
  # Optional version of {#get}
@@ -146,8 +138,8 @@ module Remap
146
138
  # @see #get
147
139
  #
148
140
  # @return [Rule::Map::Optional]
149
- def get?(*path, backtrace: Kernel.caller, &block)
150
- map?(path, to: path, backtrace: backtrace, &block)
141
+ def get?(*path, backtrace: caller, &block)
142
+ add rule?(path, to: path, backtrace: backtrace, &block)
151
143
  end
152
144
 
153
145
  # Maps using mapper
@@ -184,14 +176,18 @@ module Remap
184
176
  # output.fetch(:value) # => { car: "Volvo" }
185
177
  #
186
178
  # @return [Rule::Embed]
187
- def embed(mapper, &block)
188
- if block
179
+ def embed(mapper)
180
+ if block_given?
189
181
  raise ArgumentError, "#embed does not take a block"
190
182
  end
191
183
 
192
- add Rule::Embed.new(mapper: mapper)
193
- rescue Dry::Struct::Error
194
- raise ArgumentError, "Embeded mapper must be [Remap::Mapper], got [#{mapper}]"
184
+ embeding = rule.add do |state, &error|
185
+ mapper.call!(state.set(mapper: mapper)) do |failure|
186
+ next error[failure]
187
+ end.except(:mapper, :scope)
188
+ end
189
+
190
+ add embeding
195
191
  end
196
192
 
197
193
  # Set a static value
@@ -229,14 +225,12 @@ module Remap
229
225
  # @raise [ArgumentError]
230
226
  # if no path given
231
227
  # if path is not a Symbol or Array<Symbol>
232
- def set(*path, to:, &block)
233
- if block
228
+ def set(*path, to:)
229
+ if block_given?
234
230
  raise ArgumentError, "#set does not take a block"
235
231
  end
236
232
 
237
- add Rule::Set.new(path: path.flatten, value: to)
238
- rescue Dry::Struct::Error => e
239
- raise ArgumentError, e.message
233
+ add rule(to: path).add { to.call(_1) }
240
234
  end
241
235
 
242
236
  # Maps to path from map with block in between
@@ -260,8 +254,8 @@ module Remap
260
254
  # output.fetch(:value) # => { nickname: "John" }
261
255
  #
262
256
  # @return [Rule::Map]
263
- def to(*path, map: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
264
- map(*map, to: path, backtrace: backtrace, &block)
257
+ def to(*path, map: EMPTY_ARRAY, backtrace: caller, &block)
258
+ add rule(*map, to: path, backtrace: backtrace, &block)
265
259
  end
266
260
 
267
261
  # Optional version of {#to}
@@ -287,7 +281,7 @@ module Remap
287
281
  #
288
282
  # @return [Rule::Map::Optional]
289
283
  def to?(*path, map: EMPTY_ARRAY, &block)
290
- map?(*map, to: path, &block)
284
+ add rule?(*map, to: path, &block)
291
285
  end
292
286
 
293
287
  # Iterates over the input value, passes each value
@@ -315,11 +309,11 @@ module Remap
315
309
  # @return [Rule::Each]]
316
310
  # @raise [ArgumentError] if no block given
317
311
  def each(&block)
318
- unless block
312
+ unless block_given?
319
313
  raise ArgumentError, "#each requires a block"
320
314
  end
321
315
 
322
- add Rule::Each.new(rule: call(&block))
316
+ add rule(all, &block)
323
317
  end
324
318
 
325
319
  # Wraps output in type
@@ -348,13 +342,11 @@ module Remap
348
342
  # @return [Rule::Wrap]
349
343
  # @raise [ArgumentError] if type is not :array
350
344
  def wrap(type, &block)
351
- unless block
345
+ unless block_given?
352
346
  raise ArgumentError, "#wrap requires a block"
353
347
  end
354
348
 
355
- add Rule::Wrap.new(type: type, rule: call(&block))
356
- rescue Dry::Struct::Error => e
357
- raise ArgumentError, e.message
349
+ add rule(&block).then { Array.wrap(_1) }
358
350
  end
359
351
 
360
352
  # Selects all elements
@@ -376,8 +368,8 @@ module Remap
376
368
  # output.fetch(:value) # => { names: ["John", "Jane"] }
377
369
  #
378
370
  # @return [Rule::Path::Segment::Quantifier::All]
379
- def all(&block)
380
- if block
371
+ def all
372
+ if block_given?
381
373
  raise ArgumentError, "all selector does not take a block"
382
374
  end
383
375
 
@@ -402,8 +394,8 @@ module Remap
402
394
  # output.fetch(:value) # => { api_key: "<SECRET>" }
403
395
  #
404
396
  # @return [Rule::Static::Fixed]
405
- def value(value, &block)
406
- if block
397
+ def value(value)
398
+ if block_given?
407
399
  raise ArgumentError, "option selector does not take a block"
408
400
  end
409
401
 
@@ -428,8 +420,8 @@ module Remap
428
420
  # @param id [Symbol]
429
421
  #
430
422
  # @return [Rule::Static::Option]
431
- def option(id, backtrace: Kernel.caller, &block)
432
- if block
423
+ def option(id, backtrace: caller)
424
+ if block_given?
433
425
  raise ArgumentError, "option selector does not take a block"
434
426
  end
435
427
 
@@ -457,8 +449,8 @@ module Remap
457
449
  #
458
450
  # @return [Path::Segment::Key]
459
451
  # @raise [ArgumentError] if index is not an Integer
460
- def at(index, &block)
461
- if block
452
+ def at(index)
453
+ if block_given?
462
454
  raise ArgumentError, "first selector does not take a block"
463
455
  end
464
456
 
@@ -486,8 +478,8 @@ module Remap
486
478
  # output.fetch(:value) # => { name: "John" }
487
479
  #
488
480
  # @return [Path::Segment::Key]]
489
- def first(&block)
490
- if block
481
+ def first
482
+ if block_given?
491
483
  raise ArgumentError, "first selector does not take a block"
492
484
  end
493
485
 
@@ -513,27 +505,39 @@ module Remap
513
505
  # output.fetch(:value) # => { name: "Linus" }
514
506
  #
515
507
  # @return [Path::Segment::Key]
516
- def last(&block)
517
- if block
508
+ def last
509
+ if block_given?
518
510
  raise ArgumentError, "last selector does not take a block"
519
511
  end
520
512
 
521
513
  at(-1)
522
514
  end
523
515
 
524
- # The final rule
525
- #
526
- # @return [Rule]
527
- #
528
- # @private
529
- def rule
530
- Rule::Collection.call(rules: rules)
531
- end
532
-
533
516
  private
534
517
 
535
518
  def add(rule)
536
519
  rule.tap { rules << rule }
537
520
  end
521
+
522
+ def rule(*path, to: EMPTY_ARRAY, backtrace: caller, &block)
523
+ Rule::Map::Required.call({
524
+ path: {
525
+ output: [to].flatten,
526
+ input: path.flatten
527
+ },
528
+ backtrace: backtrace,
529
+ rule: call(&block)
530
+ })
531
+ end
532
+
533
+ def rule?(*path, to: EMPTY_ARRAY, backtrace: caller, &block)
534
+ Rule::Map::Optional.call({
535
+ path: {
536
+ output: [to].flatten,
537
+ input: path.flatten
538
+ },
539
+ rule: call(&block)
540
+ })
541
+ end
538
542
  end
539
543
  end
@@ -28,19 +28,20 @@ module Remap
28
28
  # @return [Any]
29
29
  #
30
30
  # @raise When path cannot be found
31
- def get(*path, &error)
32
- _, result = path.reduce([
33
- EMPTY_ARRAY,
34
- self
35
- ]) do |(current_path, element), key|
36
- value = element.fetch(key) do
37
- raise PathError, current_path + [key]
38
- end
31
+ def get(*path, trace: [], &fallback)
32
+ return self if path.empty?
33
+
34
+ key = path.first
39
35
 
40
- [current_path + [key], value]
36
+ unless block_given?
37
+ return get(*path, trace: trace) do
38
+ raise PathError, trace + [key]
39
+ end
41
40
  end
42
41
 
43
- result
42
+ fetch(key, &fallback).get(*path[1..], trace: trace + [key], &fallback)
43
+ rescue TypeError
44
+ raise PathError, trace + [key]
44
45
  end
45
46
  end
46
47
  end
@@ -17,14 +17,29 @@ module Remap
17
17
  block["Expected a state, got [#{self}] (#{self.class})"]
18
18
  end
19
19
 
20
+ # @return [Array]
21
+ #
22
+ # @see Extension::Paths::Hash
23
+ def paths
24
+ []
25
+ end
26
+
20
27
  # Fallback method used when #get is called on an object that does not respond to #get
21
28
  #
22
29
  # Block is invoked, if provided
23
30
  # Otherwise a symbol is thrown
24
31
  #
25
32
  # @param path [Array<Key>]
26
- def get(*path, &block)
27
- raise PathError, []
33
+ def get(*path, trace: [], &fallback)
34
+ return self if path.empty?
35
+
36
+ unless block_given?
37
+ return get(*path, trace: trace) do
38
+ raise PathError, trace
39
+ end
40
+ end
41
+
42
+ yield
28
43
  end
29
44
  alias_method :fetch, :get
30
45
 
data/lib/remap/failure.rb CHANGED
@@ -14,22 +14,21 @@ module Remap
14
14
  # @return [Failure]
15
15
  def merge(other)
16
16
  unless other.is_a?(self.class)
17
- raise ArgumentError, "can't merge #{self.class} with #{other.class}"
17
+ raise ArgumentError, "Cannot merge %s (%s) with %s (%s)" % [
18
+ self, self.class, other, other.class
19
+ ]
18
20
  end
19
21
 
20
- failure = attributes.deep_merge(other.attributes) do |_, value1, value2|
21
- case [value1, value2]
22
- in [Array, Array]
22
+ failure = attributes.deep_merge(other.attributes) do |key, value1, value2|
23
+ case [key, value1, value2]
24
+ in [:failures | :notices, Array, Array]
23
25
  value1 + value2
24
- else
25
- raise ArgumentError, "can't merge #{self.class} with #{other.class}"
26
26
  end
27
27
  end
28
28
 
29
29
  new(failure)
30
30
  end
31
31
 
32
- # @return [String]
33
32
  def exception
34
33
  Error.new(attributes.formated)
35
34
  end
data/lib/remap/proxy.rb CHANGED
@@ -8,6 +8,7 @@ module Remap
8
8
 
9
9
  include Dry::Core::Constants
10
10
  extend Dry::Initializer
11
+ include Kernel
11
12
 
12
13
  # See Object#tap
13
14
  def tap(&block)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ using State::Extension
6
+
7
+ class Block < Unit
8
+ # @return [Array<Rule>]
9
+ attribute :rules, [Types::Rule]
10
+
11
+ # Represents a non-empty define block with one or more rules
12
+ # Calls every {#rules} with state and merges the output
13
+ #
14
+ # @param state [State]
15
+ #
16
+ # @return [State]
17
+ def call(state, &error)
18
+ unless block_given?
19
+ raise ArgumentError, "Block#call(state, &error) requires a block"
20
+ end
21
+
22
+ if rules.empty?
23
+ return state.except(:value)
24
+ end
25
+
26
+ rules.map do |rule|
27
+ rule.call(state) do |failure|
28
+ return error[failure]
29
+ end
30
+ end.reduce(&:combine)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Rule
5
+ class Map
6
+ class Enum < Proxy
7
+ include Dry::Monads[:maybe]
8
+
9
+ # @return [Hash]
10
+ option :mappings, default: -> { Hash.new { default } }
11
+
12
+ # @return [Maybe]
13
+ option :default, default: -> { None() }
14
+
15
+ alias execute instance_eval
16
+
17
+ # Builds an enumeration using the block as context
18
+ #
19
+ # @example
20
+ # enum = Remap::Rule::Map::Enum.call do
21
+ # from "B", to: "C"
22
+ # value "A"
23
+ # otherwise "D"
24
+ # end
25
+ #
26
+ # enum.get("A") # => "A"
27
+ # enum.get("B") # => "C"
28
+ # enum.get("C") # => "C"
29
+ # enum.get("MISSING") # => "D"
30
+ #
31
+ # @return [Any]
32
+ def self.call(&block)
33
+ unless block
34
+ raise ArgumentError, "no block given"
35
+ end
36
+
37
+ new.tap { _1.execute(&block) }
38
+ end
39
+
40
+ # Translates key into a value using predefined mappings
41
+ #
42
+ # @param key [#hash]
43
+ #
44
+ # @yield [String]
45
+ # If the key is not found & no default value is set
46
+ #
47
+ # @return [Any]
48
+ def get(key, &error)
49
+ unless error
50
+ return get(key) { raise Error, _1 }
51
+ end
52
+
53
+ self[key].bind { return _1 }.or do
54
+ error["Enum key [#{key}] not found among [#{mappings.keys.inspect}]"]
55
+ end
56
+ end
57
+ alias call get
58
+
59
+ # @return [Maybe]
60
+ def [](key)
61
+ mappings[key]
62
+ end
63
+
64
+ # @return [void]
65
+ def from(*keys, to:)
66
+ value = Some(to)
67
+
68
+ keys.each do |key|
69
+ mappings[key] = value
70
+ mappings[to] = value
71
+ end
72
+ end
73
+
74
+ # @return [void]
75
+ def value(*ids)
76
+ ids.each do |id|
77
+ from(id, to: id)
78
+ end
79
+ end
80
+
81
+ # @return [void]
82
+ def otherwise(value)
83
+ mappings.default = Some(value)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -31,7 +31,7 @@ module Remap
31
31
  #
32
32
  # @return [State]
33
33
  def call(state, &error)
34
- unless error
34
+ unless block_given?
35
35
  raise ArgumentError, "map.call(state, &error) requires a block"
36
36
  end
37
37
 
@@ -43,15 +43,19 @@ module Remap
43
43
  #
44
44
  # @abstract
45
45
  def call(state, &error)
46
- unless error
46
+ unless block_given?
47
47
  raise ArgumentError, "Map#call(state, &error) requires error handler block"
48
48
  end
49
49
 
50
50
  notice = catch :fatal do
51
51
  return path.input.call(state) do |inner_state|
52
- rule.call(inner_state) do |failure|
52
+ other_state = rule.call(inner_state) do |failure|
53
53
  return error[failure]
54
- end.then(&callback)
54
+ end
55
+
56
+ callback(other_state) do |failure|
57
+ return error[failure]
58
+ end
55
59
  end.then(&path.output)
56
60
  end
57
61
 
@@ -181,23 +185,27 @@ module Remap
181
185
  end
182
186
  end
183
187
 
184
- private
185
-
186
188
  # @return [self]
187
189
  def add(&block)
188
190
  tap { fn << block }
189
191
  end
190
192
 
193
+ private
194
+
191
195
  # @return [Array<Proc>]
192
196
  def fn
193
197
  @fn ||= []
194
198
  end
195
199
 
196
200
  # @return [Proc]
197
- def callback
198
- -> state do
199
- fn.reduce(state) do |inner, fn|
200
- fn[inner]
201
+ def callback(state, &error)
202
+ unless block_given?
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]
201
209
  end
202
210
  end
203
211
  end
data/lib/remap/rule.rb CHANGED
@@ -5,6 +5,8 @@ module Remap
5
5
  defines :requirement
6
6
  requirement Types::Any
7
7
 
8
+ VOID = Void.new(EMPTY_HASH)
9
+
8
10
  # @param state [State]
9
11
  #
10
12
  # @abstract
@@ -8,8 +8,14 @@ module Remap
8
8
  #
9
9
  # @example Select the value at index 1 from a array
10
10
  # state = Remap::State.call([:one, :two, :tree])
11
- # result = Remap::Selector::Index.new(1).call(state)
12
- # result.fetch(:value) # => :two
11
+ # index = Remap::Selector::Index.new(1)
12
+ #
13
+ # result = index.call(state) do |element|
14
+ # value = element.fetch(:value)
15
+ # element.merge(value: value.upcase)
16
+ # end
17
+ #
18
+ # result.fetch(:value) # => :TWO
13
19
  class Index < Unit
14
20
  # @return [Integer]
15
21
  attribute :index, Integer
@@ -25,7 +31,9 @@ module Remap
25
31
  #
26
32
  # @return [State<U>]
27
33
  def call(outer_state, &block)
28
- return call(outer_state, &:itself) unless block
34
+ unless block_given?
35
+ raise ArgumentError, "The index selector requires an iteration block"
36
+ end
29
37
 
30
38
  outer_state.bind(index: index) do |array, state|
31
39
  requirement[array] do
@@ -7,13 +7,6 @@ module Remap
7
7
  using Extensions::Object
8
8
  using Extensions::Hash
9
9
 
10
- refine Object do
11
- # @see Extension::Paths::Hash
12
- def paths
13
- EMPTY_ARRAY
14
- end
15
- end
16
-
17
10
  refine Hash do
18
11
  # Returns a list of all key paths
19
12
  #
@@ -23,7 +16,7 @@ module Remap
23
16
  # b: :c
24
17
  # },
25
18
  # d: :e
26
- # }.hur_paths # => [[:a, :b], [:d]]
19
+ # }.paths # => [[:a, :b], [:d]]
27
20
  #
28
21
  # @return [Array<Array<Symbol>>] a list of key paths
29
22
  def paths
@@ -47,7 +40,7 @@ module Remap
47
40
  # b: :c
48
41
  # },
49
42
  # d: :e
50
- # }.hur_only(:a, :b) # => { a: { b: :c } }
43
+ # }.only(:a, :b) # => { a: { b: :c } }
51
44
  #
52
45
  # @returns [Hash] a hash containing the given path
53
46
  # @raise Europace::Error when path doesn't exist
@@ -117,11 +110,7 @@ module Remap
117
110
  def map(&block)
118
111
  bind do |value, state|
119
112
  Iteration.call(state: state, value: value).call do |other, **options|
120
- state.set(other, **options).then do |inner_state|
121
- block[inner_state] do |failure|
122
- throw :failure, failure
123
- end
124
- end
113
+ state.set(other, **options).then(&block)
125
114
  end.except(:index, :element, :key)
126
115
  end
127
116
  end
@@ -275,10 +264,6 @@ module Remap
275
264
  end
276
265
 
277
266
  set(result)._
278
- rescue NoMethodError => e
279
- e.name == :fetch ? error["Fetch not defined on value: #{e}"] : raise
280
- rescue NameError => e
281
- e.name == :Undefined ? error["Undefined returned, skipping!: #{e}"] : raise
282
267
  rescue KeyError, IndexError => e
283
268
  error[e.message]
284
269
  rescue PathError => e
@@ -351,7 +336,7 @@ module Remap
351
336
  #
352
337
  # @return [Struct]
353
338
  def context(value, context: self, &error)
354
- ::Struct.new(*keys, *options.keys, keyword_init: true) do
339
+ ::Struct.new(*keys, *options.keys, :state, keyword_init: true) do
355
340
  define_method :method_missing do |name, *|
356
341
  error["Method [#{name}] not defined"]
357
342
  end
@@ -359,7 +344,7 @@ module Remap
359
344
  define_method :skip! do |message = "Manual skip!"|
360
345
  context.ignore!(message)
361
346
  end
362
- end.new(**to_hash, **options, value: value)
347
+ end.new(**to_hash, **options, value: value, state: self)
363
348
  end
364
349
  end
365
350
  end
data/lib/remap/state.rb CHANGED
@@ -30,19 +30,15 @@ module Remap
30
30
  #
31
31
  # @return [Hash] A valid state
32
32
  def self.call(value, mapper: Dummy, options: EMPTY_HASH)
33
- value._ do
34
- return {
35
- notices: EMPTY_ARRAY,
36
- path: EMPTY_ARRAY,
37
- options: options,
38
- mapper: mapper,
39
- values: value,
40
- value: value,
41
- input: value
42
- }._
43
- end
44
-
45
- raise ArgumentError, "Input is a state: #{value}"
33
+ {
34
+ notices: EMPTY_ARRAY,
35
+ path: EMPTY_ARRAY,
36
+ options: options,
37
+ mapper: mapper,
38
+ values: value,
39
+ value: value,
40
+ input: value
41
+ }._
46
42
  end
47
43
  end
48
44
  end
data/lib/remap/types.rb CHANGED
@@ -15,7 +15,7 @@ module Remap
15
15
  Enumerable = Any.constrained(type: Enumerable)
16
16
  Nothing = Constant(Remap::Nothing)
17
17
  Mapper = Interface(:call!)
18
- Rule = Interface(:call)
18
+ Rule = Interface(:call) | Instance(Proc)
19
19
  Key = Interface(:hash)
20
20
 
21
21
  # Validates a state according to State::Schema
data/lib/remap.rb CHANGED
@@ -17,7 +17,6 @@ require "zeitwerk"
17
17
 
18
18
  module Remap
19
19
  loader = Zeitwerk::Loader.for_gem
20
- loader.collapse("#{__dir__}/remap/rule/support")
21
20
  loader.collapse("#{__dir__}/remap/mapper/support")
22
21
  loader.setup
23
22
  loader.eager_load
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remap
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.13
4
+ version: 2.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Linus Oleander
@@ -279,18 +279,12 @@ files:
279
279
  - lib/remap/path_error.rb
280
280
  - lib/remap/proxy.rb
281
281
  - lib/remap/rule.rb
282
- - lib/remap/rule/each.rb
283
- - lib/remap/rule/embed.rb
282
+ - lib/remap/rule/block.rb
284
283
  - lib/remap/rule/map.rb
284
+ - lib/remap/rule/map/enum.rb
285
285
  - lib/remap/rule/map/optional.rb
286
286
  - lib/remap/rule/map/required.rb
287
- - lib/remap/rule/set.rb
288
- - lib/remap/rule/support/collection.rb
289
- - lib/remap/rule/support/collection/empty.rb
290
- - lib/remap/rule/support/collection/filled.rb
291
- - lib/remap/rule/support/enum.rb
292
287
  - lib/remap/rule/void.rb
293
- - lib/remap/rule/wrap.rb
294
288
  - lib/remap/selector.rb
295
289
  - lib/remap/selector/all.rb
296
290
  - lib/remap/selector/index.rb
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- using State::Extension
6
-
7
- # Iterates over a rule, even if the rule is not a collection
8
- #
9
- # @example Upcase each value in an array
10
- # state = Remap::State.call(["John", "Jane"])
11
- # upcase = Remap::Rule::Map.call({}).then(&:upcase)
12
- # each = Remap::Rule::Each.call(rule: upcase)
13
- # error = -> failure { raise failure.exception }
14
- # each.call(state, &error).fetch(:value) # => ["JOHN", "JANE"]
15
- class Each < Unit
16
- # @return [Rule]
17
- attribute :rule, Types::Rule
18
-
19
- # Iterates over state and passes each value to rule
20
- # Restores element, key & index before returning state
21
- #
22
- # @param state [State<Enumerable>]
23
- #
24
- # @return [State<Enumerable>]
25
- def call(state, &error)
26
- unless error
27
- raise ArgumentError, "Each#call(state, &error) requires a block"
28
- end
29
-
30
- state.map do |inner_state|
31
- rule.call(inner_state) do |failure|
32
- return error[failure]
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- using State::Extension
6
-
7
- # Embed mappers into each other
8
- #
9
- # @example Embed Mapper A into B
10
- # class Car < Remap::Base
11
- # define do
12
- # map :name, to: :model
13
- # end
14
- # end
15
- #
16
- # class Person < Remap::Base
17
- # define do
18
- # to :person do
19
- # to :car do
20
- # embed Car
21
- # end
22
- # end
23
- # end
24
- # end
25
- #
26
- # Person.call({name: "Volvo"}) # => { person: { car: { model: "Volvo" } } }
27
- class Embed < Unit
28
- # @return [#call!]
29
- attribute :mapper, Types::Mapper
30
-
31
- # Evaluates input against mapper and returns the result
32
- #
33
- # @param state [State<T>]
34
- #
35
- # @return [State<U>]
36
- def call(state, &error)
37
- unless error
38
- raise ArgumentError, "A block is required to evaluate the embed"
39
- end
40
-
41
- mapper.call!(state.set(mapper: mapper)) do |failure|
42
- return error[failure]
43
- end.except(:mapper, :scope)
44
- end
45
- end
46
- end
47
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- using State::Extension
6
-
7
- # Set path to a static value
8
- #
9
- # @example Set path [:a, :b] to value "C"
10
- # value = Remap::Static::Fixed.new(value: "a value")
11
- # set = Remap::Rule::Set.new(value: value, path: [:a, :b])
12
- # state = Remap::State.call("ANY VALUE")
13
- # set.call(state).fetch(:value) # => { a: { b: "a value" } }
14
- #
15
- # @example Set path [:a, :b] to option :c
16
- # value = Remap::Static::Option.new(name: :c)
17
- # set = Remap::Rule::Set.new(value: value, path: [:a, :b])
18
- # state = Remap::State.call("ANY VALUE", options: { c: "C" })
19
- # set.call(state).fetch(:value) # => { a: { b: "C" } }
20
- class Set < Concrete
21
- # @return [Static]
22
- attribute :value, Static, alias: :rule
23
-
24
- # @return [Path::Output]
25
- attribute :path, Path::Output
26
-
27
- # @see Rule#call
28
- def call(...)
29
- rule.call(...).then(&path)
30
- end
31
- end
32
- end
33
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- class Collection
6
- using State::Extension
7
-
8
- # Represents an empty rule block
9
- class Empty < Unit
10
- attribute? :rules, Value(EMPTY_ARRAY), default: EMPTY_ARRAY
11
-
12
- # Represents an empty define block, without any rules
13
- #
14
- # @param state [State<T>]
15
- #
16
- # @return [State<T>]
17
- def call(state)
18
- state.notice!("No rules, empty block")
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- class Collection
6
- using State::Extension
7
-
8
- # Represents a non-empty rule block
9
- #
10
- # @example A collection containing a single rule
11
- # state = Remap::State.call("A")
12
- # void = Remap::Rule::Void.call({})
13
- # rule = Remap::Rule::Collection.call([void])
14
- # error = -> failure { raise failure.exception }
15
- # rule.call(state, &error).fetch(:value) # => "A"
16
- class Filled < Unit
17
- # @return [Array<Rule>]
18
- attribute :rules, [Types.Interface(:call)], min_size: 1
19
-
20
- # Represents a non-empty define block with one or more rules
21
- # Calls every {#rules} with state and merges the output
22
- #
23
- # @param state [State]
24
- #
25
- # @return [State]
26
- def call(state, &error)
27
- unless error
28
- raise ArgumentError, "Collection::Filled#call(state, &error) requires a block"
29
- end
30
-
31
- rules.map do |rule|
32
- rule.call(state) do |failure|
33
- return error[failure]
34
- end
35
- end.reduce(&:combine)
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- # Represents a block defined by a rule
6
- class Collection < Abstract
7
- attribute :rules, Array
8
-
9
- # @param state [State]
10
- #
11
- # @return [State]
12
- #
13
- # @abstract
14
- def call(state)
15
- raise NotImplementedError, "#{self.class}#call not implemented"
16
- end
17
- end
18
- end
19
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- class Enum < Proxy
6
- include Dry::Monads[:maybe]
7
-
8
- # @return [Hash]
9
- option :mappings, default: -> { Hash.new { default } }
10
-
11
- # @return [Maybe]
12
- option :default, default: -> { None() }
13
-
14
- alias execute instance_eval
15
-
16
- # Builds an enumeration using the block as context
17
- #
18
- # @example
19
- # enum = Remap::Rule::Enum.call do
20
- # from "B", to: "C"
21
- # value "A"
22
- # otherwise "D"
23
- # end
24
- #
25
- # enum.get("A") # => "A"
26
- # enum.get("B") # => "C"
27
- # enum.get("C") # => "C"
28
- # enum.get("MISSING") # => "D"
29
- #
30
- # @return [Any]
31
- def self.call(&block)
32
- unless block
33
- raise ArgumentError, "no block given"
34
- end
35
-
36
- new.tap { _1.execute(&block) }
37
- end
38
-
39
- # Translates key into a value using predefined mappings
40
- #
41
- # @param key [#hash]
42
- #
43
- # @yield [String]
44
- # If the key is not found & no default value is set
45
- #
46
- # @return [Any]
47
- def get(key, &error)
48
- unless error
49
- return get(key) { raise Error, _1 }
50
- end
51
-
52
- self[key].bind { return _1 }.or do
53
- error["Enum key [#{key}] not found among [#{mappings.keys.inspect}]"]
54
- end
55
- end
56
- alias call get
57
-
58
- # @return [Maybe]
59
- def [](key)
60
- mappings[key]
61
- end
62
-
63
- # @return [void]
64
- def from(*keys, to:)
65
- value = Some(to)
66
-
67
- keys.each do |key|
68
- mappings[key] = value
69
- mappings[to] = value
70
- end
71
- end
72
-
73
- # @return [void]
74
- def value(*ids)
75
- ids.each do |id|
76
- from(id, to: id)
77
- end
78
- end
79
-
80
- # @return [void]
81
- def otherwise(value)
82
- mappings.default = Some(value)
83
- end
84
- end
85
- end
86
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Rule
5
- using State::Extension
6
-
7
- # Wraps rule in a type
8
- #
9
- # @example Maps { name: "Ford" } to { cars: ["Ford"] }
10
- # class Mapper < Remap::Base
11
- # define do
12
- # to :cars do
13
- # wrap(:array) do
14
- # map :name
15
- # end
16
- # end
17
- # end
18
- # end
19
- #
20
- # Mapper.call({ name: "Ford" }) # => { cars: ["Ford"] }
21
- class Wrap < Concrete
22
- # @return [:array]
23
- attribute :type, Value(:array)
24
-
25
- # @return [Rule]
26
- attribute :rule, Types::Rule
27
-
28
- # Wraps the output from {#rule} in a {#type}
29
- #
30
- # @see Rule#call
31
- def call(...)
32
- rule.call(...).fmap do |value|
33
- Array.wrap(value)
34
- end
35
- end
36
- end
37
- end
38
- end