@@ -91,7 +91,23 @@ defmodule Iteraptor do
91
91
%{"a_b_c" => 42, "a_b_d_0" => nil, "a_b_d_1" => 42, "a_e_0" => :f, "a_e_1" => 42}
92
92
"""
93
93
94
- @ spec to_flatmap ( % { } | [ ... ] , Keyword . t ( ) ) :: % { }
94
+ @ type option ::
95
+ { :keys , :reverse }
96
+ | { :yield , :all | :none | :maps | :lists }
97
+ | { :structs , :values | :keep }
98
+ @ type options :: [ option ( ) ]
99
+
100
+ @ typedoc """
101
+ The function that might be passed to all the traversion functions.
102
+
103
+ When it’s a function or arity `1`, it receives `{key, value}` tuple when the key
104
+ is the list of keys down the nesting levels.
105
+
106
+ When its arity is `2`, it receives `key` and `value` as separated arguments.
107
+ """
108
+ @ type traverse_fun :: ( { any ( ) , any ( ) } -> any ( ) ) | ( any ( ) , any ( ) -> any ( ) )
109
+
110
+ @ spec to_flatmap ( Access . t ( ) , options ( ) ) :: % { }
95
111
96
112
def to_flatmap ( input , opts \\ [ ] ) when is_map ( input ) or is_list ( input ) do
97
113
reducer = fn { k , v } , acc ->
@@ -155,25 +171,22 @@ defmodule Iteraptor do
155
171
{[0, :b], 42}
156
172
[%{a: 42, b: 42}]
157
173
"""
158
- @ spec from_flatmap ( % { } , ( { any ( ) , any ( ) } -> any ( ) ) | nil , Keyword . t ( ) ) ::
159
- % { } | [ ... ] | Keyword . t ( )
174
+ @ spec from_flatmap ( % { } , traverse_fun ( ) , options ( ) ) :: Access . t ( )
160
175
161
- def from_flatmap ( input , transformer \\ nil , opts \\ [ ] ) when is_map ( input ) do
176
+ def from_flatmap ( input , transformer \\ & & 1 , opts \\ [ ] ) when is_map ( input ) do
162
177
reducer = fn { k , v } , acc ->
163
178
key =
164
179
case k |> Enum . join ( delimiter ( opts ) ) |> String . split ( delimiter ( opts ) ) do
165
180
[ k ] -> [ smart_convert ( k ) ]
166
181
list -> Enum . map ( list , & smart_convert / 1 )
167
182
end
168
183
184
+ transformer_key = if opts [ :keys ] == :reverse , do: Enum . reverse ( key ) , else: key
185
+
169
186
value =
170
- if is_nil ( transformer ) do
171
- v
172
- else
173
- case transformer . ( { key , v } ) do
174
- { ^ key , any } -> any
175
- any -> any
176
- end
187
+ case transformer . ( { transformer_key , v } ) do
188
+ { ^ key , any } -> any
189
+ any -> any
177
190
end
178
191
179
192
deep_put_in ( acc , { key , value } , opts )
@@ -198,9 +211,10 @@ defmodule Iteraptor do
198
211
is an array or deeply nested keys;
199
212
e.g. on `%{a: {b: 42}}` will be called once, with tuple `{[:a, :b], 42}`;
200
213
- `opts`: the options to be passed to the iteration
201
- - `yield`: `[:all | :maps | :keywords | nil ]` what to yield; _default:_ `nil `
214
+ - `yield`: `[:all | :none | :maps | :lists ]` what to yield; _default:_ `:all `
202
215
for yielding _values only_
203
- - `structs`: `[:values | :keep | nil]` how to handle structs; _default:_ `nil`
216
+ - `keys`: `[:reverse]` reverse keys list to ease pattern matching; _default:_ `nil`
217
+ - `structs`: `[:values | :keep]` how to handle structs; _default:_ `nil`
204
218
for treating them as `map`s. When `:values`, the nested structs
205
219
are considered leaves and returned to the iterator instead of being iterated
206
220
through; when `:keep` it returns a struct back after iteration
@@ -218,8 +232,7 @@ defmodule Iteraptor do
218
232
%{a: %{b: %{c: 42}}}
219
233
"""
220
234
221
- @ spec each ( % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , ( { any ( ) , any ( ) } -> any ( ) ) , Keyword . t ( ) ) ::
222
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( )
235
+ @ spec each ( Access . t ( ) , traverse_fun ( ) , options ( ) ) :: Access . t ( )
223
236
224
237
def each ( input , fun , opts \\ [ ] ) do
225
238
map ( input , fun , opts )
@@ -239,9 +252,7 @@ defmodule Iteraptor do
239
252
- `fun`: callback to be called on each **`{key, value}`** pair, where `key`
240
253
is an array or deeply nested keys;
241
254
e.g. on `%{a: {b: 42}}` will be called once, with tuple `{[:a, :b], 42}`;
242
- - `opts`: the options to be passed to the iteration
243
- - `yield`: `[:all | :maps | :keywords |` what to yield; _default:_ `nil`
244
- for yielding _values only_.
255
+ - `opts`: the options to be passed to the iteration (see `Iteraptpr.each/3`)
245
256
246
257
## Examples
247
258
@@ -259,8 +270,7 @@ defmodule Iteraptor do
259
270
%{a: %{b: "YAY"}}
260
271
"""
261
272
262
- @ spec map ( % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , ( { any ( ) , any ( ) } -> any ( ) ) , Keyword . t ( ) ) ::
263
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( )
273
+ @ spec map ( Access . t ( ) , traverse_fun ( ) , options ( ) ) :: Access . t ( )
264
274
265
275
def map ( input , fun , opts \\ [ ] ) do
266
276
unless is_function ( fun , 1 ) , do: raise ( "Function or arity fun/1 is required" )
@@ -285,9 +295,7 @@ defmodule Iteraptor do
285
295
- `fun`: callback to be called on each **`{key, value}, acc`** pair,
286
296
where `key` is an array or deeply nested keys, `value` is the value and
287
297
`acc` is the accumulator;
288
- - `opts`: the options to be passed to the iteration
289
- - `yield`: `[:all | :maps | :keywords |` what to yield; _default:_ `nil`
290
- for yielding _values only_.
298
+ - `opts`: the options to be passed to the iteration (see `Iteraptpr.each/3`)
291
299
292
300
## Examples
293
301
@@ -299,12 +307,7 @@ defmodule Iteraptor do
299
307
["a", "a_b", "a_b_c"]
300
308
"""
301
309
302
- @ spec reduce (
303
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) ,
304
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) ,
305
- ( { any ( ) , any ( ) } , any ( ) -> any ( ) ) ,
306
- Keyword . t ( )
307
- ) :: { % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , any ( ) }
310
+ @ spec reduce ( Access . t ( ) , Access . t ( ) , traverse_fun ( ) , options ( ) ) :: { Access . t ( ) , any ( ) }
308
311
309
312
def reduce ( input , acc \\ nil , fun , opts \\ [ ] ) do
310
313
unless is_function ( fun , 2 ) , do: raise ( "Function or arity fun/2 is required" )
@@ -332,9 +335,7 @@ defmodule Iteraptor do
332
335
- `fun`: callback to be called on each **`{key, value}, acc`** pair,
333
336
where `key` is an array or deeply nested keys, `value` is the value and
334
337
`acc` is the accumulator;
335
- - `opts`: the options to be passed to the iteration
336
- - `yield`: `[:all | :maps | :keywords |` what to yield; _default:_ `nil`
337
- for yielding _values only_.
338
+ - `opts`: the options to be passed to the iteration (see `Iteraptpr.each/3`)
338
339
339
340
## Examples
340
341
@@ -346,12 +347,7 @@ defmodule Iteraptor do
346
347
{%{a: %{b: %{c: 84}}}, ["a.b.c=", "a.b", "a"]}
347
348
"""
348
349
349
- @ spec map_reduce (
350
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) ,
351
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) ,
352
- ( { any ( ) , any ( ) } , any ( ) -> any ( ) ) ,
353
- Keyword . t ( )
354
- ) :: { % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , any ( ) }
350
+ @ spec map_reduce ( Access . t ( ) , Access . t ( ) , traverse_fun ( ) , options ( ) ) :: { Access . t ( ) , any ( ) }
355
351
356
352
def map_reduce ( input , acc \\ % { } , fun , opts \\ [ ] ) do
357
353
unless is_function ( fun , 2 ) , do: raise ( "Function or arity fun/2 is required" )
@@ -378,9 +374,7 @@ defmodule Iteraptor do
378
374
379
375
- `input`: nested map/list/keyword to be filtered.
380
376
- `fun`: callback to be called on each **`{key, value}`** to filter entries.
381
- - `opts`: the options to be passed to the iteration
382
- - `yield`: `[:all | :maps | :keywords |` what to yield; _default:_ `nil`
383
- for yielding _values only_.
377
+ - `opts`: the options to be passed to the iteration (see `Iteraptpr.each/3`)
384
378
385
379
## Examples
386
380
@@ -389,8 +383,7 @@ defmodule Iteraptor do
389
383
%{a: %{e: %{c: 42}, d: %{c: 42}}, c: 42}
390
384
"""
391
385
392
- @ spec filter ( % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , ( { any ( ) , any ( ) } -> any ( ) ) , Keyword . t ( ) ) ::
393
- { % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , any ( ) }
386
+ @ spec filter ( Access . t ( ) , traverse_fun ( ) , options ( ) ) :: { Access . t ( ) , any ( ) }
394
387
395
388
def filter ( input , fun , opts \\ [ ] ) do
396
389
unless is_function ( fun , 1 ) , do: raise ( "Function or arity fun/1 is required" )
@@ -427,7 +420,7 @@ defmodule Iteraptor do
427
420
iex> Iteraptor.jsonify([foo: [bar: [baz: :zoo], boo: 42]], keys: false)
428
421
%{foo: %{bar: %{baz: :zoo}, boo: 42}}
429
422
"""
430
- @ spec jsonify ( Access . container ( ) | any ( ) , opts :: list ( ) ) :: map ( )
423
+ @ spec jsonify ( Access . container ( ) | any ( ) , keyword ( ) ) :: map ( )
431
424
def jsonify ( input , opts \\ [ ] )
432
425
def jsonify ( [ { _ , _ } | _ ] = input , opts ) , do: input |> Map . new ( ) |> jsonify ( opts )
433
426
def jsonify ( input , opts ) when is_list ( input ) , do: Enum . map ( input , & jsonify ( & 1 , opts ) )
@@ -458,15 +451,18 @@ defmodule Iteraptor do
458
451
459
452
##############################################################################
460
453
461
- @ spec traverse_callback ( ( { any ( ) , any ( ) } -> any ( ) ) | ( any ( ) , any ( ) -> any ( ) ) , { any ( ) , any ( ) } ) ::
462
- { any ( ) , any ( ) }
454
+ @ spec traverse_callback ( nil | traverse_fun ( ) , { any ( ) , any ( ) } , nil | :reverse ) :: { any ( ) , any ( ) }
463
455
464
- defp traverse_callback ( fun , { value , acc } ) do
465
- case fun do
466
- f when is_function ( fun , 1 ) -> { f . ( value ) , nil }
467
- f when is_function ( fun , 2 ) -> f . ( value , acc )
468
- end
469
- end
456
+ defp traverse_callback ( nil , { value , acc } , _ ) , do: { value , acc }
457
+
458
+ defp traverse_callback ( fun , { { keys , value } , acc } , :reverse ) ,
459
+ do: traverse_callback ( fun , { { Enum . reverse ( keys ) , value } , acc } , nil )
460
+
461
+ defp traverse_callback ( fun , { value , acc } , nil ) when is_function ( fun , 1 ) ,
462
+ do: { fun . ( value ) , acc }
463
+
464
+ defp traverse_callback ( fun , { value , acc } , nil ) when is_function ( fun , 2 ) ,
465
+ do: fun . ( value , acc )
470
466
471
467
defmacrop traverse_value ( { k , v } , fun , opts , { deep , acc } ) do
472
468
quote do
@@ -475,16 +471,10 @@ defmodule Iteraptor do
475
471
end
476
472
end
477
473
478
- @ spec traverse (
479
- % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) ,
480
- ( { any ( ) , any ( ) } -> any ( ) ) | ( any ( ) , any ( ) -> any ( ) ) ,
481
- Keyword . t ( ) ,
482
- { [ any ( ) ] , any ( ) }
483
- ) :: { % { } | Keyword . t ( ) | [ ... ] | Access . t ( ) , any ( ) }
474
+ @ spec traverse ( Access . t ( ) , traverse_fun ( ) , options ( ) , { [ any ( ) ] , any ( ) } ) :: { Access . t ( ) , any ( ) }
484
475
485
476
defp traverse ( input , fun , opts , key_acc )
486
477
487
- # credo:disable-for-lines:41
488
478
defp traverse ( input , fun , opts , { key , acc } ) when is_list ( input ) or is_map ( input ) do
489
479
{ type , from , into } = type ( input )
490
480
@@ -507,10 +497,11 @@ defmodule Iteraptor do
507
497
508
498
{ value , acc } =
509
499
case { opts [ :yield ] , is_map ( v ) and not s_as_v , is_list ( v ) } do
510
- { _ , false , false } -> traverse_callback ( fun , { { deep , v } , acc } )
511
- { :all , _ , _ } -> traverse_callback ( fun , { { deep , v } , acc } )
512
- { :lists , _ , true } -> traverse_callback ( fun , { { deep , v } , acc } )
513
- { :maps , true , _ } -> traverse_callback ( fun , { { deep , v } , acc } )
500
+ { _ , false , false } -> traverse_callback ( fun , { { deep , v } , acc } , opts [ :keys ] )
501
+ { :all , _ , _ } -> traverse_callback ( fun , { { deep , v } , acc } , opts [ :keys ] )
502
+ { :none , _ , _ } -> traverse_callback ( nil , { { deep , v } , acc } , opts [ :keys ] )
503
+ { :lists , _ , true } -> traverse_callback ( fun , { { deep , v } , acc } , opts [ :keys ] )
504
+ { :maps , true , _ } -> traverse_callback ( fun , { { deep , v } , acc } , opts [ :keys ] )
514
505
_ -> { { deep , v } , acc }
515
506
end
516
507
@@ -535,31 +526,4 @@ defmodule Iteraptor do
535
526
end
536
527
537
528
defp traverse ( input , _fun , _opts , { _key , acc } ) , do: { input , acc }
538
-
539
- # defp process(input, :struct, {acc, key, fun}, opts) do
540
- # struct_name = input.__struct__ |> inspect |> String.replace(".", struct_joiner(opts))
541
- # input
542
- # |> Map.keys
543
- # |> Enum.reject(& &1 == :__struct__)
544
- # |> Enum.map(fn e ->
545
- # {"#{struct_name}%#{e}", get_in(input, [Access.key!(e)])}
546
- # end)
547
- # |> Enum.into(into(opts))
548
- # |> process(Map, {acc, key, fun}, opts)
549
- # end
550
-
551
- ##############################################################################
552
-
553
- ##############################################################################
554
-
555
- # defp is_struct(input) when is_map(input) do
556
- # input |> Enum.reduce(nil, fn {k, _}, acc ->
557
- # case k |> to_string |> String.split(~r{#{@struct_joiner}(?=[^#{@struct_joiner}]*$)}) do
558
- # [^acc, _] -> acc
559
- # [struct_name, _] -> if acc == nil, do: struct_name, else: false
560
- # _ -> false
561
- # end
562
- # end)
563
- # end
564
- # defp is_struct(_), do: false
565
529
end
0 commit comments