byob-go-cli

Implement pflag.Value for custom flag types

byob-command-shape.7 command-shape

Problem: flags whose value needs custom parsing — enums (--format=json|yaml|text), comma-separated lists, URLs, key=value pairs — are often captured as string and parsed inside runFunc. That means parse errors are reported after cobra has already accepted the flag, every command that needs the same shape repeats the parse logic, and --help output shows string instead of a meaningful type name.

Idea: implement pflag.Value (cobra) or flag.Value (stdlib) on a custom type. The interface is tiny: String() string, Set(string) error, and for pflag Type() string. Register the flag with cmd.Flags().Var(&v, "name", "usage"). You get parse-time validation, a custom type name in --help, and a reusable type across every command that needs the same shape. Unit tests can exercise Set() directly without spinning up the command.

Tradeoffs: a handful of extra lines per custom type. Pays back immediately on the second command that uses the same shape — and on the first unit test.

When not to use: single-command, single-use parsing so simple it's not worth a type (a one-off --count that's just an int). Use IntVar and move on.

Design

type Format string

const (
    FormatJSON Format = "json"
    FormatYAML Format = "yaml"
    FormatText Format = "text"
)

func (f *Format) String() string { return string(*f) }
func (f *Format) Type() string   { return "format" }
func (f *Format) Set(s string) error {
    switch Format(s) {
    case FormatJSON, FormatYAML, FormatText:
        *f = Format(s)
        return nil
    }
    return fmt.Errorf("must be one of json|yaml|text")
}

func NewCmdGet(f *Factory, runF func(*Options) error) *cobra.Command {
    opts := &Options{Format: FormatText}
    cmd := &cobra.Command{Use: "get"}
    cmd.Flags().Var(&opts.Format, "format", "output format (json|yaml|text)")
    return cmd
}