Packages
mazurka
0.3.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
hypermedia api toolkit
Current section
Files
Jump to
Current section
Files
lib/mazurka/compiler.ex
defmodule Mazurka.Compiler do
alias Mazurka.Compiler.Utils
@doc """
Compile a resource with the environment attributes
"""
defmacro compile(env) do
{mediatypes, includes, globals} = Utils.get(env)
|> partition({%{}, %{}, %{}})
includes = includes
|> Enum.map(&(prepare_include(&1, env)))
|> :maps.from_list
globals = globals
|> Dict.put_new(Mazurka.Resource.Param, [])
|> Dict.put_new(Mazurka.Resource.Test, [])
|> Enum.map(&(prepare_global(&1, env)))
etude_modules = Enum.map(mediatypes, &(prepare_etude_module(&1, includes, env)))
clauses = Enum.flat_map(etude_modules, &(prepare_clauses(&1, env)))
body(env.module, clauses, globals)
end
defp partition(list, acc) when list == [] or list == nil do
acc
end
defp partition([{nil, name, ast, meta} | rest], {mediatypes, includes, globals}) do
name.module_info()
globals = if :erlang.function_exported(name, :compile_global, 2) do
put_acc(globals, name, ast, meta)
else
globals
end
includes = put_acc(includes, name, ast, meta)
partition(rest, {mediatypes, includes, globals})
end
defp partition([{mediatype, name, ast, meta} | rest], {mediatypes, includes, globals}) do
mt = mediatypes
|> Dict.get(mediatype, %{__default__: Map.size(mediatypes) == 0})
|> put_acc(name, ast, meta)
mediatypes = Dict.put(mediatypes, mediatype, mt)
partition(rest, {mediatypes, includes, globals})
end
defp put_acc(map, name, ast, meta) do
acc = Dict.get(map, name, [])
acc = [{ast, meta} | acc]
Dict.put(map, name, acc)
end
defp prepare_include({include, definitions}, env) do
key = format_name(include)
{key, include.compile(definitions, env)}
end
defp prepare_global({global, definitions}, env) do
global.compile_global(definitions, env)
end
defp prepare_etude_module({mediatype, definitions}, includes, env) do
module = env.module
etude_module = Module.concat([module, mediatype.name])
is_default = Map.get(definitions, :__default__)
content_types = definitions
|> Map.get(Mazurka.Resource.Provides)
|> Mazurka.Resource.Provides.format_types(mediatype.content_types())
definitions
|> Map.delete(:__default__)
|> Map.put_new(Mazurka.Resource.Action, [Mazurka.Resource.Action.default(module)])
|> Map.put_new(Mazurka.Resource.Affordance, [Mazurka.Resource.Affordance.default(module)])
|> Enum.flat_map(&(prepare_definition(&1, mediatype, includes)))
|> Utils.expand(%{env | module: etude_module})
|> Mazurka.Compiler.Etude.elixir_to_etude(etude_module)
|> compile(etude_module, env)
{mediatype, content_types, etude_module, is_default}
end
defp prepare_clauses({mediatype, content_types, etude_module, is_default}, env) do
module = env.module
[{default_type, default_subtype, default_params, _} | _] = content_types
for {type, subtype, params, content_type} <- content_types do
params = Macro.escape(params)
# TODO format params as well
resp_type = "#{type}/#{subtype}"
quote do
defp handle(unquote(type) = type, unquote(subtype) = subtype, unquote(params) = params, context, resolve) do
context = Mazurka.Runtime.put_mediatype(context, unquote(mediatype), {type, subtype, params})
Logger.debug("handling request with #{type}/#{subtype} in #{unquote(module)}")
prev = :erlang.get()
{out, context} = try do
unquote(etude_module).action(context, resolve)
rescue
err in Mazurka.Resource.Error ->
{err.message, err.state}
err in Etude.Exception ->
clear_cache(prev)
Plug.Conn.WrapperError.reraise(err.state, :error, err.error)
e ->
clear_cache(prev)
reraise(e, System.stacktrace())
end
clear_cache(prev)
# encode the response with the content type
out = if out != nil do
unquote(content_type).encode(out)
else
out
end
{:ok, out, context, unquote(resp_type)}
end
defp affordance(unquote(type), unquote(subtype), unquote(params), context, resolve, req, scope, props) do
unquote(etude_module).affordance_partial(context, resolve, req, scope, props)
end
end
end ++ if is_default do
[quote do
defp handle("*", "*", _, context, resolve) do
handle(unquote(default_type), unquote(default_subtype), unquote(Macro.escape(default_params)), context, resolve)
end
end]
else
[]
end
end
defp prepare_definition({handler, definitions}, mediatype, globals) do
for {ast, meta} <- definitions do
fn_name = format_name(handler, meta)
{fn_name, handler.compile(mediatype, ast, globals, meta)}
end
end
defp format_name(handler, meta \\ nil)
defp format_name(handler, nil) do
handler |> Module.split |> List.last |> String.downcase |> String.to_atom
end
defp format_name(handler, meta) do
handler.module_info()
if :erlang.function_exported(handler, :format_name, 1) do
handler.format_name(meta)
else
format_name(handler, nil)
end
end
defp compile(etude_ast, etude_module, env) do
## TODO read the existing beam file and verify it has changed before compiling
{:ok, _, _, beam} = Etude.compile(etude_module, etude_ast, [file: env.file, native: Mix.env == :prod])
# Make ex_doc happy
chunk_data = :erlang.term_to_binary({:elixir_docs_v1, [
docs: [],
moduledoc: {0, false},
behaviour_docs: []
]})
beam = :elixir_module.add_beam_chunk(beam, 'ExDc', chunk_data)
path = "#{Mix.Project.compile_path}/#{etude_module}.beam"
File.write!(path, beam)
:code.load_binary(etude_module, env.file |> to_char_list, beam)
end
@doc false
defp body(module, mediatypes, globals) do
quote do
@doc """
Handle a given request
"""
def action(request, resolve) do
handle("*", "*", %{}, request, resolve)
end
@doc """
Handle a given request, passing a list of acceptable mediatypes
"""
def action(request, resolve, []) do
handle("*", "*", %{}, request, resolve)
end
def action(request, resolve, [{_, {type, subtype, params}}]) do
handle(type, subtype, params, request, resolve)
end
def action(request, resolve, [{_, {type, subtype, params}} | accepts]) do
case handle(type, subtype, params, request, resolve) do
{:error, :unacceptable} ->
action(request, resolve, accepts)
res ->
res
end
end
def action(request, resolve, [{type, subtype, params} | accepts]) do
case handle(type, subtype, params, request, resolve) do
{:error, :unacceptable} ->
action(request, resolve, accepts)
res ->
res
end
end
@doc false
def affordance_partial(context, resolve, req, scope, props) do
{type, subtype, params} = Mazurka.Runtime.get_mediatype(context)
affordance(type, subtype, params, context, resolve, req, scope, props)
end
defp handle(type, subtype, params, request, resolve)
defp affordance(type, subtype, params, context, resolve, req, scope, props)
unquote_splicing(mediatypes)
defp handle(_, _, _, _, _) do
{:error, :unacceptable}
end
defp affordance(type, subtype, _, context, _, _, _, _) do
## TODO should we throw an exception? or fail silently?
## for now we fail silently.
Logger.info("no acceptable affordance was found for #{type}/#{subtype} in #{unquote(module)}")
{{:__ETUDE_READY__, :undefined}, context}
end
defp clear_cache(prev) do
:erlang.erase()
for {k, v} <- prev do
:erlang.put(k, v)
end
end
unquote_splicing(globals)
end
end
end