Skip to content

feat: implement verbosity levels with --verbose and --quiet flags#9

Merged
timkrebs merged 2 commits into
mainfrom
8-verbosity-levels
Mar 30, 2026
Merged

feat: implement verbosity levels with --verbose and --quiet flags#9
timkrebs merged 2 commits into
mainfrom
8-verbosity-levels

Conversation

@timkrebs
Copy link
Copy Markdown
Owner

@timkrebs timkrebs commented Mar 30, 2026

Summary

Closes #8
Adds --verbose and --quiet global 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}

--quiet suppresses Output, Info, and Warn; Error, Ask, and AskSecret always pass through. --verbose passes 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

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behavior)
  • Refactor (no functional change)
  • Documentation update
  • CI / tooling change

Checklist

  • [ x] Tests added or updated to cover the change
  • [ x] go test -race ./... passes locally
  • golangci-lint run ./... passes locally (or lint issues are intentional and explained)
  • [ x] Public API additions / changes have godoc comments
  • CHANGELOG.md updated under ## [Unreleased] (for features and bug fixes)
  • [x ] PR title follows Conventional Commits format
    (feat: ..., fix: ..., docs: ..., etc.)

Testing notes

  • TestLevelFilterUi_implements | LevelFilterUi satisfies the Ui interface
  • TestLevelFilterUi_Verbose | All methods pass through at VerbosityVerbose
  • TestLevelFilterUi_Quiet | Output/Info/Warn suppressed; Error passes
  • TestLevelFilterUi_Quiet_ErrorAlwaysPasses | Error is never suppressed
  • TestLevelFilterUi_Ask | Ask always passes through even in quiet mode
  • TestLevelFilterUi_AskSecret | AskSecret always passes through even in quiet mode
  • TestLevelFilterUi_Composable | Composes correctly with PrefixedUi

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.

Copilot AI review requested due to automatic review settings March 30, 2026 13:35
@timkrebs timkrebs linked an issue Mar 30, 2026 that may be closed by this pull request
@timkrebs timkrebs merged commit 7dc1886 into main Mar 30, 2026
6 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 + LevelFilterUi UI wrapper to suppress Output/Info/Warn in quiet mode.
  • Add CLI.VerbosityFlag and CLI.Verbosity() plus arg parsing for --verbose and --quiet.
  • Add tests covering verbosity parsing and LevelFilterUi behavior/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.

Comment thread cli.go
Comment on lines +160 to +161
// and double-hyphen forms are accepted (e.g. -verbose and --verbose).
// Set to "" to disable verbosity flags entirely.
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Comment thread cli_args.go
Comment on lines +40 to +50
// 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
}
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread cli.go
Comment on lines +237 to +246
func (c *CLI) Verbosity() VerbosityLevel {
c.once.Do(c.init)
switch {
case c.isQuiet:
return VerbosityQuiet
case c.isVerbose:
return VerbosityVerbose
default:
return VerbosityNormal
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread ui_level_filter.go
Comment on lines +8 to +15
// 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
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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”).

Copilot uses AI. Check for mistakes.
Comment thread ui_level_filter.go
// 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.
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verbosity Levels

2 participants