defmodule Mix.Tasks.Arch.Treemap do @moduledoc """ Mix Task to generate a treemap for an application or a set of modules. Usage: mix arch.treemap [options] The following options are accepted: * `--metric` - Temporarily there is just one metric: size (default) * `--ns` - Namespace of the modules considered * `--app` - Application name * `--db` - Database filename * `--out` - Output filename * `--skip-tests` - Skips test related modules (default) * `--format` - Temporarily there is just one format: svg (default) """ @shortdoc "Generates a Treemap diagram" require Logger use Mix.Task use Archeometer.Repo alias Archeometer.Analysis.Treemap alias Archeometer.Analysis.Treemap.SVGRender @impl Mix.Task def run(argv) do case get_args(argv) do [ metric: metric, app: app, namespace: namespace, out: out_fname, db_name: db_name, skip_tests: skip_tests ] -> case treemap_analysis(String.to_atom(metric), app, namespace, db_name, skip_tests) do {:ok, svg} -> write(svg, out_fname) IO.puts(:stderr, "Diagram ready at: '#{out_fname}'") {:error, e} = error -> Mix.shell().error("Error: #{e}") Mix.shell().error("No Treemap is generated.") error end {:error, e} = error -> Mix.shell().error("Error: #{e}") print_help() error end end defp write(svg, "console") do IO.puts(svg) end defp write(svg, file_name) do file_name |> Path.dirname() |> File.mkdir_p() File.write(file_name, svg) end defp get_args(argv) do {opts, _args, invalid} = OptionParser.parse( argv, strict: [ metric: :string, app: :string, ns: :string, out: :string, format: :string, db: :string, skip_tests: :boolean ] ) case invalid do [] -> metric = Keyword.get(opts, :metric, "size") app = Keyword.get(opts, :app, :none) namespace = Keyword.get(opts, :ns, "*") out = Keyword.get(opts, :out, "console") db_name = Keyword.get(opts, :db, default_db_name()) skip_tests = Keyword.get(opts, :skip_tests, true) format = Keyword.get(opts, :format, "svg") case validate_options(%{metric: metric, db: db_name, format: format}) do :ok -> [ metric: metric, app: app, namespace: namespace, out: out, db_name: db_name, skip_tests: skip_tests ] {:error, error} -> {:error, error} end _ -> {:error, :wrong_arguments} end end defp validate_options(%{metric: metric, db: db_name, format: format}) do cond do # For now only the 'size' metric is supported but we pretend to support different metrics in the near future. metric not in ["size"] -> {:error, :metric_not_supported} not Archeometer.Repo.db_ready?(:full, db_name) -> {:error, "Database is not existent or doesn't have the required tables."} # For now only svg format is supported but we hope to add more formats in the future. format not in ["svg"] -> {:error, :unsupported_output_format} true -> :ok end end defp treemap_analysis(metric, app, namespace, db_name, skip_tests) do case Treemap.treemap(metric, app, namespace, db_name, skip_tests) do {:error, error} -> {:error, error} tree -> {:ok, SVGRender.render(tree)} end end defp print_help() do Mix.shell().info(""" Usage: mix arch.treemap [opts] namespace opts: --metric metric --app app --ns namespace --db db_file_name --format format (svg) --out fname --skip-tests (default) """) end end