Skip to content

Commit 101f697

Browse files
authored
Colorize hex.policy and cooldown output (#1189)
mix hex.policy show and why now highlight the active policy with ANSI colors: the policy name and rule values, ALLOWED/BLOCKED status, and per-reason colors (advisory, retirement, cooldown). The "Versions filtered by cooldown" summary printed during mix deps.get highlights the package, version, and eligible date. Also shorten the cooldown summary line to "PKG VSN — eligible DATE", dropping the redundant "published N days ago" — the eligible date is the actionable part.
1 parent a0ab6bc commit 101f697

4 files changed

Lines changed: 68 additions & 37 deletions

File tree

lib/hex/cooldown.ex

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,30 +187,41 @@ defmodule Hex.Cooldown do
187187
nil
188188

189189
entries ->
190-
today = Date.utc_today()
191-
192190
lines =
193191
entries
194192
|> Enum.group_by(fn {repo, pkg, _vsn, _} -> {repo, pkg} end)
195193
|> Enum.sort()
196-
|> Enum.flat_map(fn {_key, entries} -> package_lines(entries, cutoff, today) end)
194+
|> Enum.flat_map(fn {_key, entries} -> package_lines(entries, cutoff) end)
197195

198-
"\nVersions filtered by cooldown:\n" <> Enum.join(lines, "\n") <> "\n"
196+
iodata = ["\nVersions filtered by cooldown:\n", Enum.intersperse(lines, "\n"), "\n"]
197+
IO.chardata_to_string(Hex.Shell.format(iodata))
199198
end
200199
end
201200

202-
defp package_lines(entries, cutoff, today) do
201+
defp package_lines(entries, cutoff) do
203202
{listed, rest} =
204203
entries
205204
|> Enum.sort_by(fn {_repo, _pkg, vsn, _} -> Version.parse!(vsn) end, {:desc, Version})
206205
|> Enum.split(@versions_listed_per_package)
207206

208207
lines =
209208
Enum.map(listed, fn {_repo, pkg, vsn, published_at} ->
210-
published_date = published_at |> DateTime.from_unix!() |> DateTime.to_date()
211-
days_ago = Date.diff(today, published_date)
212209
eligible_date = eligible_on(published_at, cutoff)
213-
" #{pkg} #{vsn} — published #{days_ago} days ago, eligible #{eligible_date}"
210+
211+
[
212+
" ",
213+
:bright,
214+
pkg,
215+
:reset,
216+
" ",
217+
:yellow,
218+
vsn,
219+
:reset,
220+
" — eligible ",
221+
:cyan,
222+
to_string(eligible_date),
223+
:reset
224+
]
214225
end)
215226

216227
case rest do

lib/hex/policy/diagnostics.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ defmodule Hex.Policy.Diagnostics do
165165

166166
def format_reason(other), do: inspect(other)
167167

168+
@doc """
169+
Maps a blocker reason to an ANSI color used to highlight it in output.
170+
"""
171+
@spec reason_color(term()) :: atom()
172+
def reason_color({:advisory, _}), do: :red
173+
def reason_color({:retirement, _}), do: :yellow
174+
def reason_color({:cooldown, _, _}), do: :cyan
175+
def reason_color(:override_deny), do: :red
176+
def reason_color(_other), do: :reset
177+
168178
defp count(1, noun), do: "1 #{noun}"
169179
defp count(n, noun), do: "#{n} #{noun}s"
170180
end

lib/mix/tasks/hex.policy.ex

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,13 @@ defmodule Mix.Tasks.Hex.Policy do
9393
end
9494

9595
defp render_show(policy) do
96-
Hex.Shell.info(
97-
"Active policy: #{policy.repository}/#{policy.name} [#{visibility_label(policy.visibility)}]\n"
98-
)
96+
Hex.Shell.info([
97+
"Active policy: ",
98+
[:cyan, "#{policy.repository}/#{policy.name}"],
99+
" ",
100+
visibility_tag(policy.visibility),
101+
"\n"
102+
])
99103

100104
render_repositories(policy)
101105
Hex.Shell.info("")
@@ -107,7 +111,11 @@ defmodule Mix.Tasks.Hex.Policy do
107111
:ok
108112

109113
{source, duration} ->
110-
Hex.Shell.info("Effective cooldown: #{duration} (#{cooldown_source(source)})")
114+
Hex.Shell.info([
115+
"Effective cooldown: ",
116+
[:cyan, duration],
117+
" (#{cooldown_source(source)})"
118+
])
111119
end
112120
end
113121

@@ -125,11 +133,11 @@ defmodule Mix.Tasks.Hex.Policy do
125133
restriction = Map.get(rp, :restriction)
126134
overrides = Map.get(rp, :overrides, [])
127135

128-
Hex.Shell.info(" #{rp.repository}:")
129-
Hex.Shell.info(" Cooldown: #{cooldown_label(restriction)}")
130-
Hex.Shell.info(" Advisory rule: #{advisory_label(restriction)}")
131-
Hex.Shell.info(" Retirement rule: #{retirement_label(restriction)}")
132-
Hex.Shell.info(" Overrides: #{length(overrides)}")
136+
Hex.Shell.info([" ", [:bright, "#{rp.repository}:"]])
137+
Hex.Shell.info([" Cooldown: ", cooldown_label(restriction)])
138+
Hex.Shell.info([" Advisory rule: ", advisory_label(restriction)])
139+
Hex.Shell.info([" Retirement rule: ", retirement_label(restriction)])
140+
Hex.Shell.info([" Overrides: ", to_string(length(overrides))])
133141
end
134142

135143
defp why(arg) do
@@ -177,16 +185,16 @@ defmodule Mix.Tasks.Hex.Policy do
177185

178186
case Filter.classify(policy, candidate) do
179187
:allowed ->
180-
[version, "ALLOWED", ""]
188+
[version, [:green, "ALLOWED"], ""]
181189

182190
{:blocked, reasons} ->
183-
reason_text =
191+
reason_cell =
184192
reasons
185-
|> Enum.map(&Diagnostics.format_reason/1)
186193
|> Enum.uniq()
187-
|> Enum.join(", ")
194+
|> Enum.map(&[Diagnostics.reason_color(&1), Diagnostics.format_reason(&1)])
195+
|> Enum.intersperse(", ")
188196

189-
[version, "BLOCKED", reason_text]
197+
[version, [:red, "BLOCKED"], reason_cell]
190198
end
191199
end)
192200

@@ -196,27 +204,30 @@ defmodule Mix.Tasks.Hex.Policy do
196204
defp cooldown_source(:local), do: "local"
197205
defp cooldown_source({repo, name}), do: "#{repo}/#{name}"
198206

199-
defp visibility_label(:VISIBILITY_PUBLIC), do: "public"
200-
defp visibility_label(:VISIBILITY_PRIVATE), do: "private"
201-
defp visibility_label(other), do: to_string(other)
207+
defp visibility_tag(:VISIBILITY_PUBLIC), do: [:green, "[public]"]
208+
defp visibility_tag(:VISIBILITY_PRIVATE), do: [:yellow, "[private]"]
209+
defp visibility_tag(other), do: ["[", to_string(other), "]"]
202210

203-
defp cooldown_label(%{cooldown: duration}) when is_binary(duration), do: duration
204-
defp cooldown_label(_restriction), do: "(none)"
211+
defp cooldown_label(%{cooldown: duration}) when is_binary(duration), do: [:cyan, duration]
212+
defp cooldown_label(_restriction), do: [:faint, "(none)"]
205213

206-
defp advisory_label(%{advisory_min_severity: :SEVERITY_NONE}), do: "block any advisory"
214+
defp advisory_label(%{advisory_min_severity: :SEVERITY_NONE}), do: [:red, "block any advisory"]
207215

208216
defp advisory_label(%{advisory_min_severity: severity}) when not is_nil(severity),
209-
do: "block ≥ #{Hex.Utils.advisory_severity(severity)}"
217+
do: [:red, "block ≥ #{Hex.Utils.advisory_severity(severity)}"]
210218

211-
defp advisory_label(_restriction), do: "(disabled)"
219+
defp advisory_label(_restriction), do: [:faint, "(disabled)"]
212220

213221
defp retirement_label(%{retirement_reasons: reasons}) when is_list(reasons) and reasons != [] do
214-
reasons
215-
|> Enum.map(fn r ->
216-
r |> Hex.Utils.package_retirement_reason() |> String.upcase()
217-
end)
218-
|> Enum.join(", ")
222+
text =
223+
reasons
224+
|> Enum.map(fn r ->
225+
r |> Hex.Utils.package_retirement_reason() |> String.upcase()
226+
end)
227+
|> Enum.join(", ")
228+
229+
[:yellow, text]
219230
end
220231

221-
defp retirement_label(_restriction), do: "(disabled)"
232+
defp retirement_label(_restriction), do: [:faint, "(disabled)"]
222233
end

test/hex/cooldown_test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,6 @@ defmodule Hex.CooldownTest do
298298

299299
assert summary =~ "Versions filtered by cooldown:"
300300
assert summary =~ "castore 1.0.19"
301-
assert summary =~ "3 days ago"
302301
assert summary =~ "eligible #{Cooldown.eligible_on(published_at, cutoff)}"
303302
end
304303

0 commit comments

Comments
 (0)