-
Notifications
You must be signed in to change notification settings - Fork 5
/
destructure.ex
164 lines (121 loc) · 4.29 KB
/
destructure.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
defmodule Destructure do
@moduledoc """
Provides helpers for destructuring Elixir data structures. See the `d/1` macro
for more information.
"""
@doc """
Easy destructuring of `map`, `structs`, `keyword`, with atom keys.
String keys are supported via a special syntax `d(s%{param})`.
## Examples
The macro can be used in simple match statements:
iex> d(%{name}) = %{name: "Joe"}
...> name
"Joe"
Or in case/for/with statements.
iex> case %{name: "Mike"} do
...> d%{name} -> name
...> end
"Mike"
It can be used inside a complex match:
iex> %{body: d%{name}} = %{body: %{name: "Robert"}}
...> name
"Robert"
With multiple keys:
iex> d(%{first, last}) = %{first: "Daniel", last: "Berkompas"}
...> {first, last}
{"Daniel", "Berkompas"}
With multiple keys and custom variable naming:
iex> d(%{first, last, email: mail}) = %{first: "Daniel", last: "Berkompas", email: "top@secret.com"}
...> {first, last, mail}
{"Daniel", "Berkompas", "top@secret.com"}
For Maps with String Keys:
iex> s(%{name}) = %{"name" => "Daniel Berkompas"}
...> name
"Daniel Berkompas"
With multiple keys:
iex> s(%{name, email}) = %{"name" => "Daniel Berkompas", "email" => "top@secret.com"}
...> {name, email}
{"Daniel Berkompas", "top@secret.com"}
With multiple keys and custom variable naming:
iex> s(%{name, "email" => mail}) = %{"name" => "Daniel Berkompas", "email" => "top@secret.com"}
...> {name, mail}
{"Daniel Berkompas", "top@secret.com"}
For structs:
iex> d(%Person{name}) = %Person{name: "Daniel Berkompas"}
...> name
"Daniel Berkompas"
With multiple keys:
iex> d(%Person{name, email}) = %Person{name: "Daniel Berkompas", email: "top@secret.com"}
...> {name, email}
{"Daniel Berkompas", "top@secret.com"}
With multiple keys and custom variable naming:
iex> d(%Person{name, email: mail}) = %Person{name: "Daniel Berkompas", email: "top@secret.com"}
...> {name, mail}
{"Daniel Berkompas", "top@secret.com"}
For keyword lists:
iex> d([name]) = [name: "Daniel"]
...> name
"Daniel"
With multiple keys:
iex> d([first, last]) = [first: "Daniel", last: "Berkompas"]
...> [first, last]
["Daniel", "Berkompas"]
With multiple keys and custom assignment:
iex> d([first, last, email: mail]) = [first: "Daniel", last: "Berkompas", email: "top@secret.com"]
...> [first, last, mail]
["Daniel", "Berkompas", "top@secret.com"]
And in function definitions:
iex> defmodule Test do
...> import Destructure
...> def test(d%{name}) do
...> name
...> end
...> end
...> Test.test(%{name: "Daniel"})
"Daniel"
"""
# Handle maps, including ones with multiple keys
# {%{}, [], [{:first, [], Elixir}, {:second, [], Elixir}]}
defmacro d({:%{}, context, args}) do
{:%{}, context, Enum.map(args, &pattern/1)}
end
# Handle structs, including ones with multiple keys
# {:%, [],
# [{:__aliases__, [alias: false], [:Namespace]},
# {:%{}, [], [{:first, [], Elixir}, {:second, [], Elixir}]}]}
defmacro d({:%, x_context, [namespace, {:%{}, y_context, args}]}) do
{:%, x_context, [namespace, {:%{}, y_context, Enum.map(args, &pattern/1)}]}
end
# Handle 1 and 3+ element tuples
# {:{}, [], [{:first, [], Elixir}]}
defmacro d({:{}, _, args}) do
Enum.map(args, &pattern/1)
end
# Handle the special case of two-element tuples, whose ASTs look like
# {{:first, [], Elixir}, {:last, [], Elixir}}
defmacro d({first, second}) do
[pattern(first), pattern(second)]
end
# Handle keyword list using square bracket
defmacro d(list) when is_list(list) do
Enum.map(list, &pattern/1)
end
# Handle string maps, including ones with multiple keys
# {:%{}, [], [{:stuff, [], Elixir}, {:things, [], Elixir}]}
defmacro s({:%{}, context, args}) do
{:%{}, context, Enum.map(args, &s_pattern/1)}
end
defp s_pattern({key, _, _} = variable) do
{key |> Atom.to_string(), variable}
end
defp s_pattern(otherwise), do: pattern(otherwise)
defp pattern({key, _, _} = variable) do
{key, variable}
end
defp pattern([arg]) do
arg
end
defp pattern(other) do
other
end
end