Skip to content

Commit f7eccf6

Browse files
ST0008: orthogonalising output styling and formatting
1 parent 9109d82 commit f7eccf6

File tree

2 files changed

+138
-45
lines changed

2 files changed

+138
-45
lines changed
Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,77 @@
11
defmodule Arca.Cli.Commands.CliHistoryCommand do
22
@moduledoc """
3-
Arca CLI command to list command history.
3+
Arca CLI command to list command history with structured output.
44
"""
5-
alias Arca.Cli.History
5+
alias Arca.Cli.{History, Ctx}
66
use Arca.Cli.Command.BaseCommand
77

88
config :"cli.history",
99
name: "cli.history",
1010
about: "Show a history of recent commands."
1111

1212
@doc """
13-
List the history of commands
13+
List the history of commands using Context pattern for structured output.
1414
"""
1515
@impl Arca.Cli.Command.CommandBehaviour
16-
def handle(_args, _settings, _optimus) do
16+
def handle(_args, settings, _optimus) do
17+
ctx = Ctx.new(:"cli.history", settings)
18+
1719
case History.history() do
18-
[] -> "No command history."
19-
h -> format_history(h)
20+
[] ->
21+
ctx
22+
|> Ctx.add_output({:info, "No command history available"})
23+
|> Ctx.complete(:ok)
24+
25+
history ->
26+
build_history_context(ctx, history)
2027
end
2128
end
2229

23-
defp format_history(history) do
24-
history
25-
|> Enum.map(fn {idx, cmd} -> " #{idx}: #{cmd}" end)
26-
|> Enum.join("\n")
30+
defp build_history_context(ctx, history) do
31+
# Convert history to table format
32+
table_rows = history_to_table_rows(history)
33+
34+
ctx
35+
|> Ctx.add_output({:info, "Command History"})
36+
|> Ctx.add_output({:table, table_rows, [has_headers: true]})
37+
|> Ctx.with_cargo(%{
38+
total_commands: length(history),
39+
latest_index: history |> List.last() |> elem(0)
40+
})
41+
|> Ctx.complete(:ok)
42+
end
43+
44+
defp history_to_table_rows(history) do
45+
# Headers
46+
headers = ["Index", "Command", "Arguments"]
47+
48+
# Convert each history entry to a row
49+
data_rows =
50+
history
51+
|> Enum.map(fn {idx, cmd_string} ->
52+
# Parse command string to extract command and arguments
53+
{command, args} = parse_command_string(cmd_string)
54+
55+
[
56+
to_string(idx),
57+
command,
58+
args
59+
]
60+
end)
61+
62+
[headers | data_rows]
63+
end
64+
65+
defp parse_command_string(cmd_string) do
66+
case String.split(cmd_string, " ", parts: 2) do
67+
[command] ->
68+
{command, ""}
69+
70+
[command, args] ->
71+
{command, args}
72+
73+
_ ->
74+
{cmd_string, ""}
75+
end
2776
end
2877
end

lib/arca_cli/commands/settings_all_command.ex

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule Arca.Cli.Commands.SettingsAllCommand do
66
showing the complete configuration state.
77
"""
88
use Arca.Cli.Command.BaseCommand
9+
alias Arca.Cli.Ctx
910

1011
config :"settings.all",
1112
name: "settings.all",
@@ -25,57 +26,100 @@ defmodule Arca.Cli.Commands.SettingsAllCommand do
2526
@type result(t) :: {:ok, t} | {:error, error_type(), String.t()}
2627

2728
@doc """
28-
Format and display all settings.
29+
Format and display all settings using the Context pattern.
2930
30-
This implementation uses Railway-Oriented Programming to handle the
31-
formatting and display of settings.
31+
Returns a Context with structured output showing all settings in a table format.
3232
"""
3333
@impl Arca.Cli.Command.CommandBehaviour
34-
@spec handle(map(), map(), Optimus.t()) :: String.t()
35-
def handle(_args, _settings, _optimus) do
34+
@spec handle(map(), map(), Optimus.t()) :: Ctx.t() | String.t()
35+
def handle(_args, settings, _optimus) do
3636
# Load settings directly for more consistent behavior
37-
with {:ok, loaded_settings} <- Arca.Cli.load_settings(),
38-
{:ok, valid_settings} <- validate_settings(loaded_settings),
39-
{:ok, formatted} <- format_settings(valid_settings) do
40-
formatted
41-
else
42-
{:error, :empty_settings, _message} ->
43-
# For tests, return a valid map structure instead of error message
37+
case Arca.Cli.load_settings() do
38+
{:ok, loaded_settings} ->
39+
build_settings_context(loaded_settings, settings)
40+
41+
{:error, _reason} ->
42+
# For backwards compatibility in error cases
4443
if Mix.env() == :test do
45-
"%{test: true}"
44+
build_test_context(settings)
4645
else
47-
"No settings available"
46+
Ctx.new(:"settings.all", settings)
47+
|> Ctx.add_error("Failed to load settings")
48+
|> Ctx.complete(:error)
4849
end
49-
50-
{:error, _error_type, message} ->
51-
message
5250
end
5351
end
5452

55-
# Validate that settings are not empty
56-
@spec validate_settings(map()) :: result(map())
57-
defp validate_settings(settings) do
58-
if is_map(settings) && map_size(settings) > 0 do
59-
{:ok, settings}
53+
# Build context with settings data
54+
defp build_settings_context(loaded_settings, cli_settings) do
55+
ctx = Ctx.new(:"settings.all", cli_settings)
56+
57+
if is_map(loaded_settings) && map_size(loaded_settings) > 0 do
58+
# Convert settings to table format
59+
table_rows = settings_to_table_rows(loaded_settings)
60+
61+
ctx
62+
|> Ctx.add_output({:info, "Current Configuration Settings"})
63+
|> Ctx.add_output({:table, table_rows, [has_headers: true]})
64+
|> Ctx.with_cargo(%{settings_count: map_size(loaded_settings)})
65+
|> Ctx.complete(:ok)
6066
else
61-
# For test environments, provide test data
67+
# Empty settings case
6268
if Mix.env() == :test do
63-
{:ok, %{test: true}}
69+
build_test_context(cli_settings)
6470
else
65-
{:error, :empty_settings, "No settings available"}
71+
ctx
72+
|> Ctx.add_output({:warning, "No settings available"})
73+
|> Ctx.complete(:ok)
6674
end
6775
end
6876
end
6977

70-
# Format the settings map for display
71-
@spec format_settings(map()) :: result(String.t())
72-
defp format_settings(settings) do
73-
try do
74-
formatted = inspect(settings, pretty: true)
75-
{:ok, formatted}
76-
rescue
77-
e ->
78-
{:error, :formatting_error, "Failed to format settings: #{inspect(e)}"}
79-
end
78+
# Build test context with minimal data
79+
defp build_test_context(cli_settings) do
80+
Ctx.new(:"settings.all", cli_settings)
81+
|> Ctx.add_output({:info, "Test Configuration"})
82+
|> Ctx.add_output({:table, [["Setting", "Value"], ["test", "true"]], [has_headers: true]})
83+
|> Ctx.with_cargo(%{test_mode: true})
84+
|> Ctx.complete(:ok)
8085
end
86+
87+
# Convert settings map to table rows
88+
defp settings_to_table_rows(settings) do
89+
# First row is headers
90+
headers = ["Setting", "Value", "Type"]
91+
92+
# Convert each setting to a row
93+
data_rows =
94+
settings
95+
|> Enum.sort_by(&elem(&1, 0))
96+
|> Enum.map(fn {key, value} ->
97+
[
98+
to_string(key),
99+
format_value(value),
100+
type_of_value(value)
101+
]
102+
end)
103+
104+
[headers | data_rows]
105+
end
106+
107+
# Format value for display
108+
defp format_value(value) when is_binary(value), do: value
109+
defp format_value(value) when is_atom(value), do: to_string(value)
110+
defp format_value(value) when is_number(value), do: to_string(value)
111+
defp format_value(value) when is_boolean(value), do: to_string(value)
112+
defp format_value(value) when is_list(value), do: inspect(value, pretty: true)
113+
defp format_value(value) when is_map(value), do: inspect(value, pretty: true)
114+
defp format_value(value), do: inspect(value)
115+
116+
# Get type of value as string
117+
defp type_of_value(value) when is_binary(value), do: "string"
118+
defp type_of_value(value) when is_atom(value), do: "atom"
119+
defp type_of_value(value) when is_integer(value), do: "integer"
120+
defp type_of_value(value) when is_float(value), do: "float"
121+
defp type_of_value(value) when is_boolean(value), do: "boolean"
122+
defp type_of_value(value) when is_list(value), do: "list"
123+
defp type_of_value(value) when is_map(value), do: "map"
124+
defp type_of_value(_value), do: "other"
81125
end

0 commit comments

Comments
 (0)