defmodule Alaja.CLI.GlobalOpts do
@moduledoc """
Definition and extraction of global CLI options shared across commands.
Global options are parsed from raw args before command-specific parsing
so they are available to every subcommand.
"""
@type t :: %__MODULE__{
help: boolean(),
raw: boolean(),
pos_x: non_neg_integer(),
pos_y: non_neg_integer(),
align: :left | :center | :right,
verbose: boolean(),
box: boolean(),
box_title: String.t() | nil,
box_border: atom(),
box_color: tuple() | nil,
quiet: boolean(),
stdin: boolean()
}
defstruct help: false,
raw: false,
pos_x: 0,
pos_y: 0,
align: :left,
verbose: false,
box: false,
box_title: nil,
box_border: :rounded,
box_color: nil,
quiet: false,
stdin: false
# ---------------------------------------------------------------------------
# Extraction
# ---------------------------------------------------------------------------
@doc """
Parses global options from raw args, returning {global_opts, remaining_args}.
Only recognizes known global flags; all other args are passed through.
"""
@spec parse([String.t()]) :: {t(), [String.t()]}
def parse(args) do
{global_opts, rest} = extract_globals(args, %__MODULE__{})
{global_opts, rest}
end
defp extract_globals([], acc), do: {acc, []}
defp extract_globals(["--help" | rest], acc), do: extract_globals(rest, %{acc | help: true})
defp extract_globals(["-h" | rest], acc), do: extract_globals(rest, %{acc | help: true})
defp extract_globals(["--raw" | rest], acc), do: extract_globals(rest, %{acc | raw: true})
defp extract_globals(["-r" | rest], acc), do: extract_globals(rest, %{acc | raw: true})
defp extract_globals(["--pos-x", val | rest], acc) do
case Integer.parse(val) do
{n, _} ->
extract_globals(rest, %{acc | pos_x: n})
:error ->
IO.puts(:stderr, "Error: --pos-x requires an integer, got '#{val}'")
System.halt(1)
end
end
defp extract_globals(["--pos-y", val | rest], acc) do
case Integer.parse(val) do
{n, _} ->
extract_globals(rest, %{acc | pos_y: n})
:error ->
IO.puts(:stderr, "Error: --pos-y requires an integer, got '#{val}'")
System.halt(1)
end
end
defp extract_globals(["--align", val | rest], acc) do
extract_globals(rest, %{acc | align: parse_align(val)})
end
defp extract_globals(["-a", val | rest], acc) do
extract_globals(rest, %{acc | align: parse_align(val)})
end
defp extract_globals(["--verbose" | rest], acc),
do: extract_globals(rest, %{acc | verbose: true})
defp extract_globals(["-v" | rest], acc), do: extract_globals(rest, %{acc | verbose: true})
defp extract_globals(["--box" | rest], acc), do: extract_globals(rest, %{acc | box: true})
defp extract_globals(["--box-title", val | rest], acc) do
extract_globals(rest, %{acc | box_title: val})
end
defp extract_globals(["--box-border", val | rest], acc) do
border =
case Alaja.Helpers.safe_string_to_atom(val) do
{:ok, atom} -> atom
{:error, _} -> :rounded
end
extract_globals(rest, %{acc | box_border: border})
end
defp extract_globals(["--box-color", val | rest], acc) do
color = parse_color(val)
extract_globals(rest, %{acc | box_color: color})
end
defp extract_globals(["--quiet" | rest], acc), do: extract_globals(rest, %{acc | quiet: true})
defp extract_globals(["-q" | rest], acc), do: extract_globals(rest, %{acc | quiet: true})
defp extract_globals(["--stdin" | rest], acc), do: extract_globals(rest, %{acc | stdin: true})
defp extract_globals(["-s" | rest], acc), do: extract_globals(rest, %{acc | stdin: true})
# Unknown flag with value: keep both
defp extract_globals([flag, val | rest], acc) do
if String.starts_with?(flag, "--") do
{acc2, rest2} = extract_globals(rest, acc)
{acc2, [flag, val | rest2]}
else
{acc2, rest2} = extract_globals([val | rest], acc)
{acc2, [flag | rest2]}
end
end
# Unknown flag or positional: keep it
defp extract_globals([arg | rest], acc) do
{acc2, rest2} = extract_globals(rest, acc)
{acc2, [arg | rest2]}
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
defp parse_align("left"), do: :left
defp parse_align("center"), do: :center
defp parse_align("right"), do: :right
defp parse_align(a) when is_atom(a), do: a
defp parse_align(_), do: :left
defp parse_color(str) do
case Pote.Orchestrator.parse_color(str) do
{:ok, rgb} -> rgb
_ -> nil
end
end
@doc """
Converts global opts to printer keyword opts.
"""
@spec to_printer_opts(t()) :: keyword()
def to_printer_opts(global) do
[
raw: global.raw,
pos_x: global.pos_x,
pos_y: global.pos_y,
verbose: global.verbose,
box: global.box,
box_title: global.box_title,
box_border: global.box_border,
box_color: global.box_color,
align: global.align
]
end
end