defmodule Mix.Tasks.Arch.Report.Html do @moduledoc """ Mix Task to generate a static HTML report. Usage: mix arch.report.html [options] Options: * `--limit` - Restricts the maximum number of items in the queries displayed in the report, Default is 10. For more information see: [Basic usage guide](guides/introduction/basic_usage.md#include-dependencies-in-the-analysis) Report is generated at `reports/dev/static/html`. """ @shortdoc "Generates a code analysis HTML report" require Logger require EEx use Mix.Task use Archeometer.Repo import Archeometer.Query alias Archeometer.Reports.PageDefinition alias Archeometer.Reports.Page alias Archeometer.Reports.Render alias Archeometer.Reports.Config alias Archeometer.Schema.Application @impl Mix.Task def run(argv) do prepare_paths() copy_assets() apps_in_db = Archeometer.Repo.all( from(a in Application, select: [name: a.name] ) ) |> Map.get(:rows) |> List.flatten() case parse_args(argv) do {:ok, args} -> _run(args, apps_in_db) copy_images() Mix.shell().info("HTML report ready at '#{report_path(:html)}'") {:error, error} -> Mix.shell().error("Error: #{inspect(error)}") print_help() end end defp _run(args, [app]) do create_app_page(app, args) end @index_page_name :index defp _run(args, apps_in_db) do page_names = [to_string(@index_page_name) | apps_in_db] project_apps = Archeometer.Explore.Project.apps() |> Enum.map(&to_string/1) extra_deps = Enum.filter(apps_in_db, &(&1 not in project_apps)) @index_page_name |> to_string() |> PageDefinition.Project.definition() |> create_page(page_names, limit: args.limit, extra_deps: extra_deps) apps_in_db |> Enum.each(&create_app_page(&1, args, page_names)) end defp create_app_page(app, args, page_names \\ []) do app |> to_string() |> PageDefinition.Application.definition() |> create_page(page_names, limit: args.limit) end defp create_page(%Page.Definition{} = page_def, page_names, opts) do page_fname = page_def.id limit = Keyword.get(opts, :limit, 10) extra_deps = Keyword.get(opts, :extra_deps, []) page_def |> render_page(page_names, app: page_def.id, limit: limit, extra_deps: extra_deps) |> write_to_file(page_fname <> ".html") end defp render_page(%Page.Definition{} = page_def, page_names, bindings) do page_def |> Page.process(bindings, default_db_name()) |> Render.Html.render(page_names) end defp write_to_file(str, fname) do path = Path.expand(fname, report_path(:html)) File.write!(path, str) end defp prepare_paths() do report_path = report_path(:html) [ Path.expand("img/", report_path), Path.expand("js/", report_path), Path.expand("css/", report_path) ] |> Enum.each(fn path -> File.mkdir_p!(path) end) report_path(:img) |> File.mkdir_p!() end defp copy_assets() do template_base_path = Archeometer.Util.PathHelper.template_path("report") report_path = report_path(:html) ["css", "js"] |> Enum.each(fn subdir -> src_path = Path.expand(subdir, template_base_path) dest_path = Path.expand(subdir, report_path) File.cp_r!(src_path, dest_path) end) end defp copy_images() do src_path = report_path(:img) report_path = report_path(:html) dest_path = Path.expand("img", report_path) File.cp_r!(src_path, dest_path) end defp report_path(:html), do: Config.report_path(:html) defp report_path(:img), do: Config.report_path(:img) defp parse_args(argv) do {opts, _data, invalid_switches} = OptionParser.parse( argv, strict: [limit: :integer] ) case invalid_switches do [] -> limit = Keyword.get(opts, :limit, 10) validate_options(limit) _ -> {:error, :wrong_argument} end end defp validate_options(limit) do if limit <= 0 do {:error, :invalid_limit} else {:ok, %{limit: limit}} end end defp print_help() do Mix.shell().info(""" Usage: mix arch.report.html [opts] opts: --limit 'integer' """) end end