-
-
Save nwalker/b452c82a7b861f740b52b74346ccf211 to your computer and use it in GitHub Desktop.
Approach for constants shared between modules in Elixir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Constants do | |
defmodule Extractor do | |
require Record | |
Record.defrecord :tree, [:type, :pos, data: :undefined] | |
Record.defrecord :attr, :attribute, [:name, :args] | |
def extract(opts) do | |
cond do | |
lib = opts[:from_lib] -> from_lib_file(lib) | |
file = opts[:from_file] -> from_file(file) | |
end |> read_forms() |> decode_forms() | |
end | |
defp from_file(file) do | |
file = String.to_char_list(file) | |
case :code.where_is_file(file) do | |
:non_existing -> file | |
realfile -> realfile | |
end | |
end | |
def from_lib_file(file) do | |
[app|path] = :filename.split(String.to_char_list(file)) | |
case :code.lib_dir(List.to_atom(app)) do | |
{:error, _} -> | |
raise ArgumentError, "lib file #{file} could not be found" | |
libpath -> | |
:filename.join([libpath|path]) | |
end | |
end | |
def read_forms(filename) do | |
case :epp_dodger.parse_file(filename, []) do | |
{:ok, forms} -> forms | |
other -> raise ArgumentError, "error parsing file #{filename}, got #{inspect other}" | |
end | |
end | |
def decode_forms(forms) do | |
for tree(type: :attribute, data: attr) <- forms do | |
decode_attribute(attr) | |
end | |
|> Enum.filter_map(&(!is_nil(&1)), &rename_constant/1) | |
end | |
def rename_constant({key, val}) do | |
name = key |> Atom.to_string |> String.downcase |> String.to_atom | |
{name, val} | |
end | |
def decode_attribute(attr(name: {:atom, _, :define}, args: args)) do | |
with [arg1, arg2] <- args, | |
{:var, _pos, name} <- arg1, | |
{:ok, val} <- decode_val(arg2) | |
do | |
{:ok, name, val} | |
end |> case do | |
{:ok, name, val} -> {name, val} | |
_other -> nil | |
end | |
end | |
def decode_attribute(_), do: nil | |
def decode_val(tree(type: :binary, data: data)) do | |
extract_constant(:binary, data) | |
end | |
def decode_val(_), do: :unsupported_type | |
def extract_constant(:binary, [tree(type: :binary_field, data: data)]) do | |
with {:binary_field, f, _} <- data, | |
{:string, _, s} <- f, do: {:ok, to_string(s)} | |
end | |
def extract_constant(_type, _tree), do: nil | |
end | |
defmacro __using__(_opts) do | |
quote do | |
import Constants | |
end | |
end | |
defdelegate(extract(opts), to: Constants.Extractor) | |
@doc "Define a constant. An alias for constant" | |
defmacro define(name, value) do | |
quote do | |
defmacro unquote(name), do: unquote(value) | |
end | |
end | |
@doc "Define multiple constants" | |
defmacro define_all(constants) do | |
quote bind_quoted: [constants: constants, line: __CALLER__.line], location: :keep do | |
for {name, value} <- constants do | |
defmacro unquote({name, [line: line], Elixir}), do: unquote(value) | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule NSs do | |
use Constants | |
define_all Constants.extract(from_lib: "ejabberd/include/ns.hrl") | |
end | |
defmodule ConstantsTest do | |
use ExUnit.Case | |
alias Constants.Extractor, as: CE | |
require NSs | |
test "check extractor" do | |
d = CE.extract(from_lib: "ejabberd/include/ns.hrl") | |
assert Enum.count(d) > 0 | |
end | |
test "check defined constants" do | |
assert NSs.ns_auth == "jabber:iq:auth" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wish I had seen this fork years ago before I coded essentially the same...