Files
n8n-openai-adapter/lib/elixir/lib/record/extractor.ex
T
2018-01-10 09:41:35 +01:00

118 lines
3.4 KiB
Elixir

defmodule Record.Extractor do
@moduledoc false
def extract(name, opts) do
extract_record(name, from_or_from_lib_file(opts))
end
def extract_all(opts) do
extract_all_records(from_or_from_lib_file(opts))
end
defp from_or_from_lib_file(opts) do
cond do
file = opts[:from] ->
{from_file(file), Keyword.delete(opts, :from)}
file = opts[:from_lib] ->
{from_lib_file(file), Keyword.delete(opts, :from_lib)}
true ->
raise ArgumentError, "expected :from or :from_lib to be given as option"
end
end
# Find file using the same lookup as the *include* attribute from Erlang modules.
defp from_file(file) do
file = String.to_charlist(file)
case :code.where_is_file(file) do
:non_existing -> file
realfile -> realfile
end
end
# Find file using the same lookup as the *include_lib* attribute from Erlang modules.
defp from_lib_file(file) do
[app | path] = :filename.split(String.to_charlist(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
# Retrieve the record with the given name from the given file
defp extract_record(name, {file, opts}) do
form = read_file(file, opts)
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}. Or the record does not exist or " <>
"its entry is malformed or depends on other include files"
end
end
# Retrieve all records from the given file
defp extract_all_records({file, opts}) do
form = read_file(file, opts)
records = extract_records(form)
for rec = {name, _fields} <- records, do: {name, parse_record(rec, form)}
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 but with macros and other attributes expanded,
# such as "-include(...)" and "-include_lib(...)". This is done
# by using Erlang's epp.
defp read_file(file, opts) do
case :epp.parse_file(file, opts) 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