Current section
Files
Jump to
Current section
Files
lib/gust/dag/parser/file.ex
defmodule Gust.DAG.Parser.File do
@moduledoc false
@behaviour Gust.DAG.Parser
alias Gust.DAG.Definition
alias Gust.DAG.Graph
@impl true
def parse(file_path) do
if File.exists?(file_path) do
parse_file(file_path)
else
{:error, :enoent}
end
end
defp parse_file(file_path) do
if is_nil(maybe_using_dsl(file_path)) do
{:error, :dsl_not_found}
else
define_dag(file_path)
end
end
defp define_dag(file_path) do
name = Path.basename(file_path, ".ex")
dag_def = default_dag_def(name, file_path)
dag_def =
case compile(file_path) do
{:error, error, messages} ->
%{dag_def | error: error, messages: messages}
{:ok, mod} ->
task_list = build_task_list(mod)
all_tasks = list_tasks(mod)
tasks = Graph.link_tasks(all_tasks) |> put_store_result(all_tasks)
options = options(mod)
stages = build_stages(mod)
:code.purge(mod)
:code.delete(mod)
%{
dag_def
| mod: mod,
tasks: tasks,
task_list: task_list,
options: options,
stages: stages
}
end
{:ok, dag_def}
end
defp default_dag_def(name, file_path) do
%Definition{name: name, file_path: file_path}
end
defp put_store_result(tasks, all_tasks) do
for {t_name, opts} <- tasks, into: %{} do
{t_name, Map.put(opts, :store_result, all_tasks[String.to_atom(t_name)][:store_result])}
end
end
defp build_stages(mod) do
list_tasks(mod)
|> Graph.link_tasks()
|> Graph.to_stages()
|> then(fn {:ok, stages} -> stages end)
end
defp build_task_list(mod) do
build_stages(mod)
|> List.flatten()
end
defp options(mod) do
# TODO: Validate schedule..
mod.__dag_options__()
end
defp list_tasks(mod) do
mod.__dag_tasks__()
end
@impl true
def maybe_ex_file(path) do
if Path.extname(path) == ".ex", do: path, else: nil
end
@impl true
def maybe_valid_mod(path) do
Code.with_diagnostics(fn ->
try do
[{module, _}] = Code.compile_file(path)
:code.purge(module)
:code.delete(module)
path
rescue
_err ->
nil
end
end)
|> elem(0)
end
@impl true
def maybe_using_dsl(nil), do: nil
@impl true
def maybe_using_dsl(path) do
content = File.read!(path)
{:ok, ast} = Code.string_to_quoted(content)
found_use_dsl =
Macro.prewalker(ast)
|> Enum.filter(fn
{:use, _meta, [{:__aliases__, _, [:Gust, :DSL]} | _config]} ->
true
_node ->
false
end)
|> length() > 0
if found_use_dsl, do: path, else: nil
end
defp compile(file) do
code_result =
Code.with_diagnostics(fn ->
try do
compiled = Code.compile_file(file) |> List.first()
{:ok, compiled}
rescue
err -> {:error, err}
end
end)
case code_result do
{{:ok, {dag_module, _}}, _warnings} ->
{:ok, dag_module}
{{:error, error_type}, errors} ->
{:error, error_type, errors}
end
end
end