| 1 | // Package cliflag is the single home of the carlos CLI's claimed |
| 2 | // short-flag namespace (spec 2026-08-19-cli-flag-conventions-design.md): |
| 3 | // the alias table, the one registrar that cannot get the default-value |
| 4 | // trap wrong, and the usage printer that folds an alias into its |
| 5 | // primary. It is a package rather than a corner of package main so that |
| 6 | // verbs living outside main (internal/vet) register through the same |
| 7 | // table instead of open-coding an alias pair. |
| 8 | package cliflag |
| 9 | |
| 10 | import ( |
| 11 | "flag" |
| 12 | "fmt" |
| 13 | "strings" |
| 14 | ) |
| 15 | |
| 16 | // Aliases maps a primary flag name to its short alias. Five letters, |
| 17 | // and nothing else (-d for --destination joined the original four |
| 18 | // in 2026-09): -vv is reserved for a future verbose mode so it can |
| 19 | // never quietly claim -v and collide with --version; -console stays |
| 20 | // long-only by ruling; -f, -n, -y are claimed by logs/update as |
| 21 | // primaries and are not aliases. |
| 22 | var Aliases = map[string]string{ |
| 23 | "app": "a", |
| 24 | "channel": "c", |
| 25 | "destination": "d", |
| 26 | "environment": "e", |
| 27 | "version": "v", |
| 28 | } |
| 29 | |
| 30 | // String registers name — and its short form, when the table claims one |
| 31 | // — against one variable. The alias's StringVar assigns through the |
| 32 | // shared pointer, so both registrations MUST pass the same default; |
| 33 | // repeating def here is the reason this registrar exists rather than |
| 34 | // two open-coded calls (a mismatched alias default silently clobbers |
| 35 | // the primary's). |
| 36 | func String(fs *flag.FlagSet, name, def, usage string) *string { |
| 37 | p := fs.String(name, def, usage) |
| 38 | if short := Aliases[name]; short != "" { |
| 39 | fs.StringVar(p, short, def, "alias for --"+name) |
| 40 | } |
| 41 | return p |
| 42 | } |
| 43 | |
| 44 | // Usage replaces the stock PrintDefaults face for every carlos flag |
| 45 | // set: the alias folds into its primary ("--app, -a"), long flags render |
| 46 | // with the documented double dash, single-letter primaries (-f, -n, -y) |
| 47 | // keep their one dash, and alias registrations never print a row of |
| 48 | // their own. Output derives from the live flag set, so help cannot |
| 49 | // drift from what the parser accepts — the reason to generate rather |
| 50 | // than hand-write 90 flag lists. header is the command's synopsis |
| 51 | // ("usage: carlos ..."); empty derives one from the set's name. |
| 52 | func Usage(fs *flag.FlagSet, header string) func() { |
| 53 | return func() { |
| 54 | w := fs.Output() |
| 55 | if header == "" { |
| 56 | header = "usage: carlos " + fs.Name() + " [flags]" |
| 57 | } |
| 58 | fmt.Fprintln(w, strings.TrimRight(header, "\n")) |
| 59 | aliasOf := make(map[string]string, len(Aliases)) |
| 60 | for primary, short := range Aliases { |
| 61 | aliasOf[short] = primary |
| 62 | } |
| 63 | type row struct{ head, usage string } |
| 64 | var rows []row |
| 65 | width := 0 |
| 66 | isAlias := func(short, primary string) bool { |
| 67 | f := fs.Lookup(short) |
| 68 | return f != nil && f.Usage == "alias for --"+primary |
| 69 | } |
| 70 | fs.VisitAll(func(f *flag.Flag) { |
| 71 | // Fold only pairs String itself registered — recognised by |
| 72 | // the alias's marker usage, not by name alone, so a command |
| 73 | // registering a plain -a of its own is neither hidden nor |
| 74 | // folded into a primary it does not share a value with. |
| 75 | if p, ok := aliasOf[f.Name]; ok && isAlias(f.Name, p) && fs.Lookup(p) != nil { |
| 76 | return |
| 77 | } |
| 78 | head := "--" + f.Name |
| 79 | if len(f.Name) == 1 { |
| 80 | head = "-" + f.Name |
| 81 | } |
| 82 | if short := Aliases[f.Name]; short != "" && isAlias(short, f.Name) { |
| 83 | head += ", -" + short |
| 84 | } |
| 85 | arg, usage := flag.UnquoteUsage(f) |
| 86 | if arg != "" { |
| 87 | head += " <" + arg + ">" |
| 88 | } |
| 89 | switch f.DefValue { |
| 90 | case "", "false", "0", "0s", "[]": |
| 91 | default: |
| 92 | usage += " (default: " + f.DefValue + ")" |
| 93 | } |
| 94 | if len(head) > width { |
| 95 | width = len(head) |
| 96 | } |
| 97 | rows = append(rows, row{head, usage}) |
| 98 | }) |
| 99 | if len(rows) > 0 { |
| 100 | fmt.Fprintln(w, "flags:") |
| 101 | } |
| 102 | for _, r := range rows { |
| 103 | fmt.Fprintf(w, " %-*s %s\n", width, r.head, r.usage) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |