Permalink
Browse files

Initial work on template compilation

  • Loading branch information...
1 parent fe7b8fe commit 7786aa57d1aff435d6fcc7b03159f3eee70ba20e @josevalim josevalim committed Sep 7, 2012
View
@@ -85,12 +85,14 @@ defmodule Dynamo.App do
@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__), :load_env }
- @before_compile { unquote(__MODULE__), :apply_filters }
- @before_compile { unquote(__MODULE__), :apply_initializers }
+ @before_compile { unquote(__MODULE__), :define_filters }
+ @before_compile { unquote(__MODULE__), :define_view_paths }
+ @before_compile { unquote(__MODULE__), :define_finishers }
use Dynamo.Utils.Once
@@ -100,24 +102,8 @@ defmodule Dynamo.App do
filter Dynamo.Filters.Head
- config :dynamo, Dynamo.App.default_options(__FILE__)
-
- # The reloader needs to be the first initializer
- 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
-
- # Then starts up the application
- initializer :start_dynamo_app do
- if app = config[:dynamo][:otp_app] do
- :application.start(app)
- end
- end
+ config :dynamo, Dynamo.App.default_options(__ENV__)
+ Dynamo.App.default_initializers
if @dynamo_registration != false do
@on_load :register_dynamo_app
@@ -130,39 +116,34 @@ defmodule Dynamo.App do
end
@doc false
- def default_options(file) do
+ def default_options(env) do
[ public_route: "/public",
compile_on_demand: false,
reload_modules: false,
source_paths: ["app/*"],
view_paths: ["app/views"],
- root: File.expand_path("../..", file) ]
+ compiled_view_paths: env.module.CompiledViews,
+ root: File.expand_path("../..", env.file) ]
end
@doc false
- def config_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
+ 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
- if dynamo[:reload_modules] && !dynamo[:compile_on_demand] do
- raise "Cannot have reload_modules set to true and compile_on_demand set to false"
+ initializer :start_dynamo_app do
+ if app = config[:dynamo][:otp_app] do
+ :application.start(app)
+ end
+ end
end
-
- filters
end
@doc false
@@ -179,6 +160,15 @@ defmodule Dynamo.App do
# 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),
@@ -191,7 +181,7 @@ defmodule Dynamo.App do
end
@doc false
- defmacro load_env(module) do
+ defmacro load_env_file(module) do
root = Module.read_attribute(module, :config)[:dynamo][:root]
if root && File.dir?("#{root}/config/environments") do
file = "#{root}/config/environments/#{Dynamo.env}.exs"
@@ -200,15 +190,87 @@ defmodule Dynamo.App do
end
@doc false
- defmacro apply_filters(_) do
+ defmacro define_filters(_) do
quote location: :keep do
- Enum.each Dynamo.App.config_filters(__MODULE__), prepend_filter(&1)
+ Enum.each Dynamo.App.default_filters(__MODULE__), prepend_filter(&1)
def :filters, [], [], do: Macro.escape(Enum.reverse(@__filters))
end
end
@doc false
- defmacro apply_initializers(_) do
+ 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.compilable? end)
+ end
+
+ compiled_initializer =
+ if compiled != [] do
+ view_paths = [dynamo[:compiled_view_paths]|runtime]
+
+ quote location: :keep do
+ initializer :ensure_compiled_view_paths_is_available do
+ module = Enum.first(view_paths)
+ unless Code.ensure_loaded?(module) do
+ raise "could not find compiled view paths module #{inspect module}"
+ end
+ end
+ end
+ end
+
+ renderer_initializer =
+ if runtime != [] do
+ quote location: :keep do
+ initializer :boot_view_renderer_server 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(compiled_initializer)
+ unquote(renderer_initializer)
+ end
+ end
+
+ @doc false
+ defmacro define_finishers(_) do
quote location: :keep do
initializer :ensure_endpoint_is_available do
if @endpoint && not Code.ensure_compiled?(@endpoint) do
View
@@ -52,6 +52,13 @@ defmodule Dynamo.Reloader do
end
@doc """
+ Register a callback that is invoked every time modules are purged.
+ """
+ def on_purge(fun) when is_function(fun) do
+ :gen_server.cast(__MODULE__, { :on_purge, fun })
+ end
+
+ @doc """
Tries to load the missing module. It returns `:ok` if a file
for the given module could be found and `:error` otherwise.
Note it does not actually ensure the module was loaded (only
@@ -84,13 +91,18 @@ defmodule Dynamo.Reloader do
and loaded modules, and "unrequire" the relevant files.
"""
def conditional_purge do
- :gen_server.call(__MODULE__, :conditional_purge)
+ case :gen_server.call(__MODULE__, :conditional_purge) do
+ :ok -> :ok
+ { :purged, callbacks } ->
+ lc callback inlist callbacks, do: callback.()
+ :purged
+ end
end
## Callbacks
defrecord Config, loaded_modules: [], loaded_files: [], paths: nil,
- updated_at: { { 1970, 1, 1 }, { 0, 0, 0 } }
+ updated_at: { { 1970, 1, 1 }, { 0, 0, 0 } }, on_purge: []
@doc false
def init(paths) do
@@ -110,7 +122,8 @@ defmodule Dynamo.Reloader do
else
purge_all(config)
unload_all(config)
- { :reply, :purged, config.loaded_modules([]).loaded_files([]).updated_at(last_modified) }
+ { :reply, { :purged, Enum.reverse(config.on_purge) },
+ config.loaded_modules([]).loaded_files([]).updated_at(last_modified) }
end
end
@@ -127,6 +140,10 @@ defmodule Dynamo.Reloader do
{ :noreply, config.prepend_loaded_modules(modules).prepend_loaded_files([file]) }
end
+ def handle_cast({ :on_purge, fun }, config) do
+ { :noreply, config.prepend_on_purge([fun]) }
+ end
+
def handle_cast(_arg, _config) do
super
end
@@ -14,6 +14,11 @@ defmodule Dynamo.View.Finder do
defcallback new(info)
@doc """
+ Returns true it provides compiled templates.
+ """
+ defcallback compilable?(self)
+
+ @doc """
Returns all templates for this finder.
This is used for eager template
@@ -41,6 +46,10 @@ defmodule Dynamo.View.PathFinder do
{ __MODULE__, File.expand_path(root) }
end
+ def compilable?(_) do
+ true
+ end
+
def all({ __MODULE__, root }) do
lc path inlist File.wildcard("#{root}/**/*.*") do
key = :binary.replace(path, root <> "/", "")
@@ -22,6 +22,13 @@ defmodule Dynamo.View.Renderer do
end
@doc """
+ Clear compiled templates cache.
+ """
+ def clear do
+ :gen_server.cast(__MODULE__, :clear)
+ end
+
+ @doc """
This function is responsible for rendering the templates.
It supports both pre-compiled and on demand compilation.
@@ -50,11 +57,7 @@ defmodule Dynamo.View.Renderer do
def handle_call({ :get_cached, identifier, updated_at }, _from, dict) do
case Dict.get(dict, identifier) do
{ module, cached } when updated_at > cached ->
- spawn fn ->
- :code.purge(module)
- :code.delete(module)
- end
-
+ spawn fn -> purge_module(module) end
{ :reply, nil, Dict.delete(dict, identifier) }
{ module, _ } ->
{ :reply, module, dict }
@@ -71,11 +74,24 @@ defmodule Dynamo.View.Renderer do
end
end
- def handle_call(:stop, _from, state) do
- { :stop, :normal, :ok, state }
+ def handle_call(:stop, _from, dict) do
+ { :stop, :normal, :ok, dict }
+ end
+
+ def handle_call(_arg, _from, _dict) do
+ super
end
- def handle_call(_arg, _from, _config) do
+ def handle_cast(:clear, dict) do
+ spawn fn ->
+ Enum.each dict, fn({ _, { module, _ } }) ->
+ purge_module(module)
+ end
+ end
+ { :noreply, Binary.Dict.new }
+ end
+
+ def handle_cast(_arg, _dict) do
super
end
@@ -94,6 +110,11 @@ defmodule Dynamo.View.Renderer do
raise "Compiling template #{inspect identifier} exceeded the max number of attempts #{@max_attemps}. What gives?"
end
+ defp purge_module(module) do
+ :code.purge(module)
+ :code.delete(module)
+ end
+
defp generate_module(source, identifier, attempts) when attempts < @max_attemps do
random = :random.uniform(@slots)
module = Module.concat(Dynamo.View, "Template#{random}")
Oops, something went wrong.

0 comments on commit 7786aa5

Please sign in to comment.