Current section

Files

Jump to
rivet_utils lib cli index.ex
Raw

lib/cli/index.ex

defmodule Rivet.Utils.Cli do
@moduledoc """
Common calls across mix tasks
"""
import Rivet.Utils.Cli.Print
##############################################################################
def run_command(args, opts) do
commands = Keyword.get(opts, :commands)
opts_args = Keyword.take(opts, [:switches, :aliases])
with {matched, [cmd | args], []} <- OptionParser.parse(args, opts_args),
{:ok, module} <- match_command(cmd, commands) do
case apply(module, :run, [opts, matched, args]) do
:ok -> :ok
other -> die("Invalid result from run (#{module}.run/3): #{inspect(other)}")
end
else
{[], [], []} -> syntax(opts, "Unmatched command")
{x, y, errors} -> IO.inspect({x, y, errors}, label: "ERRORS")
{:error, msg} -> syntax(opts, msg)
{:nomatch, cmd} -> syntax(opts, "Unmatched command: #{cmd}")
end
end
##############################################################################
def parse_options(args, opts),
do: OptionParser.parse(args, Keyword.take(opts, [:switches, :aliases]))
##############################################################################
def task_cmd(module) do
case to_string(module) do
"Elixir.Mix.Tasks." <> rest -> String.downcase(rest) |> String.replace(".", " ")
end
end
##############################################################################
def syntax(opts, msg) do
stderr([list_commands(opts), list_options(opts)])
die(msg)
end
##############################################################################
def list_commands(opts) do
## TODO: or pull from last element of module as default
prefix = Keyword.get(opts, :prefix, " ")
cmd0 = Keyword.get(opts, :command, "")
[
"Syntax:\n\n"
| for {cmd, module} <- Keyword.get(opts, :commands), into: [] do
shortdoc = Mix.Task.shortdoc(module)
shortdoc =
if not is_nil(shortdoc) and String.length(shortdoc), do: " # #{shortdoc}", else: ""
[prefix, :bright, cmd0, " ", cmd, :reset, shortdoc, "\n"]
end
]
end
##############################################################################
def list_options(opts) do
switches = Keyword.get(opts, :switches, [])
aliases = Keyword.get(opts, :aliases, [])
info = Keyword.get(opts, :info, [])
prefix = Keyword.get(opts, :prefix, " ")
# invert aliases
aliases =
Map.new(aliases)
|> Enum.reduce(%{}, fn {k, v}, acc ->
Map.update(acc, v, [k], fn as -> [k | as] end)
end)
# switches as strings for sorting
sorted = Enum.map(switches, fn {k, _} -> to_string(k) end) |> Enum.sort()
# formatted
opts = list_options([], Map.new(switches), aliases, Map.new(info), prefix, sorted)
if opts == [],
do: [],
else: ["\nOptions:\n\n"] ++ opts
end
def list_options(out, switches, aliases, info, prefix, [option | rest]) do
key = String.to_atom(option)
opt = String.replace(option, "_", "-")
(out ++
[
prefix,
list_option(opt, key, switches[key], aliases[key], info[key])
])
|> list_options(switches, aliases, info, prefix, rest)
end
def list_options(out, _, _, _, _, []), do: out
##############################################################################
def list_option(opt, _optkey, :boolean, _aliases, info) do
{a, b} = if info[:default] == true, do: {"", "no-"}, else: {"no-", ""}
[:bright, "--#{a}#{opt}", :reset, "|", :bright, "--#{b}#{opt}", :reset, "\n"]
end
def list_option(opt, _optkey, [type, :keep], _aliases, _info) do
[:bright, "--#{opt}", :reset, "=#{to_string(type) |> String.upcase()}\n"]
end
def list_option(opt, _optkey, type, _aliases, _info) do
[:bright, "--#{opt}", :reset, "=#{to_string(type) |> String.upcase()}\n"]
end
##############################################################################
def match_command(cmd, [{pattern, module} | rest]) do
if match_command(cmd, pattern),
do: {:ok, module},
else: match_command(cmd, rest)
end
def match_command(cmd, pattern) when is_binary(pattern) do
case String.split(pattern, "?") do
[root] -> match_command(cmd, root, [])
[root | [tail]] -> match_command(cmd, root, String.graphemes(tail))
_ -> die("Invalid command pattern #{pattern}")
end
end
def match_command(cmd, []), do: {:nomatch, cmd}
##############################################################################
defp match_command(match, match, _), do: true
defp match_command(cmd, root, [c | rest]), do: match_command(cmd, root <> c, rest)
defp match_command(_cmd, _, []), do: false
end