defmodule FsBuild do
@external_resource "README.md"
@moduledoc "README.md"
|> File.read!()
|> String.split("<!-- MDOC -->")
|> Enum.fetch!(1)
@doc false
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
{from, paths} = FsBuild.__extract__(__MODULE__, opts)
for path <- paths do
@external_resource Path.relative_to_cwd(path)
end
def __mix_recompile__? do
unquote(from) |> Path.wildcard() |> Enum.sort() |> :erlang.md5() !=
unquote(:erlang.md5(paths))
end
end
end
@doc false
def __extract__(module, opts) do
builder = Keyword.fetch!(opts, :build)
from = Keyword.fetch!(opts, :from)
as = Keyword.fetch!(opts, :as)
{adapter_module, adapter_opts} = Keyword.fetch!(opts, :adapter)
:ok = adapter_module.init(adapter_opts)
paths = from |> Path.wildcard() |> Enum.sort()
entries =
Enum.flat_map(paths, fn path ->
content = File.read!(path)
transformed_content = adapter_module.transform(path, content, adapter_opts)
build_entry(builder, path, transformed_content)
end)
Module.put_attribute(module, as, entries)
{from, paths}
end
defp build_entry(builder, path, {_data, _metadata} = transformed_content) do
build_entry(builder, path, [transformed_content])
end
defp build_entry(builder, path, transformed_contents) when is_list(transformed_contents) do
Enum.map(transformed_contents, fn {data, metadata} ->
apply_builder(builder, [path, data, metadata])
end)
end
defp apply_builder(builder, args) when is_atom(builder) do
apply(builder, :build, args)
end
defp apply_builder(builder, args) when is_function(builder, 3) do
apply(builder, args)
end
defp apply_builder(_builder, _args) do
raise RuntimeError,
":build option should be a module which has build/3 function inside, " <>
"or a function whose arity number is 3"
end
end