mirror of
https://github.com/lukaszraczylo/harness-sync.git
synced 2026-10-14 00:57:49 +00:00
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
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMultiSelectNonInteractive(t *testing.T) {
|
||||
sel, err := MultiSelect("pick", []string{"a", "b", "c"}, WithNonInteractive([]string{"a", "c"}))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"a", "c"}, sel)
|
||||
}
|
||||
|
||||
func TestMultiSelectNonInteractiveInvalidChoice(t *testing.T) {
|
||||
_, err := MultiSelect("pick", []string{"a", "b"}, WithNonInteractive([]string{"z"}))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user