remap 2.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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