defmodule FsBuild do @external_resource "README.md" @moduledoc "README.md" |> File.read!() |> String.split("") |> 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