defmodule Protobuf.Builder do @moduledoc false alias Protobuf.Config import Protobuf.DefineEnum, only: [def_enum: 3] import Protobuf.DefineMessage, only: [def_message: 3] def define(msgs, %Config{inject: inject} = config) do # When injecting, use_in is not available, so we don't need to use @before_compile if inject do quote location: :keep do Module.register_attribute __MODULE__, :use_in, accumulate: true import unquote(__MODULE__), only: [use_in: 2] @config unquote(Macro.escape Map.to_list(%{config | :schema => nil})) @msgs unquote(Macro.escape msgs) contents = unquote(__MODULE__).generate(@msgs, @config) Module.eval_quoted __MODULE__, contents, [], __ENV__ end else quote do Module.register_attribute __MODULE__, :use_in, accumulate: true import unquote(__MODULE__), only: [use_in: 2] @config unquote(Macro.escape Map.to_list(%{config | :schema => nil})) @msgs unquote(Macro.escape msgs) @before_compile unquote(__MODULE__) end end end defmacro __before_compile__(_env) do quote location: :keep do contents = unquote(__MODULE__).generate(@msgs, @config) Module.eval_quoted __MODULE__, contents, [], __ENV__ end end # Cache use instructions defmacro use_in(module, use_module) do module = :"#{__CALLER__.module}.#{module}" use_module = quote do: use(unquote(use_module)) quote location: :keep do @use_in {unquote(module), unquote(Macro.escape(use_module)) } end end # Generate code of records (message and enum) def generate(msgs, config) do only = Keyword.get(config, :only, []) inject = Keyword.get(config, :inject, false) ns = Keyword.get(config, :namespace) quotes = for {{item_type, item_name}, fields} <- msgs, item_type in [:msg, :enum], into: [] do if only != [] do is_child? = Enum.any?(only, fn o -> o != item_name and is_child_type?(item_name, o) end) if item_name in only or is_child? do case item_type do :msg when is_child? -> def_message(item_name |> fix_ns(ns), fields, inject: false) :msg -> def_message(ns, fields, inject: inject) :enum when is_child? -> def_enum(item_name |> fix_ns(ns), fields, inject: false) :enum -> def_enum(ns, fields, inject: inject) _ -> [] end end else case item_type do :msg -> def_message(item_name, fields, inject: false) :enum -> def_enum(item_name, fields, inject: false) _ -> [] end end end # Global defs helper quotes ++ [quote do def defs do unquote(Macro.escape(msgs, unquote: true)) end end] end defp is_child_type?(child, type) do [parent|_] = child |> Atom.to_string |> String.split(".", parts: :infinity) Atom.to_string(type) == parent end defp fix_ns(name, ns) do name_parts = name |> Atom.to_string |> String.split(".", parts: :infinity) ns_parts = ns |> Atom.to_string |> String.split(".", parts: :infinity) module = name_parts -- ns_parts |> Enum.join |> String.to_atom :"#{ns}.#{module}" end end