carlos / platform Public

cli: one usage printer — aliases folded, double dash canonical

Go's PrintDefaults can neither hide a flag nor emit --, so adding
aliases to it prints two single-dash entries per flag, alphabetically
separated, contradicting the documentation this change writes. The
replacement derives every row from the live flag set — help cannot
drift from what the parser accepts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 4d1116a93d2e8e3ef81d869dec9c39ec4970536d parent 23a7643
2 files changed, +115 −1
  • flags.go +64 −1
  • flags_test.go +51 −0
diff --git a/flags.go b/flags.go
index 48e088599..b1ba4079b 100644
--- a/flags.go
+++ b/flags.go
@@ -1,6 +1,10 @@
package main
-import "flag"
+import (
+ "flag"
+ "fmt"
+ "strings"
+)
// flags.go is the single home of the CLI's claimed short-flag namespace
// (spec 2026-08-19-cli-flag-conventions-design.md): four letters, four
@@ -81,3 +85,62 @@ func flagWasSet(fs *flag.FlagSet, name string) bool {
})
return set
}
+
+// flagUsage replaces the stock PrintDefaults face for every carlos flag
+// set: the alias folds into its primary ("--app, -a"), long flags render
+// with the documented double dash, single-letter primaries (-f, -n, -y)
+// keep their one dash, and alias registrations never print a row of
+// their own. Output derives from the live flag set, so help cannot
+// drift from what the parser accepts — the reason to generate rather
+// than hand-write 90 flag lists. header is the command's synopsis
+// ("usage: carlos ..."); empty derives one from the set's name.
+func flagUsage(fs *flag.FlagSet, header string) func() {
+ return func() {
+ w := fs.Output()
+ if header == "" {
+ header = "usage: carlos " + fs.Name() + " [flags]"
+ }
+ fmt.Fprintln(w, strings.TrimRight(header, "\n"))
+ aliasOf := make(map[string]string, len(cliAliases))
+ for primary, short := range cliAliases {
+ aliasOf[short] = primary
+ }
+ type row struct{ head, usage string }
+ var rows []row
+ width := 0
+ fs.VisitAll(func(f *flag.Flag) {
+ // Fold only pairs the table AND the set both know: a
+ // command registering a plain -a of its own without the
+ // helper would neither be hidden nor claim the fold.
+ if p, ok := aliasOf[f.Name]; ok && fs.Lookup(p) != nil {
+ return
+ }
+ head := "--" + f.Name
+ if len(f.Name) == 1 {
+ head = "-" + f.Name
+ }
+ if short := cliAliases[f.Name]; short != "" && fs.Lookup(short) != nil {
+ head += ", -" + short
+ }
+ arg, usage := flag.UnquoteUsage(f)
+ if arg != "" {
+ head += " <" + arg + ">"
+ }
+ switch f.DefValue {
+ case "", "false", "0":
+ default:
+ usage += " (default: " + f.DefValue + ")"
+ }
+ if len(head) > width {
+ width = len(head)
+ }
+ rows = append(rows, row{head, usage})
+ })
+ if len(rows) > 0 {
+ fmt.Fprintln(w, "flags:")
+ }
+ for _, r := range rows {
+ fmt.Fprintf(w, " %-*s %s\n", width, r.head, r.usage)
+ }
+ }
+}
diff --git a/flags_test.go b/flags_test.go
index 29279fd6c..287d8a22f 100644
--- a/flags_test.go
+++ b/flags_test.go
@@ -3,6 +3,7 @@ package main
import (
"flag"
"io"
+ "strings"
"testing"
)
@@ -106,3 +107,53 @@ func TestFlagWasSetSeesEverySpelling(t *testing.T) {
t.Error("kind reported set when only -a was typed")
}
}
+
+func printedUsage(fs *flag.FlagSet, header string) string {
+ var b strings.Builder
+ fs.SetOutput(&b)
+ flagUsage(fs, header)()
+ return b.String()
+}
+
+// One row per flag: the alias folds into its primary, long names render
+// with --, claimed single-letter primaries (-f, -n, -y) keep one dash,
+// and the alias never gets a row of its own.
+func TestFlagUsageFoldsAliases(t *testing.T) {
+ fs := quietFlagSet("t")
+ appFlag(fs, "")
+ fs.Bool("f", false, "follow: poll for new lines until interrupted")
+ fs.String("console", "", "console to work through")
+ out := printedUsage(fs, "usage: carlos logs [flags] <app>")
+ for _, want := range []string{
+ "usage: carlos logs [flags] <app>",
+ "--app, -a <string>",
+ "app name",
+ "--console <string>",
+ "\n -f ",
+ } {
+ if !strings.Contains(out, want) {
+ t.Errorf("missing %q in:\n%s", want, out)
+ }
+ }
+ if strings.Contains(out, "alias for") {
+ t.Errorf("alias got its own row:\n%s", out)
+ }
+ if strings.Contains(out, "--f") {
+ t.Errorf("single-letter primary rendered double-dash:\n%s", out)
+ }
+}
+
+// Non-zero defaults are shown; zero-value defaults ("" / false / 0) are
+// noise and are not.
+func TestFlagUsageShowsRealDefaultsOnly(t *testing.T) {
+ fs := quietFlagSet("t")
+ aliasedString(fs, "channel", "stable", "release channel to follow")
+ fs.Int("n", 0, "at most this many lines")
+ out := printedUsage(fs, "")
+ if !strings.Contains(out, "(default: stable)") {
+ t.Errorf("stable default not shown:\n%s", out)
+ }
+ if strings.Contains(out, "default: 0") {
+ t.Errorf("zero default shown:\n%s", out)
+ }
+}