Files
harness-sync/internal/ui/prompts.go
T
lukaszraczylo 498cdb061d feat: add internal/ui prompts + init subcommand
- internal/ui/prompts.go: MultiSelect with huh TUI + WithNonInteractive for tests
- internal/cli/init.go: NewInit cobra command with --from/--no-prompt flags
- internal/cli/init_test.go: TestInitImportWritesCanonical
- internal/cli/root.go: add resolveRoot helper (shared by profile/rollback/show/init)
- go.mod: add github.com/charmbracelet/huh v1.0.0 as direct dep
2026-05-23 03:32:40 +01:00

63 lines
1.5 KiB
Go

// Package ui provides small interactive prompts and their non-interactive
// equivalents for tests.
package ui
import (
"fmt"
"github.com/charmbracelet/huh"
)
type msOpts struct {
nonInteractive []string
interactive bool
}
// Option configures MultiSelect behaviour.
type Option func(*msOpts)
// WithNonInteractive bypasses the TUI entirely and returns the given choices.
// Test code uses this to make MultiSelect deterministic.
func WithNonInteractive(choices []string) Option {
return func(o *msOpts) {
o.nonInteractive = choices
o.interactive = false
}
}
// MultiSelect prompts the user (or returns the pre-set non-interactive value).
// Choices passed via WithNonInteractive must all appear in `choices`.
func MultiSelect(title string, choices []string, opts ...Option) ([]string, error) {
o := &msOpts{interactive: true}
for _, fn := range opts {
fn(o)
}
if !o.interactive {
valid := map[string]bool{}
for _, c := range choices {
valid[c] = true
}
for _, c := range o.nonInteractive {
if !valid[c] {
return nil, fmt.Errorf("choice %q not in %v", c, choices)
}
}
return o.nonInteractive, nil
}
var picked []string
opts2 := make([]huh.Option[string], 0, len(choices))
for _, c := range choices {
opts2 = append(opts2, huh.NewOption(c, c))
}
form := huh.NewForm(huh.NewGroup(
huh.NewMultiSelect[string]().
Title(title).
Options(opts2...).
Value(&picked),
))
if err := form.Run(); err != nil {
return nil, err
}
return picked, nil
}