feat: implement verbosity levels with --verbose and --quiet flags#9
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a first-class verbosity concept to the CLI by introducing verbosity levels (quiet/normal/verbose), wiring global flags to set that level, and providing a LevelFilterUi wrapper to suppress UI output below the configured verbosity.
Changes:
- Add
VerbosityLevel+LevelFilterUiUI wrapper to suppressOutput/Info/Warnin quiet mode. - Add
CLI.VerbosityFlagandCLI.Verbosity()plus arg parsing for--verboseand--quiet. - Add tests covering verbosity parsing and
LevelFilterUibehavior/composition.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
ui_level_filter.go |
Introduces VerbosityLevel and LevelFilterUi implementation. |
ui_level_filter_test.go |
Adds unit tests for pass-through/suppression behavior and composition with PrefixedUi. |
cli.go |
Adds VerbosityFlag, internal state, NewCLI default, and CLI.Verbosity() API. |
cli_args.go |
Parses --verbose/--quiet into internal CLI state during arg processing. |
cli_test.go |
Adds tests for verbosity flag parsing and the disabled-flag behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // and double-hyphen forms are accepted (e.g. -verbose and --verbose). | ||
| // Set to "" to disable verbosity flags entirely. |
There was a problem hiding this comment.
The VerbosityFlag godoc mentions recognizing a --quiet flag, but the parser also accepts the single-hyphen form (-quiet). Consider updating the comment to reflect both forms for quiet as well, for consistency with the verbose description and actual behavior.
| // and double-hyphen forms are accepted (e.g. -verbose and --verbose). | |
| // Set to "" to disable verbosity flags entirely. | |
| // and double-hyphen forms are accepted for each (e.g. -verbose/--verbose and | |
| // -quiet/--quiet). Set to "" to disable verbosity flags entirely. |
| // Check for verbosity flags. | ||
| if c.VerbosityFlag != "" { | ||
| if arg == "-"+c.VerbosityFlag || arg == "--"+c.VerbosityFlag { | ||
| c.isVerbose = true | ||
| continue | ||
| } | ||
| if arg == "-quiet" || arg == "--quiet" { | ||
| c.isQuiet = true | ||
| continue | ||
| } | ||
| } |
There was a problem hiding this comment.
Because processArgs sets c.subcommandArgs to the remaining tail slice as soon as it sees the first non-flag subcommand, any later-occurring global verbosity flags (e.g. foo --quiet) will still be present in SubcommandArgs() even though they are handled as globals (the loop continues but doesn't remove them). This can cause commands to receive unexpected --quiet/--verbose args and fail their own flag parsing. Consider either restricting verbosity parsing to args before the subcommand, or building a filtered arg list so recognized global flags are stripped from subcommandArgs regardless of position.
| func (c *CLI) Verbosity() VerbosityLevel { | ||
| c.once.Do(c.init) | ||
| switch { | ||
| case c.isQuiet: | ||
| return VerbosityQuiet | ||
| case c.isVerbose: | ||
| return VerbosityVerbose | ||
| default: | ||
| return VerbosityNormal | ||
| } |
There was a problem hiding this comment.
CLI.Verbosity() will return VerbosityQuiet whenever --quiet is present, even if --verbose is also present and appears later in the args, because processArgs can set both booleans and the switch prioritizes isQuiet. This makes --quiet --verbose and --verbose --quiet behave the same and hides ordering. Consider detecting the conflict and returning an error during Run, or making the “last flag wins” by tracking the last verbosity flag encountered.
| // VerbosityQuiet suppresses Output, Info, and Warn; only Error passes through. | ||
| VerbosityQuiet VerbosityLevel = iota | ||
| // VerbosityNormal is the default level: all Ui methods pass through unchanged. | ||
| VerbosityNormal | ||
| // VerbosityVerbose is identical to VerbosityNormal at the Ui layer. | ||
| // Commands can inspect [CLI.Verbosity] to emit additional detail when this | ||
| // level is active. | ||
| VerbosityVerbose |
There was a problem hiding this comment.
Godoc for VerbosityQuiet says “only Error passes through”, but LevelFilterUi always forwards Ask/AskSecret regardless of verbosity. This is a public API doc mismatch; consider updating the constant comment to mention interactive prompts are still forwarded (or avoid claiming “only Error”).
| // Ask and AskSecret always pass through regardless of level, because interactive | ||
| // prompts are required for the program to function correctly. | ||
| type LevelFilterUi struct { | ||
| // Level is the minimum verbosity required for output to be forwarded. |
There was a problem hiding this comment.
Field comment for LevelFilterUi.Level describes it as “the minimum verbosity required for output to be forwarded”, but the struct is used as the active verbosity setting (e.g. Level: myCLI.Verbosity()), and each method has its own implied minimum. Consider rewording to something like “Level is the configured verbosity setting; calls are forwarded when Level meets the minimum required for that method” to avoid confusion.
| // Level is the minimum verbosity required for output to be forwarded. | |
| // Level is the configured verbosity setting; calls are forwarded when Level | |
| // meets the minimum required for each method. |
Summary
Closes #8
Adds
--verboseand--quietglobal flags to CLI, a VerbosityLevel type, and a LevelFilterUi wrapper that suppresses output below a configured level.Callers opt in by wrapping their Ui with LevelFilterUi after calling cli.Verbosity():
ui = &cli.LevelFilterUi{Level: myCLI.Verbosity(), Ui: ui}--quietsuppresses Output, Info, and Warn; Error, Ask, and AskSecret always pass through.--verbosepasses everything through at the Ui layer and signals commands to emit extra detail via cli.Verbosity() == VerbosityVerbose. Both flags are disabled by setting VerbosityFlag = "".Type of change
Checklist
go test -race ./...passes locallygolangci-lint run ./...passes locally (or lint issues are intentional and explained)CHANGELOG.mdupdated under## [Unreleased](for features and bug fixes)(
feat: ...,fix: ...,docs: ..., etc.)Testing notes
Breaking change migration guide
Not applicable — fully backward compatible. VerbosityFlag defaults to "verbose" in NewCLI but has no effect unless callers wrap their Ui with LevelFilterUi.