remap 2.0.3 → 2.1.0

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/remap/base.rb +229 -75
  3. data/lib/remap/compiler.rb +127 -37
  4. data/lib/remap/constructor/argument.rb +20 -6
  5. data/lib/remap/constructor/keyword.rb +20 -4
  6. data/lib/remap/constructor/none.rb +3 -4
  7. data/lib/remap/constructor.rb +12 -5
  8. data/lib/remap/contract.rb +27 -0
  9. data/lib/remap/extensions/enumerable.rb +48 -0
  10. data/lib/remap/extensions/hash.rb +13 -0
  11. data/lib/remap/extensions/object.rb +37 -0
  12. data/lib/remap/failure.rb +25 -15
  13. data/lib/remap/iteration/array.rb +20 -11
  14. data/lib/remap/iteration/hash.rb +21 -13
  15. data/lib/remap/iteration/other.rb +7 -7
  16. data/lib/remap/iteration.rb +8 -2
  17. data/lib/remap/mapper/and.rb +29 -7
  18. data/lib/remap/mapper/binary.rb +3 -6
  19. data/lib/remap/mapper/or.rb +29 -6
  20. data/lib/remap/mapper/support/operations.rb +40 -0
  21. data/lib/remap/mapper/xor.rb +29 -7
  22. data/lib/remap/mapper.rb +1 -48
  23. data/lib/remap/notice/traced.rb +19 -0
  24. data/lib/remap/notice/untraced.rb +11 -0
  25. data/lib/remap/notice.rb +34 -0
  26. data/lib/remap/operation.rb +26 -13
  27. data/lib/remap/path/input.rb +37 -0
  28. data/lib/remap/path/output.rb +26 -0
  29. data/lib/remap/path.rb +22 -0
  30. data/lib/remap/path_error.rb +13 -0
  31. data/lib/remap/proxy.rb +18 -0
  32. data/lib/remap/rule/each.rb +25 -24
  33. data/lib/remap/rule/embed.rb +33 -28
  34. data/lib/remap/rule/map/optional.rb +42 -0
  35. data/lib/remap/rule/map/required.rb +35 -0
  36. data/lib/remap/rule/map.rb +176 -55
  37. data/lib/remap/rule/set.rb +23 -33
  38. data/lib/remap/rule/support/collection/empty.rb +7 -7
  39. data/lib/remap/rule/support/collection/filled.rb +21 -8
  40. data/lib/remap/rule/support/collection.rb +11 -3
  41. data/lib/remap/rule/support/enum.rb +44 -21
  42. data/lib/remap/rule/void.rb +17 -18
  43. data/lib/remap/rule/wrap.rb +25 -17
  44. data/lib/remap/rule.rb +8 -1
  45. data/lib/remap/selector/all.rb +29 -7
  46. data/lib/remap/selector/index.rb +24 -16
  47. data/lib/remap/selector/key.rb +31 -16
  48. data/lib/remap/selector.rb +17 -0
  49. data/lib/remap/state/extension.rb +182 -208
  50. data/lib/remap/state/schema.rb +1 -1
  51. data/lib/remap/state.rb +30 -4
  52. data/lib/remap/static/fixed.rb +14 -3
  53. data/lib/remap/static/option.rb +21 -6
  54. data/lib/remap/static.rb +13 -0
  55. data/lib/remap/struct.rb +1 -0
  56. data/lib/remap/types.rb +13 -28
  57. data/lib/remap.rb +15 -19
  58. metadata +91 -89
  59. data/lib/remap/result.rb +0 -11
  60. data/lib/remap/rule/support/path.rb +0 -45
  61. data/lib/remap/state/types.rb +0 -11
  62. data/lib/remap/success.rb +0 -29
  63. data/lib/remap/version.rb +0 -5
@@ -1,52 +1,101 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/deep_transform_values"
4
-
5
3
  module Remap
6
4
  module State
7
5
  module Extension
8
- refine Object do
9
- def _(&block)
10
- unless block
11
- return _ { raise _1 }
12
- end
13
-
14
- block["Expected a state, got [#{self}] (#{self.class})"]
15
- end
6
+ using Extensions::Enumerable
7
+ using Extensions::Object
8
+ using Extensions::Hash
16
9
 
10
+ refine Object do
11
+ # @see Extension::Paths::Hash
17
12
  def paths
18
13
  EMPTY_ARRAY
19
14
  end
20
-
21
- def get(*path)
22
- throw :missing, path
23
- end
24
15
  end
25
16
 
26
- refine Array do
27
- def hide(value)
28
- reverse.reduce(value) do |element, key|
29
- { key => element }
17
+ refine Hash do
18
+ # Returns a list of all key paths
19
+ #
20
+ # @example Get paths
21
+ # {
22
+ # a: {
23
+ # b: :c
24
+ # },
25
+ # d: :e
26
+ # }.hur_paths # => [[:a, :b], [:d]]
27
+ #
28
+ # @return [Array<Array<Symbol>>] a list of key paths
29
+ def paths
30
+ reduce(EMPTY_ARRAY) do |acc, (path, leaves)|
31
+ if (paths = leaves.paths).empty?
32
+ next acc + [[path]]
33
+ end
34
+
35
+ acc + paths.map { |inner| [path] + inner }
30
36
  end
31
37
  end
32
38
 
33
- def get(*path, last)
34
- if path.empty?
35
- return fetch(last) do
36
- throw :missing, path + [last]
37
- end
38
- end
39
+ # Restrict hash to passed key path
40
+ #
41
+ # @param key [Symbol] to be kept
42
+ # @param rest [Array<Symbol>] of the key path
43
+ #
44
+ # @example Select key path
45
+ # {
46
+ # a: {
47
+ # b: :c
48
+ # },
49
+ # d: :e
50
+ # }.hur_only(:a, :b) # => { a: { b: :c } }
51
+ #
52
+ # @returns [Hash] a hash containing the given path
53
+ # @raise Europace::Error when path doesn't exist
54
+ def only(*path)
55
+ path.reduce(EMPTY_HASH) do |hash, key|
56
+ next hash unless key?(key)
39
57
 
40
- dig(*path).fetch(last) do
41
- throw :missing, path + [last]
58
+ hash.deep_merge(key => fetch(key))
42
59
  end
43
60
  end
44
- end
45
61
 
46
- refine Hash do
62
+ # Throws :fatal containing a Notice
63
+ def fatal!(...)
64
+ throw :fatal, notice(...)
65
+ end
66
+
67
+ # Throws :warn containing a Notice
68
+ def notice!(...)
69
+ throw :notice, notice(...)
70
+ end
71
+
72
+ # Throws :ignore containing a Notice
73
+ def ignore!(...)
74
+ throw :ignore, notice(...)
75
+ end
76
+
77
+ # Creates a notice containing the given message
78
+ #
79
+ # @param template [String]
80
+ # @param values [Array]
81
+ #
82
+ # @return [Notice]
83
+ def notice(template, *values)
84
+ Notice.call(only(:value, :path).merge(reason: template % values))
85
+ end
86
+
87
+ # Validates {self} against {Schema}
88
+ #
89
+ # Only used during development
90
+ #
91
+ # @yield [Hash] if schema fails
92
+ #
93
+ # @raise if schema fails and no block is given
94
+ #
95
+ # @return [self]
47
96
  def _(&block)
48
97
  unless block
49
- return _ { raise "Input: #{self} output: #{JSON.pretty_generate(_1)}" }
98
+ return _ { raise ArgumentError, "Input: #{self} output: #{_1.formated}" }
50
99
  end
51
100
 
52
101
  unless (result = Schema.call(self)).success?
@@ -56,22 +105,6 @@ module Remap
56
105
  self
57
106
  end
58
107
 
59
- def paths
60
- reduce(EMPTY_ARRAY) do |acc, (path, leaves)|
61
- if (paths = leaves.paths).empty?
62
- next acc + [[path]]
63
- end
64
-
65
- acc + paths.map { |inner| [path] + inner }
66
- end
67
- end
68
-
69
- def paths_pair
70
- paths.map do |path|
71
- [dig(*path), path]
72
- end
73
- end
74
-
75
108
  # Makes the state iterable
76
109
  #
77
110
  # @yieldparam value [Any]
@@ -84,16 +117,18 @@ module Remap
84
117
  def map(&block)
85
118
  bind do |value, state|
86
119
  Iteration.call(state: state, value: value).call do |other, **options|
87
- state.set(other, **options)._.then(&block)._
88
- end
120
+ state.set(other, **options).then do |inner_state|
121
+ block[inner_state] do |failure|
122
+ throw :failure, failure
123
+ end
124
+ end
125
+ end.except(:index, :element, :key)
89
126
  end
90
127
  end
91
128
 
92
129
  # @return [String]
93
130
  def inspect
94
- reject { _2.blank? }.then do |cleaned|
95
- format("#<State %<json>s>", json: JSON.pretty_generate(cleaned))
96
- end
131
+ "#<State %s>" % compact_blank.formated
97
132
  end
98
133
 
99
134
  # Merges {self} with {other} and returns a new state
@@ -101,102 +136,31 @@ module Remap
101
136
  # @param other [State]
102
137
  #
103
138
  # @return [State]
104
- def merged(other)
105
- all_problems = problems.deep_merge(other.problems) do |key, a, b|
106
- case [a, b]
107
- in [Array, Array]
108
- a + b
109
- else
110
- raise ArgumentError, "Can't merge #{a.inspect} with #{b.inspect} @ #{key}"
111
- end
112
- end
113
-
114
- catch :undefined do
115
- value = recursive_merge(other) do |reason|
116
- return merge(problems: all_problems).problem(reason)
117
- end
118
-
119
- return set(value, problems: all_problems)
120
- end
121
-
122
- set(problems: all_problems)
123
- end
124
-
125
- # Resolves conflicts unsovable by ActiveSupport#deep_merge
126
- #
127
- # @param key [Symbol] the key that cannot be merged
128
- # @param left [Any] the left value that cannot be merged
129
- # @param right [Any] the right value that cannot be merged
130
- #
131
- # @yieldparam reason [String] if {left} and {right} cannot be merged
132
- # @yieldreturn [State]
133
- #
134
- # @return [Any]
135
- def conflicts(key, left, right, &error)
136
- case [left, right]
137
- in [Array, Array]
138
- left + right
139
- in [value, ^value]
140
- value
141
- in [left, right]
142
- reason(left, right) do |reason|
143
- [reason, "[#{key}]"].join(" @ ").then(&error)
139
+ def combine(other)
140
+ deep_merge(other) do |key, value1, value2|
141
+ case [key, value1, value2]
142
+ in [:value, Array => list1, Array => list2]
143
+ list1 + list2
144
+ in [:value, left, right]
145
+ fatal!(
146
+ "Could not merge [%p] (%s) with [%p] (%s) @ %s",
147
+ left,
148
+ left.class,
149
+ right,
150
+ right.class,
151
+ (path + [key]).join("."))
152
+ in [:notices, Array => n1, Array => n2]
153
+ n1 + n2
154
+ in [Symbol, _, value]
155
+ value
144
156
  end
145
157
  end
146
158
  end
147
159
 
148
- def get(*path, last)
149
- if path.empty?
150
- return fetch(last) do
151
- throw :missing, path + [last]
152
- end
153
- end
154
-
155
- dig(*path).fetch(last) do
156
- throw :missing, path + [last]
157
- end
158
- end
159
-
160
- # Recursively merges {self} with {other}
161
- # Invokes {error} when a conflict is detected
162
- #
163
- # @param other [State]
164
- #
165
- # @yieldparam key [Symbol]
166
- # @yieldparam left [Any]
167
- # @yieldparam right [Any]
168
- # @yieldparam error [Proc]
169
- #
170
- # @yieldreturn [Any]
171
- #
172
- # @return [Any] Merge result (not a state)
173
- def recursive_merge(other, &error)
174
- case [self, other]
175
- in [{value: Hash => left}, {value: Hash => right}]
176
- left.deep_merge(right) { |*args| conflicts(*args, &error) }
177
- in [{value: Array => left}, {value: Array => right}]
178
- left + right
179
- in [{value: left}, {value: right}]
180
- reason(left, right, &error)
181
- in [{value: left}, _]
182
- left
183
- in [_, {value: right}]
184
- right
185
- in [_, _]
186
- throw :undefined
187
- end
188
- end
189
-
190
- def reason(left, right, &error)
191
- params = { left: left, cleft: left.class, right: right, cright: right.class }
192
- message = format("Could not merge [%<left>p] (%<cleft>s) with [%<right>p] (%<cright>s)", params)
193
- error[message]
194
- end
195
-
196
160
  # Creates a new state with params
197
161
  #
198
162
  # @param value [Any, Undefined] Used as {value:}
199
- # @options [Hash] To be merged into {self}
163
+ # @options [Hash] To be combine into {self}
200
164
  #
201
165
  # @return [State]
202
166
  def set(value = Undefined, **options)
@@ -205,10 +169,8 @@ module Remap
205
169
  end
206
170
 
207
171
  case [self, options]
208
- in [{path:}, {quantifier:, **rest}]
209
- merge(path: path + [quantifier]).set(**rest)
210
- in [_, {mapper:, value:, **rest}]
211
- merge(scope: value, value: value, mapper: mapper).set(**rest)
172
+ in [{notices:}, {notice: notice, **rest}]
173
+ merge(notices: notices + [notice]).set(**rest)
212
174
  in [{value:}, {mapper:, **rest}]
213
175
  merge(scope: value, mapper: mapper).set(**rest)
214
176
  in [{path:}, {key:, **rest}]
@@ -224,7 +186,7 @@ module Remap
224
186
 
225
187
  # Passes {#value} to block, if defined
226
188
  # The return value is then wrapped in a state
227
- # and returned with {options} merged into the state
189
+ # and returned with {options} combine into the state
228
190
  #
229
191
  # @yieldparam value [T]
230
192
  # @yieldparam self [State]
@@ -239,48 +201,44 @@ module Remap
239
201
  end
240
202
  end
241
203
 
242
- def failure(reason)
243
- explaination(reason)
244
- end
245
-
246
- def explaination(reason, explainations = EMPTY_HASH)
247
- Remap::Types::Report::Self[explainations]
248
-
249
- report = ->(message) { [{}.merge(reason: message)] }
250
-
251
- explaination = case [self, reason]
252
- in [{path: []}, String]
253
- { base: report[reason] }
254
- in [{path:}, String]
255
- path.hide(report[reason])
256
- in [{path:}, Hash]
257
- reason.paths_pair.reduce(EMPTY_HASH) do |acc, (item, suffix)|
258
- Array.wrap(item).map { (path + suffix).hide(report[_1]) }.reduce(acc, &:deep_merge)
204
+ # Creates a failure to be used in {Remap::Base} & {Remap::Mapper}
205
+ #
206
+ # @param reason [#to_s]
207
+ #
208
+ # @see State::Schema
209
+ #
210
+ # @return [Failure]
211
+
212
+ # class Failure < Dry::Interface
213
+ # attribute :notices, [Notice], min_size: 1
214
+ # end
215
+
216
+ def failure(reason = Undefined)
217
+ failures = case [path, reason]
218
+ in [_, Notice => notice]
219
+ [notice]
220
+ in [path, Array => reasons]
221
+ reasons.map do |inner_reason|
222
+ Notice.call(path: path, reason: inner_reason, **only(:value))
259
223
  end
260
- end
261
-
262
- output = explainations.deep_merge(explaination) do |key, left, right|
263
- case [left, right]
264
- in [Array, Array]
265
- left + right
266
- else
267
- raise ArgumentError, "Cannot merge #{left} with #{right} @ #{key}"
224
+ in [path, String => reason]
225
+ [Notice.call(path: path, reason: reason, **only(:value))]
226
+ in [path, Hash => errors]
227
+ errors.paths.flat_map do |sufix|
228
+ Array.wrap(errors.dig(*sufix)).map do |inner_reason|
229
+ Notice.call(
230
+ reason: inner_reason,
231
+ path: path + sufix,
232
+ **only(:value))
233
+ end
268
234
  end
269
235
  end
270
236
 
271
- # Remap::Types::Report::Self[output] do
272
- # binding.pry
273
- # end
274
- end
275
-
276
- def no_of_problems
277
- a = problems.except(:base).paths.count
278
- b = problems.fetch(:base, []).count
279
- a + b
237
+ Failure.new(failures: failures, notices: notices)
280
238
  end
281
239
 
282
240
  # Passes {#value} to block, if defined
283
- # {options} are merged into the final state
241
+ # {options} are combine into the final state
284
242
  #
285
243
  # @yieldparam value [T]
286
244
  # @yieldparam self [State]
@@ -290,13 +248,13 @@ module Remap
290
248
  #
291
249
  # @return [Y]
292
250
  def bind(**options, &block)
293
- unless block
294
- raise ArgumentError, "no block given"
251
+ unless block_given?
252
+ raise ArgumentError, "State#bind requires a block"
295
253
  end
296
254
 
297
255
  fetch(:value) { return self }.then do |value|
298
256
  block[value, self] do |reason, **other|
299
- return set(**options, **other).problem(reason)._
257
+ return set(**options, **other).notice!(reason)
300
258
  end
301
259
  end
302
260
  end
@@ -304,25 +262,27 @@ module Remap
304
262
  # Execute {block} in the current context
305
263
  # Only calls {block} if {#value} is defined
306
264
  #
307
- # @yieldparam value [T]
265
+ # @yieldparam [T]
308
266
  # @yieldreturn [U]
309
267
  #
310
268
  # @return [State<U>]
311
269
  def execute(&block)
312
270
  bind do |value, &error|
313
- catch :success do
314
- path = catch :missing do
315
- throw :success, set(context(value, &error).instance_exec(value, &block))._
316
- end
271
+ result = context(value, &error).instance_exec(value, &block)
317
272
 
318
- return error["Could not fetch value at", path: path]
273
+ if result.equal?(Dry::Core::Constants::Undefined)
274
+ return error["Undefined returned, skipping!"]
319
275
  end
276
+
277
+ set(result)._
320
278
  rescue NoMethodError => e
321
279
  e.name == :fetch ? error["Fetch not defined on value: #{e}"] : raise
322
280
  rescue NameError => e
323
281
  e.name == :Undefined ? error["Undefined returned, skipping!: #{e}"] : raise
324
282
  rescue KeyError, IndexError => e
325
283
  error[e.message]
284
+ rescue PathError => e
285
+ ignore!("Path %s not defined for %p (%s)", e.path.join("."), value, value.class)
326
286
  end
327
287
  end
328
288
 
@@ -333,42 +293,56 @@ module Remap
333
293
  super { fmap(&block) }
334
294
  end
335
295
 
336
- # Ensures {value:} is not a state
296
+ # A list of keys representing the path to {#value}
297
+ #
298
+ # @return [Array<Symbol, Integer, String>]
299
+ def path
300
+ fetch(:path, EMPTY_ARRAY)
301
+ end
302
+
303
+ # Represents options to a mapper
337
304
  #
338
- # @param options [Hash]
305
+ # @see Rule::Embed
339
306
  #
340
307
  # @return [Hash]
341
- def merge(options)
342
- case options
343
- in {value:}
344
- value._ { return super }
345
- else
346
- return super
347
- end
348
-
349
- raise ArgumentError, "Expected State#value not to be a State [#{value}] (#{value.class})"
308
+ def options
309
+ fetch(:options)
350
310
  end
351
311
 
312
+ # Used by {#context} to create a limited context
313
+ #
314
+ # @return [Hash]
352
315
  def to_hash
353
- except(:options, :mapper, :problems, :value)
316
+ super.except(:options, :notices, :value)
354
317
  end
355
318
 
319
+ # @return [Any]
356
320
  def value
357
321
  fetch(:value)
358
322
  end
359
323
 
360
- def problem(message)
361
- merge(problems: explaination(message, problems)).except(:value)
324
+ # @return [Integer]
325
+ def index
326
+ fetch(:index)
362
327
  end
363
328
 
364
- def problems
365
- fetch(:problems)
329
+ # @return [Any]
330
+ def element
331
+ fetch(:element)
366
332
  end
367
333
 
368
- def options
369
- fetch(:options)
334
+ # @return [Any]
335
+ def key
336
+ fetch(:key)
370
337
  end
371
338
 
339
+ # @return [Array<Notice>]
340
+ def notices
341
+ fetch(:notices)
342
+ end
343
+
344
+ private
345
+
372
346
  # Creates a context containing {options} and {self}
373
347
  #
374
348
  # @param value [Any]
@@ -376,14 +350,14 @@ module Remap
376
350
  # @yieldparam reason [T]
377
351
  #
378
352
  # @return [Struct]
379
- def context(value, state: self, &error)
353
+ def context(value, context: self, &error)
380
354
  ::Struct.new(*keys, *options.keys, keyword_init: true) do
381
355
  define_method :method_missing do |name, *|
382
356
  error["Method [#{name}] not defined"]
383
357
  end
384
358
 
385
359
  define_method :skip! do |message = "Manual skip!"|
386
- error[message]
360
+ context.ignore!(message)
387
361
  end
388
362
  end.new(**to_hash, **options, value: value)
389
363
  end
@@ -6,7 +6,7 @@ module Remap
6
6
  required(:input)
7
7
 
8
8
  required(:mapper).filled(Remap::Types::Mapper)
9
- required(:problems).hash
9
+ required(:notices).array(Types.Instance(Notice))
10
10
  required(:options).value(:hash)
11
11
  required(:path).array(Types::Key)
12
12
 
data/lib/remap/state.rb CHANGED
@@ -8,15 +8,41 @@ require "dry/logic"
8
8
  require "dry/logic/operations"
9
9
  require "dry/logic/predicates"
10
10
  require "json"
11
- require "pry"
12
11
 
13
12
  module Remap
13
+ # Represents the current state of a mapping
14
14
  module State
15
- include Dry::Core::Constants
15
+ using Extensions::Object
16
16
  using Extension
17
17
 
18
- def state(value, mapper:, options: {})
19
- { value: value, input: value, mapper: mapper, problems: {}, path: [], options: options, values: value }._
18
+ include Dry::Core::Constants
19
+
20
+ class Dummy < Remap::Base
21
+ # NOP
22
+ end
23
+
24
+ # Creates a valid state
25
+ #
26
+ # @param value [Any] Internal state value
27
+ #
28
+ # @option mapper [Mapper::Class] Mapper class
29
+ # @option options [Hash] Mapper options / arguments
30
+ #
31
+ # @return [Hash] A valid state
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}"
20
46
  end
21
47
  end
22
48
  end
@@ -2,12 +2,23 @@
2
2
 
3
3
  module Remap
4
4
  class Static
5
- class Fixed < Concrete
6
- using State::Extension
5
+ using State::Extension
7
6
 
7
+ # Maps a fixed value to state
8
+ #
9
+ # @example Map a fixed value to path
10
+ # class Mapper < Remap::Base
11
+ # define do
12
+ # set :a, :b, to: value('a value')
13
+ # end
14
+ # end
15
+ #
16
+ # Mapper.call({}) # => { a: { b: 'a value' } }
17
+ class Fixed < Concrete
18
+ # @return [Any]
8
19
  attribute :value, Types::Any
9
20
 
10
- # Set {state#value} to {#value}
21
+ # Set state to {#value}
11
22
  #
12
23
  # @param state [State]
13
24
  #
@@ -2,20 +2,35 @@
2
2
 
3
3
  module Remap
4
4
  class Static
5
- class Option < Concrete
6
- using State::Extension
5
+ using State::Extension
7
6
 
7
+ # Maps a mapper argument to a path
8
+ #
9
+ # @example Maps a mapper argument to a path
10
+ # class Mapper < Remap::Base
11
+ # option :name
12
+ #
13
+ # define do
14
+ # set :nickname, to: option(:name)
15
+ # end
16
+ # end
17
+ #
18
+ # Mapper.call({}, name: "John") # => { nickname: "John" }
19
+ class Option < Concrete
20
+ # @return [Symbol]
8
21
  attribute :name, Symbol
9
22
 
10
- # Selects {#value} from {state#params}
23
+ # Selects {#name} from state
11
24
  #
12
25
  # @param state [State]
13
26
  #
14
27
  # @return [State]
15
28
  def call(state)
16
- state.set state.options.fetch(name) {
17
- return state.problem("Option [#{name}] not found in [#{state.options.inspect}]")
18
- }
29
+ state.set(state.options.fetch(name))
30
+ rescue KeyError => e
31
+ raise ArgumentError, e.exception("Option [%s] not found in input [%p]" % [
32
+ name, state.options
33
+ ])
19
34
  end
20
35
  end
21
36
  end
data/lib/remap/static.rb CHANGED
@@ -1,6 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
+ # A static mapped value either represented by an option or a value
4
5
  class Static < Dry::Interface
6
+ attribute? :backtrace, Types::Backtrace
7
+
8
+ # Maps a static value to state
9
+ #
10
+ # @param state [State<T>]
11
+ #
12
+ # @return [State<Y>]
13
+ #
14
+ # @abstract
15
+ def call(state)
16
+ raise NotImplementedError, "#{self.class}#call not implemented"
17
+ end
5
18
  end
6
19
  end