From c7cbea29d427d964f339f15efa0fd767f389a30f Mon Sep 17 00:00:00 2001 From: David Tooke Date: Mon, 12 Oct 2026 12:31:42 -0400 Subject: [PATCH] Colour usage help on the command's terms, not picocli's guess Usage help used picocli's Ansi.AUTO, which reads the real process rather than the Host, and on Windows takes an MSYS or Cygwin TERM for a terminal. So in Git Bash help written to a pipe came out coloured, and the release workflow's Windows job, which runs under bash, failed four tests whose captured help began "Usage: \e[1mrelix" (CI runs Windows under PowerShell, which sets no TERM, and passed). Help is now coloured on the terms --color=auto colours rows: when the Host says standard output is a terminal and $NO_COLOR is not set, and never otherwise. The tests that read help text pipe standard output, as a capture is, and a new test checks the three cases. --- .../java/com/darkcollective/relix/cli/Main.java | 16 ++++++++++++++++ .../cli/command/ManagementCommandsTest.java | 12 ++++++++++-- .../relix/cli/command/ScriptSourcesTest.java | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/darkcollective/relix/cli/Main.java b/src/main/java/com/darkcollective/relix/cli/Main.java index e77a137..70f912b 100644 --- a/src/main/java/com/darkcollective/relix/cli/Main.java +++ b/src/main/java/com/darkcollective/relix/cli/Main.java @@ -79,6 +79,7 @@ public static int run(Host host, Interruption interruption, String... args) { .setOut(out) .setErr(err) .setPosixClusteredShortOptionsAllowed(true) + .setColorScheme(CommandLine.Help.defaultColorScheme(helpColors(host))) .setParameterExceptionHandler((e, ignored) -> { err.print("relix: " + e.getMessage() + "\n"); err.print("Try 'relix --help' for more information.\n"); @@ -106,6 +107,21 @@ public static int run(Host host, Interruption interruption, String... args) { return status; } + /** + * Whether usage help is coloured: on the terms {@code --color=auto} colours rows, when + * standard output is a terminal and {@code $NO_COLOR} is not set. picocli's own + * detection reads the real process rather than the host, and takes an MSYS or Cygwin + * {@code TERM} for a terminal, so in Git Bash on Windows it coloured help written to a + * pipe. + * + * @param host the process + * @return {@code ON} or {@code OFF}, never {@code AUTO} + */ + static CommandLine.Help.Ansi helpColors(Host host) { + return host.stdoutIsTerminal() && host.variable("NO_COLOR").isEmpty() + ? CommandLine.Help.Ansi.ON : CommandLine.Help.Ansi.OFF; + } + /** * The command line with a bare {@code --trace} given an empty value, so that the word * after it is never taken for its file: {@code relix --trace q.relix} traces diff --git a/src/test/java/com/darkcollective/relix/cli/command/ManagementCommandsTest.java b/src/test/java/com/darkcollective/relix/cli/command/ManagementCommandsTest.java index 27d996e..bf546ee 100644 --- a/src/test/java/com/darkcollective/relix/cli/command/ManagementCommandsTest.java +++ b/src/test/java/com/darkcollective/relix/cli/command/ManagementCommandsTest.java @@ -75,16 +75,24 @@ class Help { @Test @DisplayName("prints a command's usage to stdout") void command() { - var result = cli().run("help", "fmt"); + var result = cli().piped().run("help", "fmt"); assertThat(result.status()).isZero(); assertThat(result.out()).startsWith("Usage: relix fmt").contains("--keywords"); } + @Test + @DisplayName("colours usage on a terminal, and not in a pipe or with $NO_COLOR, wherever it runs") + void colours() { + assertThat(cli().run("help", "fmt").out()).startsWith("Usage: \u001B["); + assertThat(cli().piped().run("help", "fmt").out()).doesNotContain("\u001B"); + assertThat(cli().env("NO_COLOR", "1").run("help", "fmt").out()).doesNotContain("\u001B"); + } + @Test @DisplayName("prints the main usage, listing every command, with no COMMAND") void main() { - var result = cli().run("help"); + var result = cli().piped().run("help"); assertThat(result.status()).isZero(); assertThat(result.out()).startsWith("Usage: relix"); diff --git a/src/test/java/com/darkcollective/relix/cli/command/ScriptSourcesTest.java b/src/test/java/com/darkcollective/relix/cli/command/ScriptSourcesTest.java index 7662335..a46ca6e 100644 --- a/src/test/java/com/darkcollective/relix/cli/command/ScriptSourcesTest.java +++ b/src/test/java/com/darkcollective/relix/cli/command/ScriptSourcesTest.java @@ -110,7 +110,7 @@ void stdinWhenPiped() { @Test @DisplayName("with nothing named and a terminal on stdin, usage goes to stderr and the exit is 2") void nothingToRun() { - var result = Cli.in(dir).run(); + var result = Cli.in(dir).piped().run(); assertThat(result.status()).isEqualTo(2); assertThat(result.out()).isEmpty();