defmodule DoIt.Output do @moduledoc """ This module formats command help outputs. """ alias DoIt.{Argument, Option} @doc """ It gets the length of the longer name attribute. ## Examples iex> DoIt.Output.longer_name([%{name: "great"}, %{name: "greatest"}]) 8 iex> DoIt.Output.longer_name([%{name: "Elixir"}, %{name: "Erlang"}, %{name: "DoIt"}, %{name: "OTP"}]) 6 """ def longer_name(list) do list |> Enum.map(fn %{name: name} -> "#{name}" end) |> Enum.max_by(&String.length/1) |> String.length() end @doc """ It formats the given `DoIt.Argument` name attribute with spaces on the right, accordingly with the `align` parameter. ## Examples iex> DoIt.Output.format_argument_name(%DoIt.Argument{name: :verbose, type: :boolean, description: "Makes the command verbose"}, 15) "verbose " """ def format_argument_name(%Argument{name: name}, align), do: "#{String.pad_trailing(Atom.to_string(name), align)}" @doc """ It returns the description from `DoIt.Argument`. ## Example iex> DoIt.Output.format_argument_description(%DoIt.Argument{name: :verbose, type: :boolean, description: "Makes the command verbose"}) "Makes the command verbose" """ def format_argument_description(%Argument{description: description}), do: description @doc """ It returns the allowed values from the given `DoIt.Argument`. ## Examples iex> DoIt.Output.format_argument_allowed_values(%DoIt.Argument{name: :op, type: :string, description: "Operation", allowed_values: ["+", "-", "*", "/"]}) " (Allowed Values: \\"+\\", \\"-\\", \\"*\\", \\"/\\")" iex> DoIt.Output.format_argument_allowed_values(%DoIt.Argument{name: :verbose, type: :boolean, description: "Makes the command verbose"}) "" iex> DoIt.Output.format_argument_allowed_values(%DoIt.Argument{name: :number, type: :integer, description: "Numerical digit", allowed_values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}) " (Allowed Values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)" """ def format_argument_allowed_values(%Argument{allowed_values: nil}), do: "" def format_argument_allowed_values(%Argument{type: :string, allowed_values: allowed_values}), do: " (Allowed Values: \"#{Enum.join(allowed_values, "\", \"")}\")" def format_argument_allowed_values(%Argument{allowed_values: allowed_values}), do: " (Allowed Values: #{Enum.join(allowed_values, ", ")})" @doc """ It formats the given `DoIt.Option` alias attribute. ## Examples iex> DoIt.Output.format_option_alias(%DoIt.Option{name: :help, type: :boolean, description: "Shows the help command", alias: nil}) " " iex> DoIt.Output.format_option_alias(%DoIt.Option{name: :help, type: :boolean, description: "Shows the help command", alias: :h}) "-h," """ def format_option_alias(%Option{alias: nil}), do: " " def format_option_alias(%Option{alias: alias}), do: "-#{Atom.to_string(alias)}," @doc """ It formats the given `DoIt.Option` name attribute with spaces on the right, accordingly with the `align` parameter. ## Examples iex> DoIt.Output.format_option_name(%DoIt.Option{name: :help, type: :boolean, description: "Shows the help command", alias: nil}, 10) "--help " iex> DoIt.Output.format_option_name(%DoIt.Option{name: :log_level, type: :string, description: "Set the logging level", alias: nil}, 12) "--log-level " """ def format_option_name(%Option{name: name}, align), do: "--#{name |> Atom.to_string() |> String.replace("_", "-") |> String.pad_trailing(align)}" @doc """ It returns the description from `DoIt.Option`. ## Examples iex> DoIt.Output.format_option_description(%DoIt.Option{name: :help, type: :boolean, description: "Shows the help command", alias: nil}) "Shows the help command" """ def format_option_description(%Option{description: description}), do: description @doc """ It formats the given `DoIt.Option` default attribute. ## Examples iex> DoIt.Output.format_option_default(%DoIt.Option{name: :log_level, type: :string, description: "Set the logging level", alias: nil, default: "warn"}) " (Default: \\"warn\\")" iex> DoIt.Output.format_option_default(%DoIt.Option{name: :skip_lines, type: :integer, description: "Lines to skip", alias: nil, default: 10}) " (Default: 10)" iex> DoIt.Output.format_option_default(%DoIt.Option{name: :help, type: :boolean, description: "Shows the help command", alias: nil}) "" """ def format_option_default(%Option{default: nil}), do: "" def format_option_default(%Option{type: :string, default: default}), do: " (Default: \"#{default}\")" def format_option_default(%Option{default: default}), do: " (Default: #{default})" def format_option_allowed_values(%Option{allowed_values: nil}), do: "" @doc """ It returns the allowed values from the given `DoIt.Option`. ## Examples iex> DoIt.Output.format_option_allowed_values(%DoIt.Option{name: :op, type: :string, description: "Operation", allowed_values: ["+", "-", "*", "/"]}) " (Allowed Values: \\"+\\", \\"-\\", \\"*\\", \\"/\\")" iex> DoIt.Output.format_option_allowed_values(%DoIt.Option{name: :verbose, type: :boolean, description: "Makes the command verbose"}) "" iex> DoIt.Output.format_option_allowed_values(%DoIt.Option{name: :number, type: :integer, description: "Numerical digit", allowed_values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}) " (Allowed Values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)" """ def format_option_allowed_values(%Option{type: :string, allowed_values: allowed_values}), do: " (Allowed Values: \"#{Enum.join(allowed_values, "\", \"")}\")" def format_option_allowed_values(%Option{allowed_values: allowed_values}), do: " (Allowed Values: #{Enum.join(allowed_values, ", ")})" def print_help( app: app, commands: commands, main_description: main_description ) do IO.puts("") IO.puts("Usage: #{app} COMMAND") IO.puts("") IO.puts(main_description) IO.puts("") IO.puts("Commands:") align = longer_name(commands) for %{name: name, description: description} <- commands do IO.puts(" #{String.pad_trailing(name, align)} #{description}") end IO.puts("") end def print_help( app: app, command: command, description: description, arguments: arguments, options: options ) do IO.puts("") IO.puts( "Usage: #{app} #{command}" <> "#{if Enum.empty?(options), do: " ", else: " [OPTIONS] "}" <> "#{arguments |> Enum.reverse() |> Enum.map_join(" ", fn %{name: name} -> "<#{name}>" end)}" ) IO.puts("") IO.puts(description) IO.puts("") if !Enum.empty?(arguments) do align = longer_name(arguments) IO.puts("Arguments:") for argument <- Enum.reverse(arguments) do with name <- format_argument_name(argument, align), description <- format_argument_description(argument), allowed_values <- format_argument_allowed_values(argument) do IO.puts(" #{name} #{description}#{allowed_values}") end end IO.puts("") end if !Enum.empty?(options) do align = longer_name(options) IO.puts("Options:") for option <- Enum.reverse(options) do with alias <- format_option_alias(option), name <- format_option_name(option, align), description <- format_option_description(option), default <- format_option_default(option), allowed_values <- format_option_allowed_values(option) do IO.puts(" #{alias} #{name} #{description}#{default}#{allowed_values}") end end IO.puts("") end end def print_errors(errors) when is_list(errors) do IO.puts("error(s):\n#{errors |> Enum.map_join("\n", fn error -> " * #{error}" end)}") end def print_errors(error), do: IO.puts(error) def print_invalid_options(command, invalid_options) do IO.puts( "invalid option(s) for command #{command}:\n#{invalid_options |> Enum.map_join("\n", fn {option, nil} -> " * #{option} without value" {option, value} -> " * #{option} with #{value}" end)}" ) end end