From 378a9ec81abaa50bc7b4d6542889c51cd9de560c Mon Sep 17 00:00:00 2001 From: Kevin Dinkel <1225857+dinkelk@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:08:50 -0600 Subject: [PATCH 1/2] Add --quiet (-q) flag to suppress non-error output Add a quiet mode that suppresses all build progress output (target names, status messages, info) while still printing errors. Useful for scripting or CI where only failures matter. The flag is propagated to child processes via the REDO_QUIET environment variable, so the entire build tree stays silent. --- src/Main.hs | 14 +++++++++++--- src/PrettyPrint.hs | 31 ++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/Main.hs b/src/Main.hs index 0ccd7b5..b6d32e7 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -36,7 +36,8 @@ data Options = Options { keepGoing :: Bool, jobs :: Int, shuffle :: Bool, - noColor :: Bool + noColor :: Bool, + quiet :: Bool } -- Redo default options: @@ -51,7 +52,8 @@ defaultOptions = Options { keepGoing = False, jobs = 1, shuffle = False, - noColor = False + noColor = False, + quiet = False } -- Define program options: @@ -73,6 +75,7 @@ options = , Option ['k'] ["keep-going"] (NoArg setKeepGoing) "keep building even if some targets fail" , Option ['s'] ["shuffle"] (NoArg setShuffle) "randomize the build order to find dependency bugs" , Option ['n'] ["no-color"] (NoArg setNoColor) "print plain text, disable ANSI terminal color output" + , Option ['q'] ["quiet"] (NoArg setQuiet) "suppress all output except errors" ] -- Helper functions for setting the options: @@ -106,6 +109,9 @@ setShuffle opt = return opt { shuffle = True } setNoColor :: Options -> IO Options setNoColor opt = return opt { noColor = True } +setQuiet :: Options -> IO Options +setQuiet opt = return opt { quiet = True } + -- Print the program version and license information: printVersion :: Options -> IO Options printVersion _ = do putStrLn versionString @@ -152,13 +158,15 @@ main = do keepGoing = keepGoing', jobs = jobs', shuffle = shuffle', - noColor = noColor'} = opts + noColor = noColor', + quiet = quiet'} = opts -- Show help or version information if asked: when help' (printHelp progName options []) when keepGoing' (setEnv "REDO_KEEP_GOING" "TRUE") when shuffle' (setEnv "REDO_SHUFFLE" "TRUE") when noColor' (setEnv "REDO_NO_COLOR" "TRUE") + when quiet' (setEnv "REDO_QUIET" "TRUE") when dashD1' (setEnv "REDO_DEBUG_1" "TRUE") when dashD2' (setEnv "REDO_DEBUG_2" "TRUE") when dashC' (setEnv "REDO_CHECK" "TRUE") diff --git a/src/PrettyPrint.hs b/src/PrettyPrint.hs index f2b3ee2..79cf001 100644 --- a/src/PrettyPrint.hs +++ b/src/PrettyPrint.hs @@ -2,6 +2,7 @@ module PrettyPrint(putRedoUnformatted, putRedoInfo, putRedoWarning, putInfoStrLn, putWarningStrLn, putErrorStrLn, putStatusStrLn, putRedoStatus, putRedoError, putUnformattedStrLn, isColorTerminal, putStrBuffered) where +import Control.Monad (unless) import System.IO (hPutStrLn, stderr, hFlush, hSetBuffering, BufferMode(..), stdout, hIsTerminalDevice) import Data.Maybe (fromJust, isNothing, isJust, fromMaybe) import System.Environment (lookupEnv) @@ -51,13 +52,17 @@ putColorStrLn color string = do putUnformattedStrLn :: String -> IO () putUnformattedStrLn = hPutStrLn stderr putInfoStrLn :: String -> IO () -putInfoStrLn = putColorStrLn green +putInfoStrLn string = do + quiet' <- isQuiet + unless quiet' $ putColorStrLn green string putWarningStrLn :: String -> IO () putWarningStrLn = putColorStrLn yellow putErrorStrLn :: String -> IO () putErrorStrLn = putColorStrLn red putStatusStrLn :: String -> IO () -putStatusStrLn = putColorStrLn cyan +putStatusStrLn string = do + quiet' <- isQuiet + unless quiet' $ putColorStrLn cyan string -- Print a "redo" style string. This includes the target being built, indented -- to the appropriate level, with an optional message ater @@ -92,22 +97,34 @@ useColor = do let noColor = fromMaybe "" noColor' return $ noColor /= "TRUE" +-- Is quiet mode enabled? (suppress info and status output; warnings and errors always print) +isQuiet :: IO Bool +isQuiet = do + quiet' <- lookupEnv "REDO_QUIET" + return $ fromMaybe "" quiet' == "TRUE" + -- Put a "redo" style info string: putRedoInfo :: Target -> IO () -putRedoInfo target = putRedo green target "" +putRedoInfo target = do + quiet' <- isQuiet + unless quiet' $ putRedo green target "" -- Put a "redo" style status string: putRedoStatus :: Target -> String -> IO () -putRedoStatus target string = putRedo cyan target ("- " ++ string) +putRedoStatus target string = do + quiet' <- isQuiet + unless quiet' $ putRedo cyan target ("- " ++ string) --- Put a "redo" style warning string: +-- Put a "redo" style warning string (always printed, even in quiet mode): putRedoWarning :: Target -> String -> IO () putRedoWarning target string = putRedo yellow target ("- " ++ string) --- Put a "redo" style error string: +-- Put a "redo" style error string (always printed, even in quiet mode): putRedoError :: Target -> String -> IO () putRedoError target string = putRedo red target ("- " ++ string) -- Put a "redo" style unformatted string: putRedoUnformatted :: Target -> String -> IO () -putRedoUnformatted target string = putRedo "" target ("- " ++ string) +putRedoUnformatted target string = do + quiet' <- isQuiet + unless quiet' $ putRedo "" target ("- " ++ string) From 42bf88f0fe1ff04ea25ae8f16f5f5ea3f5e76957 Mon Sep 17 00:00:00 2001 From: Kevin Dinkel <1225857+dinkelk@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:09:17 -0600 Subject: [PATCH 2/2] Add comprehensive tests for --quiet flag 10 tests covering: - Silent output on successful build - Normal mode still shows output (control test) - Errors still printed in quiet mode - Quiet propagates to nested child builds - --quiet long form - -q combined with -k flag - Multiple targets with -q - Build correctness unaffected by -q - Nested build errors show error chain in quiet mode - Warnings are not suppressed by -q --- test/150-quiet/all.do | 139 +++++++++++++++++++++++++++++++++++ test/150-quiet/failme.do | 1 + test/150-quiet/inner.do | 1 + test/150-quiet/inner_fail.do | 1 + test/150-quiet/outer.do | 2 + test/150-quiet/outer_fail.do | 2 + test/150-quiet/target1.do | 1 + test/150-quiet/warnme.do | 2 + 8 files changed, 149 insertions(+) create mode 100644 test/150-quiet/all.do create mode 100644 test/150-quiet/failme.do create mode 100644 test/150-quiet/inner.do create mode 100644 test/150-quiet/inner_fail.do create mode 100644 test/150-quiet/outer.do create mode 100644 test/150-quiet/outer_fail.do create mode 100644 test/150-quiet/target1.do create mode 100644 test/150-quiet/warnme.do diff --git a/test/150-quiet/all.do b/test/150-quiet/all.do new file mode 100644 index 0000000..638abe7 --- /dev/null +++ b/test/150-quiet/all.do @@ -0,0 +1,139 @@ +exec >&2 +. ../skip-if-minimal-do.sh + +# Test 1: With -q, successful build produces no output +echo "Test 1: quiet mode suppresses output on success" +rm -f target1 +output=$(redo -q target1 2>&1) +if [ -n "$output" ]; then + echo "FAIL: expected no output with -q, got: $output" >&2 + exit 1 +fi +[ -e target1 ] || exit 2 +echo " PASS" + +# Test 2: Without -q, successful build produces output +echo "Test 2: normal mode shows output" +rm -f target1 +output=$(redo target1 2>&1) +if [ -z "$output" ]; then + echo "FAIL: expected output without -q" >&2 + exit 3 +fi +echo " PASS" + +# Test 3: With -q, errors are still printed +echo "Test 3: quiet mode still shows errors" +rm -f failme +set +e +output=$(redo -q failme 2>&1) +rc=$? +set -e +if [ "$rc" -eq 0 ]; then + echo "FAIL: expected non-zero exit" >&2 + exit 4 +fi +if [ -z "$output" ]; then + echo "FAIL: expected error output with -q on failure" >&2 + exit 5 +fi +case "$output" in + *Error*) ;; # good + *) echo "FAIL: expected 'Error' in output, got: $output" >&2; exit 6 ;; +esac +echo " PASS" + +# Test 4: -q propagates to child redo processes (nested builds) +echo "Test 4: quiet mode propagates to nested builds" +rm -f outer inner +output=$(redo -q outer 2>&1) +if [ -n "$output" ]; then + echo "FAIL: expected no output from nested -q build, got: $output" >&2 + exit 7 +fi +[ -e outer ] || exit 8 +[ -e inner ] || exit 9 +echo " PASS" + +# Test 5: --quiet long form works +echo "Test 5: --quiet long form" +rm -f target1 +output=$(redo --quiet target1 2>&1) +if [ -n "$output" ]; then + echo "FAIL: expected no output with --quiet, got: $output" >&2 + exit 10 +fi +echo " PASS" + +# Test 6: -q combined with other flags +echo "Test 6: -q combined with -k (keep-going)" +rm -f target1 +output=$(redo -q -k target1 2>&1) +if [ -n "$output" ]; then + echo "FAIL: expected no output with -q -k, got: $output" >&2 + exit 11 +fi +echo " PASS" + +# Test 7: -q with multiple targets +echo "Test 7: -q with multiple targets" +rm -f target1 inner +output=$(redo -q target1 inner 2>&1) +if [ -n "$output" ]; then + echo "FAIL: expected no output with -q and multiple targets, got: $output" >&2 + exit 12 +fi +[ -e target1 ] || exit 13 +[ -e inner ] || exit 14 +echo " PASS" + +# Test 8: -q doesn't affect build correctness (target content is right) +echo "Test 8: -q doesn't affect build output content" +rm -f target1 +redo -q target1 +content=$(cat target1) +if [ "$content" != "built" ]; then + echo "FAIL: expected 'built' in target1, got: $content" >&2 + exit 15 +fi +echo " PASS" + +# Test 9: Nested error in quiet mode still shows error chain +echo "Test 9: nested error in quiet mode shows error chain" +rm -f outer_fail inner_fail +set +e +output=$(redo -q outer_fail 2>&1) +rc=$? +set -e +if [ "$rc" -eq 0 ]; then + echo "FAIL: expected non-zero exit from outer_fail" >&2 + exit 16 +fi +case "$output" in + *Error*) ;; # good + *) echo "FAIL: expected error output, got: $output" >&2; exit 17 ;; +esac +echo " PASS" + +# Test 10: Warnings are NOT suppressed by -q +echo "Test 10: quiet mode still shows warnings" +rm -f warnme +# First build warnme so redo knows it as a target +redo warnme +# Now remove and create as plain source file +rm -f warnme +echo "i am a source" > warnme +# Trying to redo it should produce a warning (source file exists) +set +e +output=$(redo -q warnme 2>&1) +set -e +if [ -z "$output" ]; then + echo "FAIL: expected warning output with -q, got nothing" >&2 + exit 18 +fi +case "$output" in + *Warning*) ;; # good — warning was printed + *) echo "FAIL: expected 'Warning' in output, got: $output" >&2; exit 19 ;; +esac +rm -f warnme +echo " PASS" diff --git a/test/150-quiet/failme.do b/test/150-quiet/failme.do new file mode 100644 index 0000000..379a4c9 --- /dev/null +++ b/test/150-quiet/failme.do @@ -0,0 +1 @@ +exit 1 diff --git a/test/150-quiet/inner.do b/test/150-quiet/inner.do new file mode 100644 index 0000000..7365022 --- /dev/null +++ b/test/150-quiet/inner.do @@ -0,0 +1 @@ +echo "inner" > "$3" diff --git a/test/150-quiet/inner_fail.do b/test/150-quiet/inner_fail.do new file mode 100644 index 0000000..379a4c9 --- /dev/null +++ b/test/150-quiet/inner_fail.do @@ -0,0 +1 @@ +exit 1 diff --git a/test/150-quiet/outer.do b/test/150-quiet/outer.do new file mode 100644 index 0000000..9b0fdfc --- /dev/null +++ b/test/150-quiet/outer.do @@ -0,0 +1,2 @@ +redo-ifchange inner +echo "outer" > "$3" diff --git a/test/150-quiet/outer_fail.do b/test/150-quiet/outer_fail.do new file mode 100644 index 0000000..e3ecf96 --- /dev/null +++ b/test/150-quiet/outer_fail.do @@ -0,0 +1,2 @@ +redo-ifchange inner_fail +echo "should not reach here" > "$3" diff --git a/test/150-quiet/target1.do b/test/150-quiet/target1.do new file mode 100644 index 0000000..9d24a9f --- /dev/null +++ b/test/150-quiet/target1.do @@ -0,0 +1 @@ +echo "built" > "$3" diff --git a/test/150-quiet/warnme.do b/test/150-quiet/warnme.do new file mode 100644 index 0000000..419ea7a --- /dev/null +++ b/test/150-quiet/warnme.do @@ -0,0 +1,2 @@ +# Build a target, then mark it as source so rebuilding triggers a warning +echo "built" > "$3"