diff --git a/.gitignore b/.gitignore
index 2689f0d..5a97981 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
/ebin
/deps
/doc/*
-!/doc/*.png
erl_crash.dump
*.ez
*~
diff --git a/README.md b/README.md
index 05099af..44dd016 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ based on Webmachine from basho. This version is not backward compatible with
the previous one that was only a thin wrapper around webmachine, use the branch
1.0-legacy to use the old one.
-The principle is to go through the [HTTP decision tree](https://raw.githubusercontent.com/kbrw/ewebmachine/master/doc/http_diagram.png)
+The principle is to go through the [HTTP decision tree](./assets/http_diagram.png)
and make decisions according to response of some callbacks called "handlers".
To do that, the library gives you 5 plugs and 2 plug pipeline builders :
@@ -96,7 +96,7 @@ Go to `/wm_debug` to see precedent requests and debug there HTTP
decision path. The debug UI can be updated automatically on the
requests.
-
+
## Use Cowboy to serve the plug
diff --git a/doc/debug_ui.png b/assets/debug_ui.png
similarity index 100%
rename from doc/debug_ui.png
rename to assets/debug_ui.png
diff --git a/doc/http_diagram.png b/assets/http_diagram.png
similarity index 100%
rename from doc/http_diagram.png
rename to assets/http_diagram.png
diff --git a/lib/ewebmachine/core.utils.ex b/lib/ewebmachine/core.utils.ex
index 258d0a4..f94f7d4 100644
--- a/lib/ewebmachine/core.utils.ex
+++ b/lib/ewebmachine/core.utils.ex
@@ -82,7 +82,7 @@ defmodule Ewebmachine.Core.Utils do
@doc """
Get the string list from a comma separated list of HTTP quoted strings
"""
- @spec split_quoted_strings([String.t]) :: [String.t]
+ @spec split_quoted_strings(String.t) :: [String.t]
def split_quoted_strings(str) do
str |>
Plug.Conn.Utils.list() |>
diff --git a/lib/ewebmachine/handlers.ex b/lib/ewebmachine/handlers.ex
index 241d7e1..007cca4 100644
--- a/lib/ewebmachine/handlers.ex
+++ b/lib/ewebmachine/handlers.ex
@@ -5,7 +5,7 @@ defmodule Ewebmachine.Handlers do
@moduledoc """
Implement the functions described below to make decisions in the
- [HTTP decision tree](http_diagram.png) :
+ [HTTP decision tree](./assets/http_diagram.png) :
- `service_available/2`
- `resource_exists/2`
diff --git a/lib/ewebmachine/plug.debug.ex b/lib/ewebmachine/plug.debug.ex
index 54c16b7..02e1414 100644
--- a/lib/ewebmachine/plug.debug.ex
+++ b/lib/ewebmachine/plug.debug.ex
@@ -28,7 +28,7 @@ defmodule Ewebmachine.Plug.Debug do
browser will navigate to the debugging UI of the new request (you
can still use back/next to navigate through requests)
- 
+ 
"""
use Plug.Router
diff --git a/lib/ewebmachine/plug.run.ex b/lib/ewebmachine/plug.run.ex
index 7cde75f..806751f 100644
--- a/lib/ewebmachine/plug.run.ex
+++ b/lib/ewebmachine/plug.run.ex
@@ -1,7 +1,7 @@
defmodule Ewebmachine.Plug.Run do
@moduledoc ~S"""
- Plug passing your `conn` through the [HTTP decision tree](http_diagram.png)
- to fill its status and response.
+ Plug passing your `conn` through the [HTTP decision
+ tree](./assets/http_diagram.png) to fill its status and response.
This plug does not send the HTTP result, instead the `conn`
result of this plug must be sent with the plug
diff --git a/mix.exs b/mix.exs
index 1fd3472..2a7b1dd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -32,9 +32,11 @@ defmodule Ewebmachine.Mixfile do
defp docs do
[
+ assets: "assets",
extras: [
"CHANGELOG.md": [title: "Changelog"],
- "README.md": [title: "Overview"]
+ "README.md": [title: "Overview"],
+ "pages/demystify_dsl.md": [title: "Demystify Ewebmachine DSL"],
],
main: "readme",
source_url: git_repository(),
diff --git a/pages/demystify_dsl.md b/pages/demystify_dsl.md
new file mode 100644
index 0000000..3032980
--- /dev/null
+++ b/pages/demystify_dsl.md
@@ -0,0 +1,316 @@
+# Demystify Ewebmachine DSL
+
+It's very likely, as a reader of this documentation, that you already wrote a a
+route with `Ewebmachine` (or at least copy and pasted one), but did you ever
+wonder once how does it works under the hood? Maybe you did start looking into
+it and were repelled by the heavy use of macro.
+
+This document aims to go through some of `Ewebmachine`'s internals, in order to
+explain how, from a bunch of macros, we end up with a whole Plug pipeline.
+
+---
+
+Let's start with this small module:
+```elixir
+defmodule MyApi do
+ use Ewebmachine.Builder.Resources
+
+ resource "/api/path" do after
+ allowed_methods do: ["GET"]
+
+ defh(to_html, do: "
HTML
")
+ end
+end
+```
+
+It imports the macro `Ewebmachine.Builder.Resources.resource/[3-4]` into the
+scope, that we can then use to make the `/api/path` route.
+
+From this point on, the macro's magic starts :).
+
+## How do handlers (`allowed_methods` and friends) work?
+
+The [`resource` macro creates a module from the given
+body](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.resources.ex#L157-L174).
+
+```elixir
+defmodule Ewebmachine.Builder.Resources do
+ defmacro resource({:__aliases__, _, route_aliases},route,do: init_block, after: body) do
+ resource_quote(Module.concat([__CALLER__.module|route_aliases]),route,init_block,body)
+ end
+ defmacro resource(route,do: init_block, after: body) do
+ resource_quote(Module.concat(__CALLER__.module,"EWM"<>route_as_mod(route)),route,init_block,body)
+ end
+
+ def resource_quote(wm_module,route,init_block,body) do
+ quote do
+ @wm_routes {unquote(route), unquote(wm_module), unquote(Macro.escape(init_block))}
+
+ defmodule unquote(wm_module) do
+ use Ewebmachine.Builder.Handlers
+ unquote(body)
+ plug :add_handlers
+ end
+ end
+ end
+
+ # [...]
+end
+```
+
+> #### Dynamic module {: .neutral}
+>
+> Dynamically named module aren't nested under their parent module. That's why
+> the `resource` macro concatenates it with the caller's module.
+
+In this module each handler will become a function. As is, each handler is a
+macro.
+
+The created module uses the `Ewebmachine.Builder.Handler` module. This module
+defines the [list of
+handlers](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.handlers.ex#L91-L97)
+(`allowed_methods`, etc...). For [each handler defined in this list, a
+macro](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.handlers.ex#L144-L152)
+is created:
+
+```elixir
+defmodule Ewebmachine.Builder.Handlers do
+ @resource_fun_names [
+ :allowed_methods,
+ # [...]
+ ]
+
+ for resource_fun_name<-@resource_fun_names do
+ Module.eval_quoted(Ewebmachine.Builder.Handlers, quote do
+ @doc "see `Ewebmachine.Handlers.#{unquote(resource_fun_name)}/2`"
+ defmacro unquote(resource_fun_name)(do_block) do
+ name = unquote(resource_fun_name)
+ handler_quote(name,do_block[:do])
+ end
+ end)
+ end
+
+ # [...]
+end
+```
+
+Inside this macro, the called [function
+`handler_quote`](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.handlers.ex#L102-L110)
+takes care of adding the `{name, __MODULE__}` (where `name` is the handler's
+name) to the module attribute `@resource_handlers` and defining a function.
+
+```elixir
+defmodule Ewebmachine.Builder.Handlers do
+ defp handler_quote(name,body,guard,conn_match,state_match) do
+ quote do
+ @resource_handlers Map.put(@resource_handlers,unquote(name),__MODULE__)
+ def unquote(name)(unquote(conn_match)=var!(conn),unquote(state_match)=var!(state)) when unquote(guard) do
+ res = unquote(body)
+ wrap_response(res,var!(conn),var!(state))
+ end
+ end
+ end
+
+ # [...]
+end
+```
+
+> #### defh macro {: .info}
+>
+> [`defh`](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.handlers.ex#L139-L142)
+> macro which allows you to pass guard, works the same way underneath and calls
+> `handler_quote` too.
+
+Great we now know how handlers are transformed into functions.
+
+---
+
+## But how are handlers called?
+
+### Adding custom handlers to the connection
+
+The `:add_handlers` plug used by the created module takes care of adding
+handler names saved into the module's attribute to the connection's private
+field `:resource_handlers`.
+
+`use Ewebmachine.Builder.Handler` defines a `@before_compile
+Ewebmachine.Builder.Handler` attributes in which the [`add_handlers` plug
+function](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.handlers.ex#L71-L78)
+is defined:
+
+```elixir
+defmodule Ewebmachine.Builder.Handlers do
+ defmacro __before_compile__(_env) do
+ quote do
+ defp add_handlers(conn, opts) do
+ # [ ... ]
+ Plug.Conn.put_private(conn, :resource_handlers,
+ Enum.into(@resource_handlers, conn.private[:resource_handlers] || %{}))
+ end
+ end
+ end
+end
+```
+
+### Internal usage of custom handlers
+
+Ewebmachine [decision
+tree](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/core.ex)
+calls handlers when going through the tree. For instance, the
+[`allowed_methods` is
+call](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/core.ex#L61)
+as such:
+
+```elixir
+{methods, conn, state} = resource_call(conn, state, :allowed_methods)
+```
+
+To use a custom handler, `Ewebmachine` simply looks up with the handler's name,
+into its private connection field `:resource_handlers` (added by the
+`:add_handlers` plug), which contains a map where keys are handler's names and
+values are the handler's module. If you did not define a handler it falls back
+to the default one inside the `Ewebmachine.Handlers` module.
+
+```elixir
+defmodule Ewebmachine.Core.DSL do
+ def resource_call(conn, state, fun) do
+ handler = conn.private[:resource_handlers][fun] || Ewebmachine.Handlers
+ {reply, conn, state} = term = apply(handler, fun, [conn, state])
+ # [ ... ]
+ end
+
+ # [ ... ]
+end
+```
+
+---
+
+Here is what the code would look like if we expand explained macros until now:
+
+```elixir
+defmodule MyApi do
+ @before_compile Ewebmachine.Builder.Resources
+ use Plug.Router
+ import Plug.Router, only: []
+ import Ewebmachine.Builder.Resources
+
+ defp resource_match(conn, _opts) do
+ conn |> match(nil) |> dispatch(nil)
+ end
+
+ @wm_routes [{"/api/path", MyApi.EWMApiPath, []}]
+end
+
+defmodule MyApi.EWMApiPath do
+ use Plug.Builder
+
+ @resource_handlers %{
+ allowed_methods: __MODULE__,
+ to_html: __MODULE__
+ }
+
+ def allowed_methods(conn, state) do
+ res = ["GET"]
+ {res, conn, state}
+ end
+
+ def to_html(conn, state) do
+ res = "HTML
"
+ {res, conn, state}
+ end
+
+ defp add_handlers(conn, _opts) do
+ # [...]
+ Plug.Conn.put_private(conn, :resource_handlers,
+ Enum.into(@resource_handlers, conn.private[:resource_handlers] || %{}))
+ end
+
+ plug :add_handlers
+end
+```
+
+### How does Ewebmachine call all of this?
+
+The missing piece of the puzzle is now, how does Ewebmachine call our plug
+module `MyApi.EWMApiPath`.
+
+From the macros' expansion above, we can see that it uses the `Plug.Router`.
+Moreover, the line `@before_compile Ewebmachine.Builder.Resources` isn't
+expanded, let's look into it. `Ewebmachine.Builder.Resources` calls the
+[`__before_compile__`
+macro](https://github.com/kbrw/ewebmachine/blob/b7659b9f5068cb188409d016d13635c6c4b74d6b/lib/ewebmachine/builder.resources.ex#L102-L119)
+does the following:
+
+```elixir
+defmacro __before_compile__(_env) do
+ wm_routes = Module.get_attribute __CALLER__.module, :wm_routes
+ route_matches = for {route,wm_module,init_block}<-Enum.reverse(wm_routes) do
+ quote do
+ Plug.Router.match unquote(route) do
+ init = unquote(init_block)
+ var!(conn) = put_private(var!(conn),:machine_init,init)
+ unquote(wm_module).call(var!(conn),[])
+ end
+ end
+ end
+ final_match = if !match?({"/*"<>_,_,_},hd(wm_routes)),
+ do: quote(do: Plug.Router.match _ do var!(conn) end)
+ quote do
+ unquote_splicing(route_matches)
+ unquote(final_match)
+ end
+end
+```
+
+which produces a [`Plug.Router`'s
+match](https://hexdocs.pm/plug/Plug.Router.html#match/3), giving us the
+following once expanded:
+
+```elixir
+defmodule MyApi do
+ use Plug.Router
+ import Plug.Router, only: []
+ import Ewebmachine.Builder.Resources
+
+ defp resource_match(conn, _opts) do
+ conn |> match(nil) |> dispatch(nil)
+ end
+
+ @wm_routes [{"/api/path", MyApi.EWMApiPath, :irrelevant_stuff}]
+
+ Plug.Router.match "/api/path" do
+ init = :irrelevant_stuff
+ conn = put_private(conn, :machine_init, init)
+ MyApiEWMApiPath.call(conn, [])
+ end
+
+ Plug.Router.match _ do conn
+end
+```
+
+The only thing left to make the whole thing work is to add a few plugs. That's
+what the `Ewebmachine.Builder.Resources.resources_plugs` macro usually does,
+but let's use only the required bits:
+
+```elixir
+defmodule MyApi do
+ # [...]
+ Plug.Router.match _ do conn
+
+ plug :resource_match
+ plug Ewebmachine.Plug.Run
+ plug Ewebmachine.Plug.Send
+end
+```
+
+The `:resource_match` function plug finds a matching route (`match(nil)`) and
+calls it if matching (`dispatch(nil)`). Once found the connection `conn` is
+returned by the plug module (for instance here `MyApiEWMApiPath`), and now
+contains our resource custom handlers.
+
+Then the `Ewebmachine.Plug.Run` plug, which contains the `Ewebmachine`'s
+decision tree, is called, and its behaviour will change based on our custom
+handlers.
+
+Finally, the `Ewebmachine.Plug.Send` plug is called and sends the response if
+the connection wasn't halted before.