carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

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.
8package cliflag
9
10import (
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.
22var 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).
36func 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.
52func 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