Skip to content

Instantly share code, notes, and snippets.

@asonge
Created March 30, 2014 22:37
Show Gist options
  • Save asonge/9881111 to your computer and use it in GitHub Desktop.
Save asonge/9881111 to your computer and use it in GitHub Desktop.
defmodule Rout.Router do
defmacro __using__(_opts) do
quote do
import unquote(__MODULE__)
Module.register_attribute(__MODULE__, :route, [persist: true, accumulate: true])
@before_compile unquote(__MODULE__)
end
end
defmacro route(verb, path, opts \\ []), do: make_route(verb, path, opts)
defmacro get(path, opts \\ []), do: make_route(:get, path, opts)
defmacro delete(path, opts \\ []), do: make_route(:delete, path, opts)
defmacro post(path, opts \\ []), do: make_route(:post, path, opts)
defmacro put(path, opts \\ []), do: make_route(:put, path, opts)
defmacro __before_compile__(_env) do
quote do
def match(verb, str), do: match(verb, str, [])
def match(verb, str, opts) when is_binary(str) do
pathlist = String.split(str, "/")
match(verb, {Enum.count(pathlist), pathlist}, opts)
end
def get_routes(), do: Enum.reverse(@route)
end
end
# Parse the path into forms
def parse_spec(path), do: String.split(path, "/") |> parse0([], [])
defp parse0([], path_spec, opts_spec) do
{:ok, Enum.reverse(path_spec),
Enum.reverse(opts_spec),
Enum.count(path_spec)
}
end
defp parse0([<<":"<>path>>|rest], path_spec, opts_spec) do
varname = binary_to_atom(path)
parse0(rest, [{varname, [], __MODULE__}|path_spec], [varname|opts_spec])
end
defp parse0([<<"*">>], path_spec, opts_spec) do
parse0([<<"*"<>"path">>], path_spec, opts_spec)
end
defp parse0([<<"*"<>path>>], path_spec, opts_spec) do
varname = binary_to_atom(path)
{:ok, Enum.reverse(path_spec),
{varname, Enum.reverse(opts_spec)},
Enum.count(path_spec)
}
end
defp parse0([<<"*"<>_>>|_rest], _path_spec, _opts_spec) do
raise ArgumentError.new("*path pattern must be the last item in the list")
end
defp parse0([part|rest], path_spec, opts_spec) do
parse0(rest, [part|path_spec], opts_spec)
end
def make_route(verb, path, opts) do
case parse_spec(path) do
{:ok, path_spec, {rest_name, opts_spec}, len} ->
quote do
@route {unquote(verb), unquote(path), unquote(opts)}
def match(unquote(verb),
{r__n, [unquote_splicing(path_spec)|r__rest]},
r__opts) when r__n > unquote(len) do
unquote(__MODULE__).make_match(
r__opts ++ unquote(opts),
[{ unquote(rest_name), Enum.join(r__rest, "/") } |
unquote(Enum.map(opts_spec, &{ &1, {&1,[],__MODULE__} }))]
)
end
end
{:ok, path_spec, opts_spec, len} ->
quote do
@route {unquote(verb), unquote(path), unquote(opts)}
def match(unquote(verb), {unquote(len), unquote(path_spec)}, r__opts) do
unquote(__MODULE__).make_match(
r__opts ++ unquote(opts),
unquote(Enum.map(opts_spec, &{ &1, {&1,[],__MODULE__} }))
)
end
end
end
end
def make_match(opts, args) do
args = Dict.merge(args, Dict.get(opts, :args, []))
Dict.put(opts, :args, args)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment