-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Expand file tree
/
Copy pathengine.ex
More file actions
202 lines (159 loc) · 5.31 KB
/
engine.ex
File metadata and controls
202 lines (159 loc) · 5.31 KB
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
defmodule EEx.Engine do
@moduledoc ~S"""
Basic EEx engine that ships with Elixir.
An engine needs to implement six functions:
* `init(opts)` - called at the beginning of every text
and it must return the initial state.
* `handle_body(state)` - receives the state of the document
and it must return a quoted expression.
* `handle_text(state, text)` - it receives the state,
the text and must return a new quoted expression.
* `handle_expr(state, marker, expr)` - it receives the state,
the marker, the expr and must return a new state.
* `handle_begin(state)` - called every time there a new state
is needed with an empty buffer. Typically called for do/end
blocks, case expressions, anonymous functions, etc
* `handle_end(state)` - opposite of `handle_begin(state)` and
it must return quoted expression
The marker is what follows exactly after `<%`. For example,
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
as marker. The allowed markers so far are:
* `""`
* `"="`
* `"/"`
* `"|"`
Markers `"/"` and `"|"` are only for use in custom EEx engines
and are not implemented by default. Using them without the
implementation raises `EEx.SyntaxError`.
If your engine does not implement all markers, please ensure that
`handle_expr/3` falls back to `EEx.Engine.handle_expr/3`
to raise the proper error message.
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
`EEx.Engine` can be used directly if one desires to use the
default implementations for the functions above.
"""
@type state :: term
@callback init(opts :: keyword) :: state
@callback handle_body(state) :: Macro.t()
@callback handle_text(state, text :: String.t()) :: state
@callback handle_expr(state, marker :: String.t(), expr :: Macro.t()) :: state
@callback handle_begin(state) :: state
@callback handle_end(state) :: Macro.t()
@doc false
defmacro __using__(_) do
quote do
@behaviour EEx.Engine
def init(opts) do
EEx.Engine.init(opts)
end
def handle_body(quoted) do
EEx.Engine.handle_body(quoted)
end
def handle_begin(quoted) do
EEx.Engine.handle_begin(quoted)
end
def handle_end(quoted) do
EEx.Engine.handle_end(quoted)
end
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, marker, expr) do
EEx.Engine.handle_expr(buffer, marker, expr)
end
defoverridable EEx.Engine
end
end
@doc """
Handles assigns in quoted expressions.
A warning will be printed on missing assigns.
Future versions will raise.
This can be added to any custom engine by invoking
`handle_assign/1` with `Macro.prewalk/2`:
def handle_expr(buffer, token, expr) do
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
EEx.Engine.handle_expr(buffer, token, expr)
end
"""
@spec handle_assign(Macro.t()) :: Macro.t()
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
line = meta[:line] || 0
quote(line: line, do: EEx.Engine.fetch_assign!(var!(assigns), unquote(name)))
end
def handle_assign(arg) do
arg
end
@doc false
# TODO: Raise on 2.0
@spec fetch_assign!(Access.t(), Access.key()) :: term | nil
def fetch_assign!(assigns, key) do
case Access.fetch(assigns, key) do
{:ok, val} ->
val
:error ->
keys = Enum.map(assigns, &elem(&1, 0))
IO.warn(
"assign @#{key} not available in EEx template. " <>
"Please ensure all assigns are given as options. " <>
"Available assigns: #{inspect(keys)}"
)
nil
end
end
@doc """
Returns an empty string as initial buffer.
"""
def init(_opts) do
""
end
@doc """
Returns an empty string as the new buffer.
"""
def handle_begin(_previous) do
""
end
@doc """
End of the new buffer.
"""
def handle_end(quoted) do
quoted
end
@doc """
The default implementation simply returns the given expression.
"""
def handle_body(quoted) do
quoted
end
@doc """
The default implementation simply concatenates text to the buffer.
"""
def handle_text(buffer, text) do
quote(do: unquote(buffer) <> unquote(text))
end
@doc """
Implements expressions according to the markers.
<% Elixir expression - inline with output %>
<%= Elixir expression - replace with result %>
<%/ Elixir expression - raise EEx.SyntaxError, to be implemented by custom engines %>
<%| Elixir expression - raise EEx.SyntaxError, to be implemented by custom engines %>
All other markers are not implemented by this engine.
"""
def handle_expr(buffer, "=", expr) do
quote do
tmp1 = unquote(buffer)
tmp1 <> String.Chars.to_string(unquote(expr))
end
end
def handle_expr(buffer, "", expr) do
quote do
tmp2 = unquote(buffer)
unquote(expr)
tmp2
end
end
def handle_expr(_buffer, marker, _expr) when marker in ["/", "|"] do
raise EEx.SyntaxError,
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
end
end