Current section

Files

Jump to
doctor lib cli cli.ex
Raw

lib/cli/cli.ex

defmodule Doctor.CLI do
@moduledoc """
Provides the various CLI task entry points and CLI arg parsing.
"""
alias Mix.Project
alias Doctor.{ModuleInformation, ModuleReport, ReportUtils}
@doc """
Given the CLI arguments, run the report on the project,
"""
def run_report(args) do
# Using the project's app name, fetch all the modules associated with the app
module_report_list =
Project.config()
|> Keyword.get(:app)
|> get_application_modules()
# Fetch the module information from the list of application modules
|> Enum.map(&generate_module_entry/1)
# Filter out any files/modules that were specified in the config
|> Enum.reject(fn module_info -> module_info.module in args.ignore_modules end)
|> Enum.reject(fn module_info -> filter_ignore_paths(module_info.file_relative_path, args.ignore_paths) end)
# Asynchronously get the user defined functions from the modules
|> Enum.map(&async_fetch_user_defined_functions/1)
|> Enum.map(&Task.await(&1, 15_000))
# Build report struct for each module
|> Enum.sort(&(&1.file_relative_path < &2.file_relative_path))
|> Enum.map(&ModuleReport.build/1)
# Invoke the configured module reporter and return whether Doctor validation passed/failed
args.reporter.generate_report(module_report_list, args)
ReportUtils.doctor_report_passed?(module_report_list, args)
end
defp generate_module_entry(module) do
module
|> Code.fetch_docs()
|> ModuleInformation.build(module)
end
defp async_fetch_user_defined_functions(%ModuleInformation{} = module_info) do
Task.async(fn ->
module_info
|> ModuleInformation.load_file_ast()
|> ModuleInformation.load_user_defined_functions()
end)
end
defp get_application_modules(application) do
# Compile and load the application
Mix.Task.run("compile")
Application.load(application)
# Get all the modules in the application
{:ok, modules} = :application.get_key(application, :modules)
modules
end
defp filter_ignore_paths(file_relative_path, ignore_paths) do
ignore_paths
|> Enum.reduce_while(false, fn pattern, _acc ->
compare_ignore_path(file_relative_path, pattern)
end)
end
defp compare_ignore_path(file_relative_path, %Regex{} = ignore_pattern) do
if Regex.match?(ignore_pattern, file_relative_path) do
{:halt, true}
else
{:cont, false}
end
end
defp compare_ignore_path(file_relative_path, ignore_string) when is_bitstring(ignore_string) do
if file_relative_path == ignore_string do
{:halt, true}
else
{:cont, false}
end
end
defp compare_ignore_path(_, ignore_value),
do: raise("Encountered invalid ignore_paths entry: #{inspect(ignore_value)}")
end