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