lib/mix/tasks/archeometer.collect.xrefs.ex

defmodule Mix.Tasks.Arch.Collect.Xrefs do
  use Mix.Task
  alias Archeometer.Collect.Project

  @moduledoc """
  Mix Task to analyze internal dependencies between modules and store them into
  a Archeometer database.

  Usage:

      mix arch.collect.xrefs [options]

  The following options are accepted:

    * `--include-deps` - Wildcard glob style to filter dependencies

  For more information see:
  [Basic usage guide](guides/introduction/basic_usage.md#include-dependencies-in-the-analysis)
  """

  @shortdoc "Runs cross reference analysis"

  @impl Mix.Task
  def run(argv) do
    case get_args(argv) do
      {:error, error} ->
        Mix.shell().error("Error: #{error}")
        print_help()
        {:error, error}

      [
        include_deps: deps_filter
      ] ->
        if Archeometer.Repo.db_ready?(:basic) do
          Mix.shell().info("Starting cross reference analysis..")

          xrefs =
            append_deps(deps_filter)
            |> Archeometer.Collect.XRef.compile_xrefs()

          Mix.shell().info("Saving cross references...")
          Archeometer.Util.DumpStats.save_xrefs(xrefs)

          Mix.shell().info("Done!")
        else
          Mix.shell().error("Please run static analysis first")
          print_help()
          {:error, :no_static_analysis_found}
        end
    end
  end

  defp get_args(argv) do
    {opts, _args, invalid} =
      OptionParser.parse(
        argv,
        strict: [
          include_deps: :keep
        ]
      )

    case invalid do
      [] ->
        deps_filter = Keyword.get_values(opts, :include_deps)

        validate_options(deps_filter)

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

  defp validate_options(deps_filter) do
    case Project.filter_deps(deps_filter) do
      [] ->
        {:error, "Filter doesn't match any dependency"}

      _ ->
        [
          include_deps: deps_filter
        ]
    end
  end

  defp append_deps(deps_filter) do
    deps = Project.filter_deps(deps_filter)
    if deps, do: Keyword.keys(deps), else: []
  end

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

    opts: --include-deps 'deps_filter'

    Where `deps_filter` is a glob Unix-like pattern, matches dependencies name.
    --include-deps can be used more than once.

    - `*` matches none or many tokens
    - `?` matches exactly one token
    - `[abc]` matches a set of tokens
    - `[a-z]` matches a range of tokens
    - `[!...]` matches anything but a set of tokens
    """)
  end
end