Prompter as a narrow consumer interface
byob-prompter.1
contextinterfacesprompter
Problem: a wide prompter interface (AskOne, AskMany, 20 options
per call) couples every command to a specific library's question
shape. When the library changes — or the implementation swaps for
tests — the blast radius is large.
Idea: five methods is the minimum that covers a real CLI. Each
takes context.Context as its first argument so prompts inherit
the caller's deadline and cancellation — a command with a deadline
should be able to abandon a hung prompt instead of waiting on
stdin forever:
type Prompter interface {
Confirm(ctx context.Context, msg string, def bool) (bool, error)
Input(ctx context.Context, msg, def string) (string, error)
Password(ctx context.Context, msg string) (string, error)
Select(ctx context.Context, msg string, options []string) (int, error)
MultiSelect(ctx context.Context, msg string, options []string) ([]int, error)
}
Per byob-interfaces.1, the interface lives in pkg/cmd/prompt/ (or closer
to the consumer) and concrete impls (live, stub) satisfy it
structurally. No library type leaks into the consumer.
Tradeoffs: five methods won't cover every fancy UX (autocomplete
pickers, path completers, multi-line editors). When you need one,
add a dedicated method — don't stretch Input to do it. The ctx
parameter is uniform across all methods even though most callers
will pass the command's root ctx untouched — the consistency
matters more than per-method ergonomics.
Design
// pkg/cmd/prompt/prompt.go
package prompt
type Prompter interface {
Confirm(ctx context.Context, msg string, def bool) (bool, error)
Input(ctx context.Context, msg, def string) (string, error)
Password(ctx context.Context, msg string) (string, error)
Select(ctx context.Context, msg string, options []string) (int, error)
MultiSelect(ctx context.Context, msg string, options []string) ([]int, error)
}
// Factory holds it as an eager field (cheap, like IOStreams):
type Factory struct {
IOStreams *iostreams.IOStreams
Prompter prompt.Prompter
// ...lazy fields
}
Commands accept prompt.Prompter as an Options field, not a
library type. Tests pass prompt.Stub{...}; production passes
prompt.NewLive(io).