/
transformer.ex
121 lines (99 loc) · 3.55 KB
/
transformer.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
defmodule Ash.Dsl.Transformer do
@moduledoc """
A transformer manipulates and/or validates the entire DSL state of a resource.
It's `transform/2` takes a `map`, which is just the values/configurations at each point
of the DSL. Don't manipulate it directly, if possible, instead use functions like
`get_entities/3` and `replace_entity/5` to manipulate it.
Use the `after?/1` and `before?/1` callbacks to ensure that your transformer
runs either before or after some other transformer.
The pattern for requesting information from other modules that use the DSL and are
also currently compiling has not yet been determined. If you have that requirement
you will need extra utilities to ensure that some other DSL based module has either
completed or reached a certain point in its transformers. These utilities have not
yet been written.
"""
@callback transform(module, map) :: {:ok, map} | {:error, term} | :halt
@callback before?(module) :: boolean
@callback after?(module) :: boolean
defmacro __using__(_) do
quote do
@behaviour Ash.Dsl.Transformer
def before?(_), do: false
def after?(_), do: false
defoverridable before?: 1, after?: 1
end
end
def persist(dsl, key, value) do
Map.update(dsl, :persist, %{key => value}, &Map.put(&1, key, value))
end
def get_persisted(dsl, key, default \\ nil) do
dsl
|> Map.get(:persist, %{})
|> Map.get(key, default)
end
def build_entity(extension, path, name, opts) do
do_build_entity(extension.sections(), path, name, opts)
end
defp do_build_entity(sections, [section_name], name, opts) do
section = Enum.find(sections, &(&1.name == section_name))
entity = Enum.find(section.entities, &(&1.name == name))
case Ash.OptionsHelpers.validate(opts, entity.schema) do
{:ok, opts} ->
{:ok, struct(entity.target, opts)}
{:error, error} ->
{:error, error}
end
end
defp do_build_entity(sections, [section_name | rest], name, opts) do
section = Enum.find(sections, &(&1.name == section_name))
do_build_entity(section.sections, rest, name, opts)
end
def add_entity(dsl_state, path, entity) do
Map.update(dsl_state, path, %{entities: [entity], opts: []}, fn config ->
Map.update(config, :entities, [entity], fn entities ->
[entity | entities]
end)
end)
end
def get_entities(dsl_state, path) do
dsl_state
|> Map.get(path, %{entities: []})
|> Map.get(:entities, [])
end
def get_option(dsl_state, path, option) do
dsl_state
|> Map.get(path, %{opts: []})
|> Map.get(:opts)
|> Kernel.||([])
|> Keyword.get(option)
end
def replace_entity(dsl_state, path, replacement, matcher) do
Map.update(dsl_state, path, %{entities: [replacement], opts: []}, fn config ->
Map.update(config, :entities, [replacement], fn entities ->
replace_match(entities, replacement, matcher)
end)
end)
end
defp replace_match(entities, replacement, matcher) do
Enum.map(entities, fn entity ->
if matcher.(entity) do
replacement
else
entity
end
end)
end
def sort(transformers) do
Enum.reduce(transformers, [], fn transformer, list ->
put_transformer_in(list, transformer)
end)
end
defp put_transformer_in([], transformer), do: [transformer]
defp put_transformer_in([first | rest] = remaining, transformer) do
if transformer.before?(first) or first.after?(transformer) do
[transformer | remaining]
else
[first | put_transformer_in(rest, transformer)]
end
end
end