remap 2.2.44 → 2.2.48

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9c58b85124adc855131d9efee5ce1ff8653cf4828882a5ad1b60ebb0d1ea99b
4
- data.tar.gz: ace57dfce51c7b820d2db5c2f742e5e44828bc998cc719a817bb040e4d08f139
3
+ metadata.gz: a564dcf130adbf8c40eb089d14f498f993362b74823728ec8ad44b98014034e6
4
+ data.tar.gz: 3dcac97cd1e4c630ec55ccdbdb6c43922f2ce5367d770e471ebaa77f5ae3fc8c
5
5
  SHA512:
6
- metadata.gz: d868e87397958d9c68f2048a7888e306c3764dbac799ff5c34c2d8d00e08463fe5abacc708dcc232605c33f319b1cca0b34252108cfb8f4ca1f7831718956d9e
7
- data.tar.gz: 3880cde868fbfa68702db58d6db914825f34614b140c3284c264f6b4303338dfda22f1a0f151419d610225c4cd029a776c149b4901607915988acf3c6598386a
6
+ metadata.gz: 237c0305757a4a12e5d3c24358fd9ed6295d0159baf0bb5786a9b019e3b50c98bd163334b35b04bab99ef1f19adeb232aec234a2aed76ad16ade778c3950606e
7
+ data.tar.gz: 1c6637383207baf0bd32fd6398f34dc935a5237e2fa67eafa2fccaa1db0d1f9d3f569c6f0695f9c148e737b1c4de15a974a09ba193d0af55ffaf3bef21bad07d
data/lib/remap/base.rb CHANGED
@@ -34,33 +34,33 @@ module Remap
34
34
  # Mapper.call({}) # => { api_key: "ABC-123" }
35
35
  #
36
36
  # @example Maps ["A", "B", "C"] to ["A", "C"]
37
- # class Mapper < Remap::Base
37
+ # class IfNotMapper < Remap::Base
38
38
  # define do
39
39
  # each do
40
- # map?.if_not do
40
+ # map?.if_not do |value|
41
41
  # value.include?("B")
42
42
  # end
43
43
  # end
44
44
  # end
45
45
  # end
46
46
  #
47
- # Mapper.call(["A", "B", "C"]) # => ["A", "C"]
47
+ # IfNotMapper.call(["A", "B", "C"]) # => ["A", "C"]
48
48
  #
49
49
  # @example Maps ["A", "B", "C"] to ["B"]
50
- # class Mapper < Remap::Base
50
+ # class IfMapper < Remap::Base
51
51
  # define do
52
52
  # each do
53
- # map?.if do
53
+ # map?.if do |value|
54
54
  # value.include?("B")
55
55
  # end
56
56
  # end
57
57
  # end
58
58
  # end
59
59
  #
60
- # Mapper.call(["A", "B", "C"]) # => ["B"]
60
+ # IfMapper.call(["A", "B", "C"]) # => ["B"]
61
61
  #
62
62
  # @example Maps { a: { b: "A" } } to "A"
63
- # class Mapper < Remap::Base
63
+ # class EnumMapper < Remap::Base
64
64
  # define do
65
65
  # map(:a, :b).enum do
66
66
  # value "A", "B"
@@ -68,11 +68,11 @@ module Remap
68
68
  # end
69
69
  # end
70
70
  #
71
- # Mapper.call({ a: { b: "A" } }) # => "A"
72
- # Mapper.call({ a: { b: "B" } }) # => "B"
71
+ # EnumMapper.call({ a: { b: "A" } }) # => "A"
72
+ # EnumMapper.call({ a: { b: "B" } }) # => "B"
73
73
  #
74
74
  # @example Map { people: [{ name: "John" }] } to { names: ["John"] }
75
- # class Mapper < Remap::Base
75
+ # class PeopleMapper < Remap::Base
76
76
  # define do
77
77
  # map :people, to: :names do
78
78
  # each do
@@ -82,12 +82,12 @@ module Remap
82
82
  # end
83
83
  # end
84
84
  #
85
- # Mapper.call({ people: [{ name: "John" }] }) # => { names: ["John"] }
85
+ # PeopleMapper.call({ people: [{ name: "John" }] }) # => { names: ["John"] }
86
86
  #
87
87
  # @example Map "Hello" to "Hello!"
88
88
  # class HelloMapper < Remap::Base
89
89
  # define do
90
- # map.adjust do
90
+ # map.adjust do |value|
91
91
  # "#{value}!"
92
92
  # end
93
93
  # end
@@ -105,6 +105,7 @@ module Remap
105
105
  # Mapper.call([1, 2, 3]) # => 2
106
106
  class Base < Mapper
107
107
  include ActiveSupport::Configurable
108
+ include Dry::Core::Memoizable
108
109
  include Dry::Core::Constants
109
110
  include Catchable
110
111
  extend Mapper::API
@@ -239,7 +240,7 @@ module Remap
239
240
  # @return [void]
240
241
  # rubocop:disable Layout/LineLength
241
242
  def self.define(target = Nothing, method: :new, strategy: :argument, backtrace: caller, &context)
242
- unless block_given?
243
+ unless context
243
244
  raise ArgumentError, "#{self}.define requires a block"
244
245
  end
245
246
 
@@ -305,11 +306,11 @@ module Remap
305
306
  end
306
307
  end
307
308
 
308
- s1 = catch_ignored do |id|
309
- return context.call(state.set(id: id)).then(&constructor).remove_id
309
+ s1 = catch_ignored(state) do |s0|
310
+ return context.call(s0).then(&constructor).remove_id
310
311
  end
311
312
 
312
- Failure.new(failures: s1.notices).then(&error)
313
+ error[s1.failure]
313
314
  end
314
315
 
315
316
  private
@@ -318,5 +319,7 @@ module Remap
318
319
  def validation
319
320
  Contract.call(attributes: attributes, contract: contract, options: options, rules: rules)
320
321
  end
322
+
323
+ memoize :validation
321
324
  end
322
325
  end
@@ -1,21 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
+ # @api private
4
5
  module Catchable
5
- # @yieldparam id [Symbol]
6
- # @yieldreturn [T]
6
+ using State::Extension
7
+
8
+ # @param state [State]
9
+ #
10
+ # @yieldparam state [State]
11
+ # @yieldparam id [Symbol, String]
12
+ # @yieldreturn [State<T>]
7
13
  #
8
- # @return [T]
9
- def catch_ignored(&block)
10
- catch(to_id(:ignored), &block)
14
+ # @return [State<T>]
15
+ def catch_ignored(state, &block)
16
+ id = to_id(:ignored)
17
+
18
+ catch(id) do
19
+ block[state.set(id: id), id: id].remove_id
20
+ end
11
21
  end
12
22
 
13
- # @yieldparam id [Symbol]
14
- # @yieldreturn [T]
23
+ # @param state [State]
24
+ # @param backtrace [Array<String>]
15
25
  #
16
- # @return [T]
17
- def catch_fatal(&block)
18
- catch(to_id(:fatal), &block)
26
+ # @yieldparam state [State]
27
+ # @yieldparam id [Symbol, String]
28
+ # @yieldreturn [State<T>]
29
+ #
30
+ # @return [State<T>]
31
+ # @raise [Failure::Error]
32
+ def catch_fatal(state, backtrace, &block)
33
+ id = to_id(:fatal)
34
+
35
+ failure = catch(id) do
36
+ return block[state.set(fatal_id: id), fatal_id: id].remove_fatal_id
37
+ end
38
+
39
+ raise failure.exception(backtrace)
19
40
  end
20
41
 
21
42
  private
@@ -32,7 +32,7 @@ module Remap
32
32
  #
33
33
  # @return [Rule]
34
34
  def self.call(backtrace: caller, &block)
35
- unless block_given?
35
+ unless block
36
36
  return Rule::VOID
37
37
  end
38
38
 
@@ -317,7 +317,7 @@ module Remap
317
317
  # @return [Rule::Each]]
318
318
  # @raise [ArgumentError] if no block given
319
319
  def each(backtrace: caller, &block)
320
- unless block_given?
320
+ unless block
321
321
  raise ArgumentError, "#each requires a block"
322
322
  end
323
323
 
@@ -350,7 +350,7 @@ module Remap
350
350
  # @return [Rule::Wrap]
351
351
  # @raise [ArgumentError] if type is not :array
352
352
  def wrap(type, backtrace: caller, &block)
353
- unless block_given?
353
+ unless block
354
354
  raise ArgumentError, "#wrap requires a block"
355
355
  end
356
356
 
@@ -548,13 +548,13 @@ module Remap
548
548
  output: [to].flatten,
549
549
  input: path.flatten
550
550
  },
551
+ backtrace: backtrace,
551
552
  rule: call(backtrace: backtrace, &block)
552
553
  })
553
554
  end
554
555
 
555
556
  def build_embed(s0, mapper, backtrace)
556
- f0 = catch_fatal do |fatal_id|
557
- s1 = s0.set(fatal_id: fatal_id)
557
+ catch_fatal(s0, backtrace) do |s1|
558
558
  s2 = s1.set(mapper: mapper)
559
559
  old_mapper = s0.fetch(:mapper)
560
560
 
@@ -563,8 +563,6 @@ module Remap
563
563
  s3.return!
564
564
  end.except(:scope).merge(mapper: old_mapper)
565
565
  end
566
-
567
- raise f0.exception(backtrace)
568
566
  end
569
567
  end
570
568
  end
@@ -33,15 +33,13 @@ module Remap
33
33
 
34
34
  key = path.first
35
35
 
36
- unless block_given?
36
+ unless fallback
37
37
  return get(*path, trace: trace) do
38
- raise PathError, trace + [key]
38
+ throw :ignore, trace + [key]
39
39
  end
40
40
  end
41
41
 
42
42
  fetch(key, &fallback).get(*path[1..], trace: trace + [key], &fallback)
43
- rescue TypeError
44
- raise PathError, trace + [key]
45
43
  end
46
44
  end
47
45
  end
@@ -38,9 +38,9 @@ module Remap
38
38
  def get(*path, trace: [], &fallback)
39
39
  return self if path.empty?
40
40
 
41
- unless block_given?
41
+ unless fallback
42
42
  return get(*path, trace: trace) do
43
- raise PathError, trace
43
+ throw :ignore, trace + path
44
44
  end
45
45
  end
46
46
 
data/lib/remap/failure.rb CHANGED
@@ -17,7 +17,7 @@ module Remap
17
17
  ]
18
18
  end
19
19
 
20
- failure = attributes.deep_merge(other.attributes) do |key, value1, value2|
20
+ failure = attributes.merge(other.attributes) do |key, value1, value2|
21
21
  case [key, value1, value2]
22
22
  in [:failures | :notices, Array, Array]
23
23
  value1 + value2
@@ -22,7 +22,7 @@ module Remap
22
22
  #
23
23
  # @return [Any, T]
24
24
  def call(input, backtrace: caller, **options, &error)
25
- unless block_given?
25
+ unless error
26
26
  return call(input, **options) do |failure|
27
27
  raise failure.exception(backtrace)
28
28
  end
@@ -25,7 +25,7 @@ module Remap
25
25
  #
26
26
  # @return [State]
27
27
  def call(state, &iterator)
28
- unless block_given?
28
+ unless iterator
29
29
  raise ArgumentError, "Input path requires an iterator block"
30
30
  end
31
31
 
@@ -16,29 +16,26 @@ module Remap
16
16
  #
17
17
  # @return [State]
18
18
  def call(state)
19
- s0 = state.except(:value)
19
+ init = state.except(:value)
20
20
 
21
21
  if rules.empty?
22
- return s0
22
+ return init
23
23
  end
24
24
 
25
- failure = catch_fatal do |fatal_id|
26
- s1 = s0.set(fatal_id: fatal_id)
27
- s4 = state.set(fatal_id: fatal_id)
25
+ catch_fatal(init, backtrace) do |s1, fatal_id:|
26
+ s2 = state.set(fatal_id: fatal_id)
28
27
 
29
- return catch_ignored do |id|
30
- s2 = s1.set(id: id)
28
+ catch_ignored(s1) do |s3, id:|
29
+ states = rules.map do |rule|
30
+ rule.call(s2)
31
+ end
31
32
 
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)
33
+ states.reduce(s3) do |s4, s5|
34
+ s6 = s5.set(id: id)
35
+ s4.combine(s6)
37
36
  end
38
- end.remove_id.remove_fatal_id
37
+ end
39
38
  end
40
-
41
- raise failure.exception(backtrace)
42
39
  end
43
40
  end
44
41
  end
@@ -4,13 +4,9 @@ module Remap
4
4
  class Rule
5
5
  class Map
6
6
  class Enum < Proxy
7
- include Dry::Monads[:maybe]
8
-
9
7
  # @return [Hash]
10
- option :mappings, default: -> { Hash.new { default } }
11
-
12
- # @return [Maybe]
13
- option :default, default: -> { None() }
8
+ option :table, default: -> { {} }
9
+ option :default, default: -> { Undefined }
14
10
 
15
11
  alias execute instance_eval
16
12
 
@@ -37,7 +33,7 @@ module Remap
37
33
  new.tap { _1.execute(&block) }
38
34
  end
39
35
 
40
- # Translates key into a value using predefined mappings
36
+ # Translates key into a value using predefined table
41
37
  #
42
38
  # @param key [#hash]
43
39
  #
@@ -50,24 +46,21 @@ module Remap
50
46
  return get(key) { raise Error, _1 }
51
47
  end
52
48
 
53
- self[key].bind { return _1 }.or do
54
- error["Enum key [#{key}] not found among [#{mappings.keys.inspect}]"]
49
+ table.fetch(key) do
50
+ unless default == Undefined
51
+ return default
52
+ end
53
+
54
+ error["Enum key [#{key}] not found among [#{table.keys.inspect}]"]
55
55
  end
56
56
  end
57
57
  alias call get
58
58
 
59
- # @return [Maybe]
60
- def [](key)
61
- mappings[key]
62
- end
63
-
64
59
  # @return [void]
65
60
  def from(*keys, to:)
66
- value = Some(to)
67
-
68
61
  keys.each do |key|
69
- mappings[key] = value
70
- mappings[to] = value
62
+ table[key] = to
63
+ table[to] = to
71
64
  end
72
65
  end
73
66
 
@@ -80,7 +73,7 @@ module Remap
80
73
 
81
74
  # @return [void]
82
75
  def otherwise(value)
83
- mappings.default = Some(value)
76
+ @default = value
84
77
  end
85
78
  end
86
79
  end
@@ -5,12 +5,14 @@ module Remap
5
5
  class Map
6
6
  using State::Extension
7
7
 
8
- class Optional < Concrete
8
+ # @api private
9
+ class Optional < Required
9
10
  # Represents an optional mapping rule
10
11
  # When the mapping fails, the value is ignored
11
12
  #
12
13
  # @example Map [:name] to [:nickname]
13
14
  # map = Map::Optional.call({
15
+ # backtrace: caller,
14
16
  # path: {
15
17
  # input: [:name],
16
18
  # output: [:nickname]
@@ -22,16 +24,14 @@ module Remap
22
24
  # })
23
25
  #
24
26
  # output = map.call(state) do |failure|
25
- # raise failure.exeception
27
+ # raise failure.exception(caller)
26
28
  # end
27
29
  #
28
30
  # output.fetch(:value) # => { nickname: "John" }
29
31
  #
30
- # @param state [State]
31
- #
32
- # @return [State]
32
+ # @see Map#call
33
33
  def call(state)
34
- catch { super(state.set(id: _1)).except(:id) }
34
+ catch_ignored(state) { super(_1) }
35
35
  end
36
36
  end
37
37
  end
@@ -8,7 +8,19 @@ module Remap
8
8
  class Required < Concrete
9
9
  attribute :backtrace, Types::Backtrace
10
10
 
11
- # TODO: Remove
11
+ # @see Map#call
12
+ def call(state)
13
+ catch_fatal(state, backtrace) do |s0|
14
+ s2 = path.input.call(s0) do |s1|
15
+ callback(rule.call(s1))
16
+ end
17
+
18
+ s3 = s2.then(&path.output)
19
+ s4 = s3.set(path: state.path)
20
+
21
+ s4.except(:key)
22
+ end
23
+ end
12
24
  end
13
25
  end
14
26
  end
@@ -43,29 +43,14 @@ module Remap
43
43
  #
44
44
  # @abstract
45
45
  def call(state)
46
- failure = catch_fatal do |fatal_id|
47
- s0 = state.set(fatal_id: fatal_id)
48
-
49
- s2 = path.input.call(s0) do |s1|
50
- s2 = rule.call(s1)
51
- callback(s2)
52
- end
53
-
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
60
-
61
- raise failure.exception(backtrace)
46
+ raise NotImplementedError, "#{self.class}#call not implemented"
62
47
  end
63
48
 
64
49
  # A post-processor method
65
50
  #
66
51
  # @example Up-case mapped value
67
52
  # state = Remap::State.call("Hello World")
68
- # map = Remap::Rule::Map.call({})
53
+ # map = Remap::Rule::Map.call(backtrace: caller)
69
54
  # upcase = map.adjust(&:upcase)
70
55
  # error = -> failure { raise failure.exception }
71
56
  # upcase.call(state, &error).fetch(:value) # => "HELLO WORLD"
@@ -84,7 +69,7 @@ module Remap
84
69
  #
85
70
  # @example Pending mapping
86
71
  # state = Remap::State.call(:value)
87
- # map = Remap::Rule::Map.call({})
72
+ # map = Remap::Rule::Map::Optional.call(backtrace: caller)
88
73
  # pending = map.pending("this is pending")
89
74
  # error = -> failure { raise failure.exception }
90
75
  # pending.call(state, &error).key?(:value) # => false
@@ -99,7 +84,7 @@ module Remap
99
84
  # An enumeration processor
100
85
  #
101
86
  # @example A mapped enum
102
- # enum = Remap::Rule::Map.call({}).enum do
87
+ # enum = Remap::Rule::Map.call(backtrace: caller).enum do
103
88
  # value "A", "B"
104
89
  # otherwise "C"
105
90
  # end
@@ -132,7 +117,7 @@ module Remap
132
117
  # Keeps map, only if block is true
133
118
  #
134
119
  # @example Keep if value contains "A"
135
- # map = Remap::Rule::Map.call({}).if do
120
+ # map = Remap::Rule::Map::Optional.call(backtrace: caller).if do |value|
136
121
  # value.include?("A")
137
122
  # end
138
123
  #
@@ -160,11 +145,11 @@ module Remap
160
145
  #
161
146
 
162
147
  # @example Keep unless value contains "A"
163
- # map = Remap::Rule::Map.call({}).if_not do
148
+ # map = Remap::Rule::Map::Optional.new(backtrace: caller).if_not do |value|
164
149
  # value.include?("A")
165
150
  # end
166
151
  #
167
- # error = -> failure { raise failure.exception }
152
+ # error = -> failure { raise failure.exception(caller) }
168
153
  #
169
154
  # a = Remap::State.call("A")
170
155
  # map.call(a, &error).key?(:value) # => false
@@ -26,7 +26,7 @@ module Remap
26
26
  #
27
27
  # @return [State<U>]
28
28
  def call(outer_state, &block)
29
- unless block_given?
29
+ unless block
30
30
  raise ArgumentError, "All selector requires an iteration block"
31
31
  end
32
32
 
@@ -31,7 +31,7 @@ module Remap
31
31
  #
32
32
  # @return [State<U>]
33
33
  def call(state, &block)
34
- unless block_given?
34
+ unless block
35
35
  raise ArgumentError, "The index selector requires an iteration block"
36
36
  end
37
37
 
@@ -28,7 +28,7 @@ module Remap
28
28
  #
29
29
  # @return [State<U>]
30
30
  def call(state, &block)
31
- unless block_given?
31
+ unless block
32
32
  raise ArgumentError, "The key selector requires an iteration block"
33
33
  end
34
34
 
@@ -46,14 +46,10 @@ module Remap
46
46
  # @returns [Hash] a hash containing the given path
47
47
  # @raise Europace::Error when path doesn't exist
48
48
  def only(*path)
49
- path.reduce(EMPTY_HASH) do |hash, key|
50
- next hash unless key?(key)
51
-
52
- hash.deep_merge(key => fetch(key))
53
- end
49
+ dup.extract!(*path)
54
50
  end
55
51
 
56
- # Throws :fatal containing a Notice
52
+ # @see #notice
57
53
  def fatal!(...)
58
54
  fatal_id = fetch(:fatal_id) do
59
55
  raise ArgumentError, "Missing :fatal_id in %s" % formatted
@@ -62,12 +58,7 @@ module Remap
62
58
  throw fatal_id, Failure.new(failures: [notice(...)], notices: notices)
63
59
  end
64
60
 
65
- # Throws :warn containing a Notice
66
- def notice!(...)
67
- raise NotImplementedError, "Not implemented"
68
- end
69
-
70
- # Throws :ignore containing a Notice
61
+ # @see #notice
71
62
  def ignore!(...)
72
63
  set(notice: notice(...)).except(:value).return!
73
64
  end
@@ -117,11 +108,30 @@ module Remap
117
108
  #
118
109
  # @return [State]
119
110
  def map(&block)
120
- bind do |value, state|
121
- Iteration.call(state: state, value: value).call do |other, **options|
122
- state.set(other, **options).then(&block)
123
- end.except(:index, :element, :key)
111
+ result = case self
112
+ in { value: Array => array }
113
+ array.each_with_index.each_with_object([]) do |(value, index), array|
114
+ s1 = block[set(value, index: index)]
115
+
116
+ if s1.key?(:value)
117
+ array << s1[:value]
118
+ end
119
+ end
120
+ in { value: Hash => hash }
121
+ hash.each_with_object({}) do |(key, value), acc|
122
+ s1 = block[set(value, key: key)]
123
+
124
+ if s1.key?(:value)
125
+ acc[key] = s1[:value]
126
+ end
127
+ end
128
+ in { value: }
129
+ fatal!("Expected an enumerable got %s", value.class)
130
+ else
131
+ return self
124
132
  end
133
+
134
+ set(result)
125
135
  end
126
136
 
127
137
  # @return [String]
@@ -135,10 +145,14 @@ module Remap
135
145
  #
136
146
  # @return [State]
137
147
  def combine(other)
138
- deep_merge(other) do |key, value1, value2|
148
+ merge(other) do |key, value1, value2|
139
149
  case [key, value1, value2]
140
- in [:value, Array => list1, Array => list2]
141
- list1 + list2
150
+ in [_, Hash => left, Hash => right]
151
+ left.merge(right)
152
+ in [:ids | :fatal_ids, _, right]
153
+ right
154
+ in [_, Array => left, Array => right]
155
+ left + right
142
156
  in [:value, left, right]
143
157
  other.fatal!(
144
158
  "Could not merge [%s] (%s) with [%s] (%s)",
@@ -147,15 +161,6 @@ module Remap
147
161
  right.formatted,
148
162
  right.class
149
163
  )
150
- in [:notices, Array => n1, Array => n2]
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)
159
164
  in [Symbol, _, value]
160
165
  value
161
166
  end
@@ -165,31 +170,39 @@ module Remap
165
170
  # @todo Merge with {#remove_fatal_id}
166
171
  # @return [State]
167
172
  def remove_id
168
- case self
173
+ state = dup
174
+
175
+ case state
169
176
  in { ids: [], id: }
170
- except(:id)
177
+ state.except!(:id)
171
178
  in { ids:, id: }
172
- merge(ids: ids[1...], id: ids[0])
179
+ state.merge!(ids: ids[1...], id: ids[0])
173
180
  in { ids: [] }
174
- self
181
+ state
175
182
  in { ids: }
176
183
  raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
177
- end._
184
+ end
185
+
186
+ state
178
187
  end
179
188
 
180
189
  # @todo Merge with {#remove_id}
181
190
  # @return [State]
182
191
  def remove_fatal_id
183
- case self
192
+ state = dup
193
+
194
+ case state
184
195
  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])
196
+ state.except!(:fatal_id)
197
+ in { fatal_ids: ids, fatal_id: }
198
+ state.merge!(fatal_ids: ids[1...], fatal_id: ids[0])
188
199
  in { fatal_ids: [] }
189
- self
200
+ state
190
201
  in { fatal_ids: }
191
202
  raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
192
- end._
203
+ end
204
+
205
+ state
193
206
  end
194
207
 
195
208
  # Creates a new state with params
@@ -203,24 +216,30 @@ module Remap
203
216
  return set(**options, value: value)
204
217
  end
205
218
 
206
- case [self, options]
207
- in [{notices:}, {notice: notice, **rest}]
208
- merge(notices: notices + [notice]).set(**rest)
209
- in [{value:}, {mapper:, **rest}]
210
- merge(scope: value, mapper: mapper).set(**rest)
211
- in [{path:}, {key:, **rest}]
212
- merge(path: path + [key], key: key).set(**rest)
213
- in [{path:}, {index:, value:, **rest}]
214
- merge(path: path + [index], element: value, index: index, value: value).set(**rest)
215
- in [{path:}, {index:, **rest}]
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)
219
+ state = dup
220
+
221
+ case [state, options]
222
+ in [{notices:}, {notice: notice}]
223
+ state.merge!(notices: notices + [notice])
224
+ in [{value:}, {mapper:}]
225
+ state.merge!(scope: value, mapper: mapper)
226
+ in [{path:}, {key:, value:}]
227
+ state.merge!(path: path + [key], key: key, value: value)
228
+ in [{path:}, {key:}]
229
+ state.merge!(path: path + [key], key: key)
230
+ in [{path:}, {index:, value:}]
231
+ state.merge!(path: path + [index], element: value, index: index, value: value)
232
+ in [{path:}, {index:}]
233
+ state.merge!(path: path + [index], index: index)
234
+ in [{ids:, id: old_id}, {id: new_id}]
235
+ state.merge!(ids: [old_id] + ids, id: new_id)
236
+ in [{fatal_ids:, fatal_id: old_id}, {fatal_id: new_id}]
237
+ state.merge!(fatal_ids: [old_id] + fatal_ids, fatal_id: new_id)
221
238
  else
222
- merge(options)
239
+ state.merge!(options)
223
240
  end
241
+
242
+ state
224
243
  end
225
244
 
226
245
  # Passes {#value} to block, if defined
@@ -240,22 +259,6 @@ module Remap
240
259
  end
241
260
  end
242
261
 
243
- # Creates a failure to be used in {Remap::Base} & {Remap::Mapper}
244
- #
245
- # @param reason [#to_s]
246
- #
247
- # @see State::Schema
248
- #
249
- # @return [Failure]
250
-
251
- # class Failure < Dry::Interface
252
- # attribute :notices, [Notice], min_size: 1
253
- # end
254
-
255
- def failure(reason = Undefined)
256
- raise NotImplementedError, "Not implemented"
257
- end
258
-
259
262
  # Passes {#value} to block, if defined
260
263
  # {options} are combine into the final state
261
264
  #
@@ -267,7 +270,7 @@ module Remap
267
270
  #
268
271
  # @return [Y]
269
272
  def bind(**options, &block)
270
- unless block_given?
273
+ unless block
271
274
  raise ArgumentError, "State#bind requires a block"
272
275
  end
273
276
 
@@ -287,19 +290,37 @@ module Remap
287
290
  # @return [State<U>]
288
291
  def execute(&block)
289
292
  bind do |value|
290
- result = context(value).instance_exec(value, &block)
293
+ result = catch :done do
294
+ tail_path = catch :ignore do
295
+ names = block.parameters.reduce([]) do |acc, (type, name)|
296
+ case type
297
+ in :keyreq
298
+ acc + [name]
299
+ else
300
+ acc
301
+ end
302
+ end
303
+
304
+ r = Proc.new(&block).call(value, **only(*names), **options.only(*names)) do |reason|
305
+ ignore!(reason)
306
+ end
307
+ throw :done, r
308
+ rescue NameError => e
309
+ fatal!(e.message)
310
+ rescue KeyError => e
311
+ [e.key]
312
+ rescue IndexError
313
+ []
314
+ end
315
+
316
+ set(path: path + tail_path).ignore!("Undefined path")
317
+ end
291
318
 
292
319
  if result.equal?(Dry::Core::Constants::Undefined)
293
320
  ignore!("Undefined returned, skipping!")
294
321
  end
295
322
 
296
323
  set(result)
297
- rescue KeyError => e
298
- set(path: path + [e.key]).ignore!(e.message)
299
- rescue IndexError => e
300
- ignore!(e.message)
301
- rescue PathError => e
302
- set(path: path + e.path).ignore!("Undefined path")
303
324
  end
304
325
  end
305
326
 
@@ -385,6 +406,8 @@ module Remap
385
406
  # @return [Failure]
386
407
  def failure(reason = Undefined)
387
408
  failures = case [path, reason]
409
+ in [_, Undefined]
410
+ return Failure.new(failures: notices)
388
411
  in [_, Notice => notice]
389
412
  [notice]
390
413
  in [path, Array => reasons]
@@ -419,27 +442,6 @@ module Remap
419
442
 
420
443
  throw id, remove_id
421
444
  end
422
-
423
- private
424
-
425
- # Creates a context containing {options} and {self}
426
- #
427
- # @param value [Any]
428
- #
429
- # @yieldparam reason [T]
430
- #
431
- # @return [Struct]
432
- def context(value, context: self)
433
- ::Struct.new(*except(:id).keys, *options.keys, :state, keyword_init: true) do
434
- define_method :method_missing do |name, *|
435
- context.fatal!("Method [%s] not defined", name)
436
- end
437
-
438
- define_method(:skip!) do |message = "Manual skip!"|
439
- context.ignore!(message)
440
- end
441
- end.new(**to_hash, **options, value: value, state: self)
442
- end
443
445
  end
444
446
  end
445
447
  end
@@ -9,8 +9,12 @@ 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
+
13
+ required(:ids).array(Types::ID)
14
+ optional(:id).filled(Types::ID)
15
+
16
+ required(:fatal_ids).array(Types::ID)
17
+ optional(:fatal_id).filled(Types::ID)
14
18
 
15
19
  optional(:index).filled(:integer)
16
20
  optional(:element).filled
data/lib/remap/state.rb CHANGED
@@ -31,11 +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,
35
- notices: EMPTY_ARRAY,
36
- path: EMPTY_ARRAY,
34
+ fatal_ids: [],
35
+ notices: [],
36
+ path: [],
37
37
  options: options,
38
- ids: EMPTY_ARRAY,
38
+ ids: [],
39
39
  mapper: mapper,
40
40
  values: value,
41
41
  value: value,
data/lib/remap/types.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/monads/maybe"
4
3
  require "dry/logic/operations/negation"
5
4
  require "dry/logic"
6
5
 
@@ -18,6 +17,7 @@ module Remap
18
17
  Rule = Interface(:call) | Instance(Proc)
19
18
  Key = Interface(:hash)
20
19
  Notice = Instance(Remap::Notice)
20
+ ID = String | Symbol
21
21
 
22
22
  # Validates a state according to State::Schema
23
23
  State = Hash.constructor do |input, type, &error|
data/lib/remap.rb CHANGED
@@ -5,11 +5,11 @@ require "active_support/core_ext/enumerable"
5
5
  require "active_support/core_ext/array/wrap"
6
6
  require "active_support/proxy_object"
7
7
 
8
+ require "dry/core/memoizable"
8
9
  require "dry/validation"
9
10
  require "dry/interface"
10
11
  require "dry/schema"
11
12
  require "dry/struct"
12
- require "dry/monads"
13
13
  require "dry/types"
14
14
 
15
15
  require "neatjson"
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.2.44
4
+ version: 2.2.48
5
5
  platform: ruby
6
6
  authors:
7
7
  - Linus Oleander
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.0.3
69
- - !ruby/object:Gem::Dependency
70
- name: dry-monads
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 1.4.0
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 1.4.0
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: dry-schema
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -264,10 +250,6 @@ files:
264
250
  - lib/remap/extensions/object.rb
265
251
  - lib/remap/failure.rb
266
252
  - lib/remap/failure/error.rb
267
- - lib/remap/iteration.rb
268
- - lib/remap/iteration/array.rb
269
- - lib/remap/iteration/hash.rb
270
- - lib/remap/iteration/other.rb
271
253
  - lib/remap/mapper.rb
272
254
  - lib/remap/mapper/and.rb
273
255
  - lib/remap/mapper/binary.rb
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Iteration
5
- using State::Extension
6
-
7
- # Implements an array iterator which defines index in state
8
- class Array < Concrete
9
- # @return [Array<T>]
10
- attribute :value, Types::Array, alias: :array
11
-
12
- # @return [State<Array<T>>]
13
- attribute :state, Types::State
14
-
15
- # @see Iteration#map
16
- def call(&block)
17
- array.each_with_index.reduce(init) do |state, (value, index)|
18
- reduce(state, value, index, &block)
19
- end
20
- end
21
-
22
- private
23
-
24
- def init
25
- state.set(EMPTY_ARRAY)
26
- end
27
-
28
- def reduce(state, value, index, &block)
29
- s0 = block[value, index: index]
30
- s1 = s0.set(**state.only(:ids, :fatal_id))
31
- state.combine(s1.fmap { [_1] })
32
- end
33
- end
34
- end
35
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Iteration
5
- using State::Extension
6
-
7
- # Implements a hash iterator which defines key in state
8
- class Hash < Concrete
9
- # @return [Hash]
10
- attribute :value, Types::Hash, alias: :hash
11
-
12
- # @return [State<Hash>]
13
- attribute :state, Types::State
14
-
15
- # @see Iteration#map
16
- def call(&block)
17
- hash.reduce(init) do |state, (key, value)|
18
- reduce(state, key, value, &block)
19
- end
20
- end
21
-
22
- private
23
-
24
- def reduce(state, key, value, &block)
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 } })
28
- end
29
-
30
- def init
31
- state.set(EMPTY_HASH)
32
- end
33
- end
34
- end
35
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Iteration
5
- using State::Extension
6
-
7
- # Default iterator which doesn't do anything
8
- class Other < Concrete
9
- attribute :value, Types::Any, alias: :other
10
- attribute :state, Types::State
11
-
12
- # @see Iteration#map
13
- def call(&block)
14
- state.fatal!("Expected an enumerable")
15
- end
16
- end
17
- end
18
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- class Iteration < Dry::Interface
5
- # @return [State<T>]
6
- attribute :state, Types::State
7
-
8
- # @return [T]
9
- attribute :value, Types::Any
10
-
11
- # Maps every element in {#value}
12
- #
13
- # @abstract
14
- #
15
- # @yieldparam element [V]
16
- # @yieldparam key [K, Integer]
17
- # @yieldreturn [Array<V>, Hash<V, K>]
18
- #
19
- # @return [Array<V>, Hash<V, K>]
20
- def call(state)
21
- raise NotImplementedError, "#{self.class}#call not implemented"
22
- end
23
-
24
- order :Hash, :Array, :Other
25
- end
26
- end