93 lines
2.7 KiB
Elixir
93 lines
2.7 KiB
Elixir
defmodule Record.Extractor do
|
|
@moduledoc false
|
|
|
|
# Retrieve a record definition from an Erlang file using
|
|
# the same lookup as the *include* attribute from Erlang modules.
|
|
def extract(name, from: file) when is_binary(file) do
|
|
file = String.to_char_list(file)
|
|
|
|
realfile =
|
|
case :code.where_is_file(file) do
|
|
:non_existing -> file
|
|
realfile -> realfile
|
|
end
|
|
|
|
extract_record(name, realfile)
|
|
end
|
|
|
|
# Retrieve a record definition from an Erlang file using
|
|
# the same lookup as the *include_lib* attribute from Erlang modules.
|
|
def extract(name, from_lib: file) when is_binary(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 ->
|
|
extract_record name, :filename.join([libpath|path])
|
|
end
|
|
end
|
|
|
|
# Retrieve the record with the given name from the given file
|
|
defp extract_record(name, file) do
|
|
form = read_file(file)
|
|
records = extract_records(form)
|
|
if record = List.keyfind(records, name, 0) do
|
|
parse_record(record, form)
|
|
else
|
|
raise ArgumentError, "no record #{name} found at #{file}"
|
|
end
|
|
end
|
|
|
|
# Parse the given file and extract all existent records.
|
|
defp extract_records(form) do
|
|
for {:attribute, _, :record, record} <- form, do: record
|
|
end
|
|
|
|
# Read a file and return its abstract syntax form that also
|
|
# includes record and other preprocessor modules. This is done
|
|
# by using Erlang's epp_dodger.
|
|
defp read_file(file) do
|
|
case :epp_dodger.quick_parse_file(file) do
|
|
{:ok, form} ->
|
|
form
|
|
other ->
|
|
raise "error parsing file #{file}, got: #{inspect(other)}"
|
|
end
|
|
end
|
|
|
|
# Parse a tuple with name and fields and returns a
|
|
# list of tuples where the first element is the field
|
|
# and the second is its default value.
|
|
defp parse_record({_name, fields}, form) do
|
|
cons = List.foldr fields, {nil, 0}, fn f, acc ->
|
|
{:cons, 0, parse_field(f), acc}
|
|
end
|
|
eval_record(cons, form)
|
|
end
|
|
|
|
defp parse_field({:typed_record_field, record_field, _type}) do
|
|
parse_field(record_field)
|
|
end
|
|
|
|
defp parse_field({:record_field, _, key}) do
|
|
{:tuple, 0, [key, {:atom, 0, :undefined}]}
|
|
end
|
|
|
|
defp parse_field({:record_field, _, key, value}) do
|
|
{:tuple, 0, [key, value]}
|
|
end
|
|
|
|
defp eval_record(cons, form) do
|
|
form = form ++
|
|
[ {:function, 0, :hello, 0, [
|
|
{:clause, 0, [], [], [ cons ]} ]} ]
|
|
|
|
{:function, 0, :hello, 0, [
|
|
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
|
|
|
|
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
|
record
|
|
end
|
|
end
|