forked from dynamo/dynamo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.ex
251 lines (197 loc) · 7.14 KB
/
app.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
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
defmodule Dynamo.App do
@moduledoc """
`Dynamo.App` is a module that helps you define your
application behavior globally.
A `Dynamo.App` can be used on top of a `Dynamo.Router`,
so you can route and point to other endpoints easily.
## Configuration
Dynamo comes with a configuration API that allows a
developer to customize how dynamo works and custom
extensions.
For example, here is a snippet that configures Dynamo
to serve public assets from the :myapp application
everytime we have a request at `/public`:
config :dynamo,
public_root: :myapp,
public_route: "/public"
The available `:dynamo` configurations are:
* `:public_route` - The route to trigger public assets serving
* `:compile_on_demand` - Compiles modules as they are needed
* `:reload_modules` - Reload modules after they are changed
* `:source_paths` - The paths to search when compiling modules on demand
* `:view_paths` - The paths to find views
* `:otp_app` - The otp application associated to this app
## Filters
A `Dynamo.App` also contains a set of filters that are meant
to be used throughout your whole application. Some of these
filters are added based on your configuration option. The
filters included by default and when they are included are:
* `Dynamo.Filters.Static` - when a public_route and public_root are set,
we add this filter to serve static assets;
* `Dynamo.Filters.Reloader` - when `:compile_on_demand` or `:reload_modules`
configs are set to true, allowing code to be compiled and reloaded on demand;
* `Dynamo.Filters.Head` - converts HEAD requests to GET;
Filters can be added and removed using `filter` and `remove_filter`
macros. You can get the list of application filters using:
`mix dynamo.filters`.
For more information, check `Dynamo.Router.Filters` docs.
## Initialization
`Dynamo.App` allows you to register initializers which are
invoked when the application starts. A Dynamo application
is initialized in three steps:
* The dynamo framework needs to be loaded via Dynamo.start
* The application needs to be loaded via APP.start
* A handler needs to be run to serve an application
The step 2 can be extended via initializers. For example:
defmodule MyApp do
use Dynamo.App
initializer :some_config do
# Connect to the database
end
end
By default, the application ships with 3 initializers:
* `:start_dynamo_reloader` - starts the code reloader, usually
used in development and test
* `:start_dynamo_app` - starts the Dynamo application registered as `otp_app`
* `:start_dynamo_renderer` - starts dynamo renderer if there
are templates to be compiled on demand
"""
@doc false
defmacro __using__(_) do
quote do
require Dynamo.App
@dynamo_app true
@before_compile { unquote(__MODULE__), :load_env_file }
@before_compile { unquote(__MODULE__), :normalize_options }
@before_compile { unquote(__MODULE__), :define_filters }
@before_compile { unquote(__MODULE__), :define_view_paths }
use Dynamo.Utils.Once
use_once Dynamo.App.Config
use_once Dynamo.App.Runner
use_once Dynamo.Router.Filters
filter Dynamo.Filters.Head
config :dynamo, Dynamo.App.default_options(__ENV__)
Dynamo.App.default_initializers
if @dynamo_registration != false do
@on_load :register_dynamo_app
defp register_dynamo_app do
Dynamo.app(__MODULE__)
end
end
end
end
@doc false
def default_options(env) do
[ public_route: "/public",
compile_on_demand: false,
reload_modules: false,
source_paths: ["app/*"],
view_paths: ["app/views"],
compiled_view_paths: env.module.CompiledViews ]
end
@doc false
defmacro default_initializers do
quote location: :keep do
initializer :start_dynamo_reloader do
dynamo = config[:dynamo]
if dynamo[:compile_on_demand] do
Dynamo.Reloader.start_link dynamo[:source_paths]
Dynamo.Reloader.enable!
IEx.preload.after_spawn(fn -> Dynamo.Reloader.enable! end)
end
end
end
end
@doc false
defmacro normalize_options(mod) do
dynamo = Module.read_attribute(mod, :config)[:dynamo]
root = Dynamo.root
source = dynamo[:source_paths]
source = Enum.reduce source, [], fn(path, acc) -> expand_paths(path, root) ++ acc end
view = dynamo[:view_paths]
view = Enum.reduce view, [], fn(path, acc) -> expand_paths(path, root) ++ acc end
# Remove views that eventually end up on source
source = source -- view
# Now convert all view paths to Dynamo.View.Finders
view = lc path inlist view do
if is_binary(path) do
Dynamo.View.PathFinder.new(path)
else
path
end
end
quote do
config :dynamo,
view_paths: unquote(view),
source_paths: unquote(source)
end
end
defp expand_paths(path, root) do
path /> File.expand_path(root) /> File.wildcard
end
@doc false
defmacro load_env_file(_) do
root = Dynamo.root
if File.dir?("#{root}/config/environments") do
file = "#{root}/config/environments/#{Dynamo.env}.exs"
Code.string_to_ast! File.read!(file), file: file
end
end
@doc false
defmacro define_filters(_) do
quote location: :keep do
Enum.each Dynamo.App.default_filters(__MODULE__), prepend_filter(&1)
def :filters, [], [], do: Macro.escape(Enum.reverse(@__filters))
end
end
@doc false
def default_filters(mod) do
filters = []
dynamo = Module.read_attribute(mod, :config)[:dynamo]
public_route = dynamo[:public_route]
public_root = case dynamo[:public_root] do
nil -> dynamo[:otp_app]
other -> other
end
if public_root && public_route do
filters = [Dynamo.Filters.Static.new(public_route, public_root)|filters]
end
if dynamo[:compile_on_demand] || dynamo[:reload_modules] do
filters = [Dynamo.Filters.Reloader.new(dynamo[:compile_on_demand], dynamo[:reload_modules])|filters]
end
if dynamo[:reload_modules] && !dynamo[:compile_on_demand] do
raise "Cannot have reload_modules set to true and compile_on_demand set to false"
end
filters
end
@doc false
defmacro define_view_paths(module) do
dynamo = Module.read_attribute(module, :config)[:dynamo]
view_paths = dynamo[:view_paths]
{ compiled, runtime } =
if dynamo[:compile_on_demand] do
{ [], view_paths }
else
Enum.partition(view_paths, fn(path) -> path.eager? end)
end
if compiled != [] do
module = dynamo[:compiled_view_paths]
view_paths = [module|runtime]
end
renderer_initializer =
if runtime != [] do
quote location: :keep do
initializer :start_dynamo_renderer do
Dynamo.View.Renderer.start_link
if config[:dynamo][:compile_on_demand] do
Dynamo.Reloader.on_purge(fn -> Dynamo.View.Renderer.clear end)
end
end
end
end
quote location: :keep do
def view_paths, do: unquote(Macro.escape(view_paths))
unquote(renderer_initializer)
end
end
end