lib/mix/tasks/archeometer.apps.xref.ex

defmodule Mix.Tasks.Arch.Apps.Xref do
  @moduledoc """
  Mix Task to generate a dependency graph for the applications within an umbrella.

  Usage:

      mix arch.apps.xref [options]

  The following options are accepted:

    * `--format` - Can be one of `dot`, `png` or `svg`
    * `--out` - Output filename

  """
  @shortdoc "Generates a dependency graph of applications"

  require Logger
  require EEx
  use Mix.Task

  @supported_formats ["png", "dot", "mermaid", "svg"]

  @impl Mix.Task
  def run(argv) do
    case parse_args(argv) do
      {:ok, [format: format, out: out_file, db: db]} ->
        Archeometer.Analysis.Apps.Xref.gen_graph(format, db)
        |> write(out_file)

        Mix.shell().info("Graph ready at: '#{out_file}'")

      {:error, error} ->
        Mix.shell().error("Error: #{error}")
        print_help()
    end
  end

  defp parse_args(argv) do
    {opts, _, invalid_switches} =
      OptionParser.parse(
        argv,
        strict: [
          format: :string,
          out: :string,
          db: :string
        ]
      )

    case invalid_switches do
      [] ->
        format = Keyword.get(opts, :format, "dot")
        out_fname = Keyword.get(opts, :out, "console")
        db_name = Keyword.get(opts, :db, Archeometer.Repo.default_db_name())

        case validate_options(%{format: format, out: out_fname, db: db_name}) do
          :ok ->
            {:ok,
             [
               format: format,
               out: out_fname,
               db: db_name
             ]}

          {:error, error} ->
            {:error, error}
        end

      _ ->
        {:error, :wrong_arguments}
    end
  end

  defp validate_options(%{format: format, out: out_fname, db: db_name}) do
    cond do
      not Archeometer.Repo.db_ready?(:full, db_name) ->
        {:error, "Database is not existent or doesn't have the required tables."}

      format not in @supported_formats ->
        {:error, :unsupported_output_format}

      format == "png" and out_fname == "console" ->
        {:error, :png_not_writable_to_console}

      true ->
        :ok
    end
  end

  defp print_help() do
    Mix.shell().info("""
    Usage: mix arch.apps.xref [opts]

    opts: --format (dot, png), --out out_fname
    """)
  end

  defp write(content, "console") do
    Mix.shell().info(content)
  end

  defp write(content, out_fname) do
    File.write!(out_fname, content)
  end
end