remap 2.0.2 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/remap/base.rb +229 -75
- data/lib/remap/compiler.rb +403 -37
- data/lib/remap/constructor/argument.rb +20 -6
- data/lib/remap/constructor/keyword.rb +20 -4
- data/lib/remap/constructor/none.rb +3 -4
- data/lib/remap/constructor.rb +12 -5
- data/lib/remap/contract.rb +27 -0
- data/lib/remap/extensions/enumerable.rb +48 -0
- data/lib/remap/extensions/hash.rb +13 -0
- data/lib/remap/extensions/object.rb +37 -0
- data/lib/remap/failure.rb +25 -15
- data/lib/remap/iteration/array.rb +20 -11
- data/lib/remap/iteration/hash.rb +21 -13
- data/lib/remap/iteration/other.rb +7 -7
- data/lib/remap/iteration.rb +8 -2
- data/lib/remap/mapper/and.rb +29 -7
- data/lib/remap/mapper/binary.rb +3 -6
- data/lib/remap/mapper/or.rb +29 -6
- data/lib/remap/mapper/support/operations.rb +40 -0
- data/lib/remap/mapper/xor.rb +29 -7
- data/lib/remap/mapper.rb +1 -48
- data/lib/remap/notice/traced.rb +19 -0
- data/lib/remap/notice/untraced.rb +11 -0
- data/lib/remap/notice.rb +34 -0
- data/lib/remap/operation.rb +26 -13
- data/lib/remap/path/input.rb +37 -0
- data/lib/remap/path/output.rb +26 -0
- data/lib/remap/path.rb +22 -0
- data/lib/remap/path_error.rb +13 -0
- data/lib/remap/proxy.rb +18 -0
- data/lib/remap/rule/each.rb +25 -24
- data/lib/remap/rule/embed.rb +33 -28
- data/lib/remap/rule/map/optional.rb +42 -0
- data/lib/remap/rule/map/required.rb +35 -0
- data/lib/remap/rule/map.rb +176 -55
- data/lib/remap/rule/set.rb +23 -33
- data/lib/remap/rule/support/collection/empty.rb +7 -7
- data/lib/remap/rule/support/collection/filled.rb +21 -8
- data/lib/remap/rule/support/collection.rb +11 -3
- data/lib/remap/rule/support/enum.rb +44 -21
- data/lib/remap/rule/void.rb +17 -18
- data/lib/remap/rule/wrap.rb +25 -17
- data/lib/remap/rule.rb +8 -1
- data/lib/remap/selector/all.rb +29 -7
- data/lib/remap/selector/index.rb +24 -16
- data/lib/remap/selector/key.rb +31 -16
- data/lib/remap/selector.rb +17 -0
- data/lib/remap/state/extension.rb +182 -208
- data/lib/remap/state/schema.rb +1 -1
- data/lib/remap/state.rb +30 -4
- data/lib/remap/static/fixed.rb +14 -3
- data/lib/remap/static/option.rb +21 -6
- data/lib/remap/static.rb +13 -0
- data/lib/remap/struct.rb +1 -0
- data/lib/remap/types.rb +13 -28
- data/lib/remap.rb +15 -19
- metadata +95 -93
- data/lib/remap/result.rb +0 -11
- data/lib/remap/rule/support/path.rb +0 -45
- data/lib/remap/state/types.rb +0 -11
- data/lib/remap/success.rb +0 -29
- data/lib/remap/version.rb +0 -5
data/lib/remap/compiler.rb
CHANGED
@@ -1,17 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Remap
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
param :rules, default: -> { EMPTY_ARRAY.dup }
|
4
|
+
# Constructs a {Rule} from the block passed to {Remap::Base.define}
|
5
|
+
class Compiler < Proxy
|
6
|
+
# @return [Array<Rule>]
|
7
|
+
param :rules, type: Types.Array(Rule)
|
10
8
|
|
11
9
|
# @return [Rule]
|
12
|
-
delegate call:
|
10
|
+
delegate :call, to: Compiler
|
13
11
|
|
14
|
-
# Constructs a rule tree given
|
12
|
+
# Constructs a rule tree given block
|
13
|
+
#
|
14
|
+
# @example Compiles two rules, [get] and [map]
|
15
|
+
# rule = Remap::Compiler.call do
|
16
|
+
# get :name
|
17
|
+
# get :age
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# state = Remap::State.call({
|
21
|
+
# name: "John",
|
22
|
+
# age: 50
|
23
|
+
# })
|
24
|
+
#
|
25
|
+
# error = -> failure { raise failure.exception }
|
26
|
+
#
|
27
|
+
# rule.call(state, &error).fetch(:value) # => { name: "John", age: 50 }
|
15
28
|
#
|
16
29
|
# @return [Rule]
|
17
30
|
def self.call(&block)
|
@@ -19,83 +32,324 @@ module Remap
|
|
19
32
|
return Rule::Void.new
|
20
33
|
end
|
21
34
|
|
22
|
-
new.tap
|
35
|
+
new([]).tap do |compiler|
|
36
|
+
compiler.instance_exec(&block)
|
37
|
+
end.rule
|
23
38
|
end
|
24
39
|
|
25
|
-
# Maps
|
40
|
+
# Maps input path [input] to output path [to]
|
26
41
|
#
|
27
42
|
# @param path ([]) [Array<Segment>, Segment]
|
28
43
|
# @param to ([]) [Array<Symbol>, Symbol]
|
29
44
|
#
|
30
|
-
# @
|
31
|
-
|
32
|
-
|
45
|
+
# @example From path [:name] to [:nickname]
|
46
|
+
# rule = Remap::Compiler.call do
|
47
|
+
# map :name, to: :nickname
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# state = Remap::State.call({
|
51
|
+
# name: "John"
|
52
|
+
# })
|
53
|
+
#
|
54
|
+
# output = rule.call(state) do |failure|
|
55
|
+
# raise failure.exception
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# output.fetch(:value) # => { nickname: "John" }
|
59
|
+
#
|
60
|
+
# @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))
|
69
|
+
end
|
70
|
+
|
71
|
+
# Optional version of {#map}
|
72
|
+
#
|
73
|
+
# @example Map an optional field
|
74
|
+
# rule = Remap::Compiler.call do
|
75
|
+
# to :person do
|
76
|
+
# map? :age, to: :age
|
77
|
+
# map :name, to: :name
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# state = Remap::State.call({
|
82
|
+
# name: "John"
|
83
|
+
# })
|
84
|
+
#
|
85
|
+
# output = rule.call(state) do |failure|
|
86
|
+
# raise failure.exception
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# output.fetch(:value) # => { person: { name: "John" } }
|
90
|
+
#
|
91
|
+
# @see #map
|
92
|
+
#
|
93
|
+
# @return [Rule::Map::Optional]
|
94
|
+
def map?(*path, to: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
|
95
|
+
add Rule::Map::Optional.call(
|
33
96
|
path: {
|
34
|
-
|
35
|
-
|
97
|
+
output: [to].flatten,
|
98
|
+
input: path.flatten
|
36
99
|
},
|
37
|
-
|
38
|
-
|
100
|
+
backtrace: backtrace,
|
101
|
+
rule: call(&block))
|
39
102
|
end
|
40
103
|
|
41
|
-
#
|
104
|
+
# Select a path and uses the same path as output
|
105
|
+
#
|
106
|
+
# @param path ([]) [Array<Segment>, Segment]
|
107
|
+
#
|
108
|
+
# @example Map from [:name] to [:name]
|
109
|
+
# rule = Remap::Compiler.call do
|
110
|
+
# get :name
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# state = Remap::State.call({
|
114
|
+
# name: "John"
|
115
|
+
# })
|
116
|
+
#
|
117
|
+
# output = rule.call(state) do |failure|
|
118
|
+
# raise failure.exception
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# output.fetch(:value) # => { name: "John" }
|
122
|
+
#
|
123
|
+
# @return [Rule::Map::Required]
|
124
|
+
def get(*path, backtrace: Kernel.caller, &block)
|
125
|
+
map(path, to: path, backtrace: backtrace, &block)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Optional version of {#get}
|
129
|
+
#
|
130
|
+
# @example Map from [:name] to [:name]
|
131
|
+
# rule = Remap::Compiler.call do
|
132
|
+
# get :name
|
133
|
+
# get? :age
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# state = Remap::State.call({
|
137
|
+
# name: "John"
|
138
|
+
# })
|
139
|
+
#
|
140
|
+
# output = rule.call(state) do |failure|
|
141
|
+
# raise failure.exception
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# output.fetch(:value) # => { name: "John" }
|
145
|
+
#
|
146
|
+
# @see #get
|
147
|
+
#
|
148
|
+
# @return [Rule::Map::Optional]
|
149
|
+
def get?(*path, backtrace: Kernel.caller, &block)
|
150
|
+
map?(path, to: path, backtrace: backtrace, &block)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Maps using mapper
|
42
154
|
#
|
43
155
|
# @param mapper [Remap]
|
44
156
|
#
|
157
|
+
# @example Embed mapper Car into a person
|
158
|
+
# class Car < Remap::Base
|
159
|
+
# define do
|
160
|
+
# map :car do
|
161
|
+
# map :name, to: :car
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# rule = Remap::Compiler.call do
|
167
|
+
# map :person do
|
168
|
+
# embed Car
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# state = Remap::State.call({
|
173
|
+
# person: {
|
174
|
+
# car: {
|
175
|
+
# name: "Volvo"
|
176
|
+
# }
|
177
|
+
# }
|
178
|
+
# })
|
179
|
+
#
|
180
|
+
# output = rule.call(state) do |failure|
|
181
|
+
# raise failure.exception
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# output.fetch(:value) # => { car: "Volvo" }
|
185
|
+
#
|
45
186
|
# @return [Rule::Embed]
|
46
|
-
def embed(mapper)
|
187
|
+
def embed(mapper, &block)
|
188
|
+
if block
|
189
|
+
raise ArgumentError, "#embed does not take a block"
|
190
|
+
end
|
191
|
+
|
47
192
|
add Rule::Embed.new(mapper: mapper)
|
48
193
|
rescue Dry::Struct::Error
|
49
194
|
raise ArgumentError, "Embeded mapper must be [Remap::Mapper], got [#{mapper}]"
|
50
195
|
end
|
51
196
|
|
52
|
-
#
|
197
|
+
# Set a static value
|
198
|
+
#
|
199
|
+
# @param path ([]) [Symbol, Array<Symbol>]
|
53
200
|
# @option to [Remap::Static]
|
54
201
|
#
|
202
|
+
# @example Set static value to { name: "John" }
|
203
|
+
# rule = Remap::Compiler.call do
|
204
|
+
# set :name, to: value("John")
|
205
|
+
# end
|
206
|
+
#
|
207
|
+
# state = Remap::State.call({})
|
208
|
+
#
|
209
|
+
# output = rule.call(state) do |failure|
|
210
|
+
# raise failure.exception
|
211
|
+
# end
|
212
|
+
#
|
213
|
+
# output.fetch(:value) # => { name: "John" }
|
214
|
+
#
|
215
|
+
# @example Reference an option
|
216
|
+
# rule = Remap::Compiler.call do
|
217
|
+
# set :name, to: option(:name)
|
218
|
+
# end
|
219
|
+
#
|
220
|
+
# state = Remap::State.call({}, options: { name: "John" })
|
221
|
+
#
|
222
|
+
# output = rule.call(state) do |failure|
|
223
|
+
# raise failure.exception
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# output.fetch(:value) # => { name: "John" }
|
227
|
+
#
|
55
228
|
# @return [Rule::Set]
|
56
229
|
# @raise [ArgumentError]
|
57
230
|
# if no path given
|
58
231
|
# if path is not a Symbol or Array<Symbol>
|
59
|
-
def set(*path, to
|
60
|
-
|
232
|
+
def set(*path, to:, &block)
|
233
|
+
if block
|
234
|
+
raise ArgumentError, "#set does not take a block"
|
235
|
+
end
|
236
|
+
|
237
|
+
add Rule::Set.new(path: path.flatten, value: to)
|
61
238
|
rescue Dry::Struct::Error => e
|
62
239
|
raise ArgumentError, e.message
|
63
240
|
end
|
64
241
|
|
65
|
-
# Maps to
|
242
|
+
# Maps to path from map with block in between
|
66
243
|
#
|
67
244
|
# @param path [Array<Symbol>, Symbol]
|
68
245
|
# @param map [Array<Segment>, Segment]
|
69
246
|
#
|
247
|
+
# @example From path [:name] to [:nickname]
|
248
|
+
# rule = Remap::Compiler.call do
|
249
|
+
# to :nickname, map: :name
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
# state = Remap::State.call({
|
253
|
+
# name: "John"
|
254
|
+
# })
|
255
|
+
#
|
256
|
+
# output = rule.call(state) do |failure|
|
257
|
+
# raise failure.exception
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# output.fetch(:value) # => { nickname: "John" }
|
261
|
+
#
|
70
262
|
# @return [Rule::Map]
|
71
|
-
def to(*path, map: EMPTY_ARRAY, &block)
|
72
|
-
map(*map, to: path, &block)
|
263
|
+
def to(*path, map: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
|
264
|
+
map(*map, to: path, backtrace: backtrace, &block)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Optional version of {#to}
|
268
|
+
#
|
269
|
+
# @example Map an optional field
|
270
|
+
# rule = Remap::Compiler.call do
|
271
|
+
# to :person do
|
272
|
+
# to? :age, map: :age
|
273
|
+
# to :name, map: :name
|
274
|
+
# end
|
275
|
+
# end
|
276
|
+
#
|
277
|
+
# state = Remap::State.call({
|
278
|
+
# name: "John"
|
279
|
+
# })
|
280
|
+
#
|
281
|
+
# output = rule.call(state) do |failure|
|
282
|
+
# raise failure.exception
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# output.fetch(:value) # => { person: { name: "John" } }
|
286
|
+
# @see #to
|
287
|
+
#
|
288
|
+
# @return [Rule::Map::Optional]
|
289
|
+
def to?(*path, map: EMPTY_ARRAY, &block)
|
290
|
+
map?(*map, to: path, &block)
|
73
291
|
end
|
74
292
|
|
75
293
|
# Iterates over the input value, passes each value
|
76
294
|
# to its block and merges the result back together
|
77
295
|
#
|
296
|
+
# @example Map an array of hashes
|
297
|
+
# rule = Remap::Compiler.call do
|
298
|
+
# each do
|
299
|
+
# map :name
|
300
|
+
# end
|
301
|
+
# end
|
302
|
+
#
|
303
|
+
# state = Remap::State.call([{
|
304
|
+
# name: "John"
|
305
|
+
# }, {
|
306
|
+
# name: "Jane"
|
307
|
+
# }])
|
308
|
+
#
|
309
|
+
# output = rule.call(state) do |failure|
|
310
|
+
# raise failure.exception
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# output.fetch(:value) # => ["John", "Jane"]
|
314
|
+
#
|
78
315
|
# @return [Rule::Each]]
|
79
316
|
# @raise [ArgumentError] if no block given
|
80
317
|
def each(&block)
|
81
318
|
unless block
|
82
|
-
raise ArgumentError, "
|
319
|
+
raise ArgumentError, "#each requires a block"
|
83
320
|
end
|
84
321
|
|
85
322
|
add Rule::Each.new(rule: call(&block))
|
86
323
|
end
|
87
324
|
|
88
|
-
# Wraps output in
|
325
|
+
# Wraps output in type
|
89
326
|
#
|
90
327
|
# @param type [:array]
|
91
328
|
#
|
92
329
|
# @yieldreturn [Rule]
|
93
330
|
#
|
331
|
+
# @example Wrap an output value in an array
|
332
|
+
# rule = Remap::Compiler.call do
|
333
|
+
# wrap(:array) do
|
334
|
+
# map :name
|
335
|
+
# end
|
336
|
+
# end
|
337
|
+
#
|
338
|
+
# state = Remap::State.call({
|
339
|
+
# name: "John"
|
340
|
+
# })
|
341
|
+
#
|
342
|
+
# output = rule.call(state) do |failure|
|
343
|
+
# raise failure.exception
|
344
|
+
# end
|
345
|
+
#
|
346
|
+
# output.fetch(:value) # => ["John"]
|
347
|
+
#
|
94
348
|
# @return [Rule::Wrap]
|
95
349
|
# @raise [ArgumentError] if type is not :array
|
96
350
|
def wrap(type, &block)
|
97
351
|
unless block
|
98
|
-
raise ArgumentError, "
|
352
|
+
raise ArgumentError, "#wrap requires a block"
|
99
353
|
end
|
100
354
|
|
101
355
|
add Rule::Wrap.new(type: type, rule: call(&block))
|
@@ -105,8 +359,28 @@ module Remap
|
|
105
359
|
|
106
360
|
# Selects all elements
|
107
361
|
#
|
362
|
+
# @example Select all keys in array
|
363
|
+
# rule = Remap::Compiler.call do
|
364
|
+
# map all, :name, to: :names
|
365
|
+
# end
|
366
|
+
#
|
367
|
+
# state = Remap::State.call([
|
368
|
+
# { name: "John" },
|
369
|
+
# { name: "Jane" }
|
370
|
+
# ])
|
371
|
+
#
|
372
|
+
# output = rule.call(state) do |failure|
|
373
|
+
# raise failure.exception
|
374
|
+
# end
|
375
|
+
#
|
376
|
+
# output.fetch(:value) # => { names: ["John", "Jane"] }
|
377
|
+
#
|
108
378
|
# @return [Rule::Path::Segment::Quantifier::All]
|
109
|
-
def all
|
379
|
+
def all(&block)
|
380
|
+
if block
|
381
|
+
raise ArgumentError, "all selector does not take a block"
|
382
|
+
end
|
383
|
+
|
110
384
|
Selector::All.new(EMPTY_HASH)
|
111
385
|
end
|
112
386
|
|
@@ -114,44 +388,136 @@ module Remap
|
|
114
388
|
#
|
115
389
|
# @param value [Any]
|
116
390
|
#
|
391
|
+
# @example Set path to static value
|
392
|
+
# rule = Remap::Compiler.call do
|
393
|
+
# set :api_key, to: value("<SECRET>")
|
394
|
+
# end
|
395
|
+
#
|
396
|
+
# state = Remap::State.call({})
|
397
|
+
#
|
398
|
+
# output = rule.call(state) do |failure|
|
399
|
+
# raise failure.exception
|
400
|
+
# end
|
401
|
+
#
|
402
|
+
# output.fetch(:value) # => { api_key: "<SECRET>" }
|
403
|
+
#
|
117
404
|
# @return [Rule::Static::Fixed]
|
118
|
-
def value(value)
|
405
|
+
def value(value, &block)
|
406
|
+
if block
|
407
|
+
raise ArgumentError, "option selector does not take a block"
|
408
|
+
end
|
409
|
+
|
119
410
|
Static::Fixed.new(value: value)
|
120
411
|
end
|
121
412
|
|
122
413
|
# Static option to be selected
|
123
414
|
#
|
415
|
+
# @example Set path to option
|
416
|
+
# rule = Remap::Compiler.call do
|
417
|
+
# set :meaning_of_life, to: option(:number)
|
418
|
+
# end
|
419
|
+
#
|
420
|
+
# state = Remap::State.call({}, options: { number: 42 })
|
421
|
+
#
|
422
|
+
# output = rule.call(state) do |failure|
|
423
|
+
# raise failure.exception
|
424
|
+
# end
|
425
|
+
#
|
426
|
+
# output.fetch(:value) # => { meaning_of_life: 42 }
|
427
|
+
#
|
124
428
|
# @param id [Symbol]
|
125
429
|
#
|
126
430
|
# @return [Rule::Static::Option]
|
127
|
-
def option(id)
|
128
|
-
|
431
|
+
def option(id, backtrace: Kernel.caller, &block)
|
432
|
+
if block
|
433
|
+
raise ArgumentError, "option selector does not take a block"
|
434
|
+
end
|
435
|
+
|
436
|
+
Static::Option.new(name: id, backtrace: backtrace)
|
129
437
|
end
|
130
438
|
|
131
|
-
# Selects
|
439
|
+
# Selects index element in input
|
132
440
|
#
|
133
441
|
# @param index [Integer]
|
134
442
|
#
|
443
|
+
# @example Select value at index
|
444
|
+
# rule = Remap::Compiler.call do
|
445
|
+
# map :names, at(1), to: :name
|
446
|
+
# end
|
447
|
+
#
|
448
|
+
# state = Remap::State.call({
|
449
|
+
# names: ["John", "Jane"]
|
450
|
+
# })
|
451
|
+
#
|
452
|
+
# output = rule.call(state) do |failure|
|
453
|
+
# raise failure.exception
|
454
|
+
# end
|
455
|
+
#
|
456
|
+
# output.fetch(:value) # => { name: "Jane" }
|
457
|
+
#
|
135
458
|
# @return [Path::Segment::Key]
|
136
459
|
# @raise [ArgumentError] if index is not an Integer
|
137
|
-
def at(index)
|
460
|
+
def at(index, &block)
|
461
|
+
if block
|
462
|
+
raise ArgumentError, "first selector does not take a block"
|
463
|
+
end
|
464
|
+
|
138
465
|
Selector::Index.new(index: index)
|
139
466
|
rescue Dry::Struct::Error
|
140
|
-
raise ArgumentError,
|
467
|
+
raise ArgumentError,
|
468
|
+
"Selector at(index) requires an integer argument, got [#{index}] (#{index.class})"
|
141
469
|
end
|
142
470
|
|
143
471
|
# Selects first element in input
|
144
472
|
#
|
473
|
+
# @example Select first value in an array
|
474
|
+
# rule = Remap::Compiler.call do
|
475
|
+
# map :names, first, to: :name
|
476
|
+
# end
|
477
|
+
#
|
478
|
+
# state = Remap::State.call({
|
479
|
+
# names: ["John", "Jane"]
|
480
|
+
# })
|
481
|
+
#
|
482
|
+
# output = rule.call(state) do |failure|
|
483
|
+
# raise failure.exception
|
484
|
+
# end
|
485
|
+
#
|
486
|
+
# output.fetch(:value) # => { name: "John" }
|
487
|
+
#
|
145
488
|
# @return [Path::Segment::Key]]
|
146
|
-
def first
|
489
|
+
def first(&block)
|
490
|
+
if block
|
491
|
+
raise ArgumentError, "first selector does not take a block"
|
492
|
+
end
|
493
|
+
|
147
494
|
at(0)
|
148
495
|
end
|
149
496
|
alias any first
|
150
497
|
|
151
498
|
# Selects last element in input
|
152
499
|
#
|
500
|
+
# @example Select last value in an array
|
501
|
+
# rule = Remap::Compiler.call do
|
502
|
+
# map :names, last, to: :name
|
503
|
+
# end
|
504
|
+
#
|
505
|
+
# state = Remap::State.call({
|
506
|
+
# names: ["John", "Jane", "Linus"]
|
507
|
+
# })
|
508
|
+
#
|
509
|
+
# output = rule.call(state) do |failure|
|
510
|
+
# raise failure.exception
|
511
|
+
# end
|
512
|
+
#
|
513
|
+
# output.fetch(:value) # => { name: "Linus" }
|
514
|
+
#
|
153
515
|
# @return [Path::Segment::Key]
|
154
|
-
def last
|
516
|
+
def last(&block)
|
517
|
+
if block
|
518
|
+
raise ArgumentError, "last selector does not take a block"
|
519
|
+
end
|
520
|
+
|
155
521
|
at(-1)
|
156
522
|
end
|
157
523
|
|
@@ -2,16 +2,27 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Allows a class (target) to be called with a regular argument
|
8
|
+
class Argument < Concrete
|
9
|
+
# @return [:argument]
|
8
10
|
attribute :strategy, Value(:argument), default: :argument
|
9
11
|
|
10
|
-
# Uses the {#method} method to initialize {#target} with
|
11
|
-
# Target is only called if
|
12
|
+
# Uses the {#method} method to initialize {#target} with state
|
13
|
+
# Target is only called if state is defined
|
14
|
+
#
|
15
|
+
# Used by {Remap::Base} to define constructors for mapped data
|
12
16
|
#
|
13
17
|
# Fails if {#target} does not respond to {#method}
|
14
|
-
# Fails if {#target} cannot be called with
|
18
|
+
# Fails if {#target} cannot be called with state
|
19
|
+
#
|
20
|
+
# @example Initialize a target with a state
|
21
|
+
# target = ::Struct.new(:foo)
|
22
|
+
# constructor = Remap::Constructor.call(strategy: :argument, target: target, method: :new)
|
23
|
+
# state = Remap::State.call(:bar)
|
24
|
+
# new_state = constructor.call(state)
|
25
|
+
# new_state.fetch(:value).foo # => :bar
|
15
26
|
#
|
16
27
|
# @param state [State]
|
17
28
|
#
|
@@ -20,7 +31,10 @@ module Remap
|
|
20
31
|
super.fmap do |input|
|
21
32
|
target.public_send(id, input)
|
22
33
|
rescue ArgumentError => e
|
23
|
-
raise e.exception("
|
34
|
+
raise e.exception("Failed to create [%p] with input [%s] (%s)" % [
|
35
|
+
target, input,
|
36
|
+
input.class
|
37
|
+
])
|
24
38
|
end
|
25
39
|
end
|
26
40
|
end
|
@@ -2,15 +2,26 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Allows a class (target) to be called with keyword arguments
|
8
|
+
class Keyword < Concrete
|
9
|
+
# @return [:keyword]
|
8
10
|
attribute :strategy, Value(:keyword)
|
9
11
|
|
10
12
|
# Calls {#target} as with keyword arguments
|
11
13
|
#
|
12
14
|
# Fails if {#target} does not respond to {#method}
|
13
|
-
# Fails if {#target} cannot be called with
|
15
|
+
# Fails if {#target} cannot be called with state
|
16
|
+
#
|
17
|
+
# Used by {Remap::Base} to define constructors for mapped data
|
18
|
+
#
|
19
|
+
# @example Initialize a target with a state
|
20
|
+
# target = OpenStruct
|
21
|
+
# constructor = Remap::Constructor.call(strategy: :keyword, target: target, method: :new)
|
22
|
+
# state = Remap::State.call({ foo: :bar })
|
23
|
+
# new_state = constructor.call(state)
|
24
|
+
# new_state.fetch(:value).foo # => :bar
|
14
25
|
#
|
15
26
|
# @param state [State]
|
16
27
|
#
|
@@ -23,7 +34,12 @@ module Remap
|
|
23
34
|
|
24
35
|
target.public_send(id, **input)
|
25
36
|
rescue ArgumentError => e
|
26
|
-
raise e.exception("
|
37
|
+
raise e.exception("Failed to create [%p] with input [%s] (%s}) using method %s" % [
|
38
|
+
target,
|
39
|
+
input,
|
40
|
+
input.class,
|
41
|
+
id
|
42
|
+
])
|
27
43
|
end
|
28
44
|
end
|
29
45
|
end
|
@@ -2,15 +2,14 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor
|
5
|
+
# Default type used by {Remap::Base}
|
5
6
|
class None < Concrete
|
6
7
|
attribute :target, Types::Nothing
|
7
8
|
attribute :strategy, Types::Any
|
8
9
|
attribute :method, Types::Any
|
9
10
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# Fails if {#target} does not respond to {#method}
|
13
|
-
# Fails if {#target} cannot be called with {state}
|
11
|
+
# Used by {Remap::Base} as a default constructor
|
12
|
+
# Using it does nothing but return its input state
|
14
13
|
#
|
15
14
|
# @param state [State]
|
16
15
|
#
|
data/lib/remap/constructor.rb
CHANGED
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor < Dry::Interface
|
5
|
+
# @return [Any]
|
6
|
+
attribute :target, Types::Any, not_eql: Nothing
|
7
|
+
|
8
|
+
# @return [Symbol]
|
5
9
|
attribute :method, Symbol, default: :new
|
6
|
-
attribute :target, Types::Any.constrained(not_eql: Nothing)
|
7
10
|
|
8
11
|
# Ensures {#target} responds to {#method}
|
9
12
|
# Returns an error state unless above is true
|
@@ -19,12 +22,16 @@ module Remap
|
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
attributes.fetch(:method)
|
24
|
-
end
|
25
|
-
|
25
|
+
# @return [Proc]
|
26
26
|
def to_proc
|
27
27
|
method(:call).to_proc
|
28
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @return [Symbol]
|
33
|
+
def id
|
34
|
+
attributes.fetch(:method)
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|