Current section
Files
Jump to
Current section
Files
lib/nexus/cli/help.ex
defmodule Nexus.CLI.Help do
@moduledoc """
Provides functionality to display help messages based on the CLI AST.
"""
alias Nexus.CLI
alias Nexus.CLI.Command
@doc """
Displays help information for the given command path.
If no command path is provided, it displays the root help.
"""
@spec display(CLI.t(), list(atom)) :: :ok
def display(%CLI{} = cli, command_path \\ []) do
cmd = get_command(cli, command_path)
if cmd do
if function_exported?(cli.handler, :banner, 0) do
IO.puts(cli.handler.banner() <> "\n")
end
# Build usage line
usage = build_usage_line(cli.name, command_path, cmd)
IO.puts("Usage: #{usage}\n")
# Display description of the command
if cmd.description do
IO.puts("#{cmd.description}\n")
end
# Display subcommands, arguments, and options
display_subcommands(cmd)
display_arguments(cmd)
display_options(cmd)
# Final note
if cmd.subcommands != [] do
IO.puts(
"\nUse '#{cli.name} #{Enum.join(command_path, " ")} [COMMAND] --help' for more information on a command."
)
end
else
IO.puts("Command not found")
end
end
## Helper Functions
# Retrieves the command based on the command path
defp get_command(%CLI{} = cli, []) do
%Command{
name: cli.name,
description: cli.description,
subcommands: cli.spec,
flags: [],
args: []
}
end
defp get_command(%CLI{} = cli, [root | rest]) do
if root_cmd = Enum.find(cli.spec, &(&1.name == root)) do
get_subcommand(root_cmd, rest)
else
nil
end
end
defp get_subcommand(cmd, []) do
cmd
end
defp get_subcommand(cmd, [name | rest]) do
if subcmd = Enum.find(cmd.subcommands || [], &(&1.name == name)) do
get_subcommand(subcmd, rest)
else
nil
end
end
# Builds the usage line for the help output
defp build_usage_line(cli_name, command_path, cmd) do
parts = [cli_name | Enum.map(command_path, &Atom.to_string/1)]
# Include options
parts = if cmd.flags != [], do: parts ++ ["[OPTIONS]"], else: parts
# Include subcommands
parts =
if cmd.subcommands != [] do
parts ++ ["[COMMAND]"]
else
parts
end
# Include arguments
arg_strings =
Enum.map(cmd.args || [], fn arg ->
if arg.required, do: "<#{arg.name}>", else: "[#{arg.name}]"
end)
parts = parts ++ arg_strings
Enum.join(parts, " ")
end
# Displays subcommands if any
defp display_subcommands(cmd) do
if cmd.subcommands != [] do
IO.puts("Commands:")
Enum.each(cmd.subcommands, fn subcmd ->
IO.puts(" #{subcmd.name} #{subcmd.description || "No description"}")
end)
IO.puts("")
end
end
# Displays arguments if any
defp display_arguments(cmd) do
if cmd.args != [] do
IO.puts("Arguments:")
Enum.each(cmd.args, &display_arg/1)
IO.puts("")
end
end
defp display_arg(arg) do
arg_name = if arg.required, do: "<#{arg.name}>", else: "[#{arg.name}]"
IO.puts(" #{arg_name} Type: #{format_arg_type(arg.type)}")
end
# Displays options (flags), including the help option
defp display_options(cmd) do
all_flags = cmd.flags || []
if all_flags != [] do
IO.puts("Options:")
Enum.each(all_flags, &display_option/1)
end
end
defp display_option(flag) do
short = if flag.short, do: "-#{flag.short}, ", else: " "
type = if flag.type != :boolean, do: " <#{String.upcase(to_string(flag.type))}>", else: ""
IO.puts(" #{short}--#{flag.name}#{type} #{flag.description || "No description"}")
end
# Formats the argument type for display
defp format_arg_type({:list, type}), do: "List of #{inspect(type)}"
defp format_arg_type({:enum, values}), do: "One of #{inspect(values)}"
defp format_arg_type(type), do: "#{inspect(type)}"
end