form.go

  1// Package form provides interactive huh-based prompts for collecting user
  2// input when keld is invoked via the interactive menu.
  3package form
  4
  5import (
  6	"errors"
  7	"fmt"
  8	"strings"
  9
 10	"charm.land/huh/v2"
 11)
 12
 13// ErrAborted is returned when the user cancels a form (ctrl+c / q).
 14var ErrAborted = errors.New("aborted by user")
 15
 16// wrapAbort converts huh's abort error into our own sentinel.
 17func wrapAbort(err error) error {
 18	if err != nil && errors.Is(err, huh.ErrUserAborted) {
 19		return ErrAborted
 20	}
 21	return err
 22}
 23
 24// SelectPreset displays an interactive selector for the given preset names.
 25// Returns the selected preset, or "" if the user picks "(none)".
 26func SelectPreset(presets []string) (string, error) {
 27	if len(presets) == 0 {
 28		return "", nil
 29	}
 30
 31	opts := make([]huh.Option[string], 0, len(presets)+1)
 32	opts = append(opts, huh.NewOption("(global defaults only)", ""))
 33	for _, p := range presets {
 34		opts = append(opts, huh.NewOption(p, p))
 35	}
 36
 37	var selected string
 38	form := huh.NewForm(
 39		huh.NewGroup(
 40			huh.NewSelect[string]().
 41				Title("Select a preset").
 42				Options(opts...).
 43				Value(&selected),
 44		),
 45	)
 46
 47	if err := wrapAbort(form.Run()); err != nil {
 48		return "", err
 49	}
 50	return selected, nil
 51}
 52
 53// TargetDirectory prompts for a restore target directory.
 54func TargetDirectory() (string, error) {
 55	var target string
 56	form := huh.NewForm(
 57		huh.NewGroup(
 58			huh.NewInput().
 59				Title("Target directory").
 60				Placeholder("e.g. /tmp/restore").
 61				Value(&target).
 62				Validate(notEmpty("target directory")),
 63		),
 64	)
 65
 66	if err := wrapAbort(form.Run()); err != nil {
 67		return "", err
 68	}
 69	return target, nil
 70}
 71
 72// BackupPaths collects one or more paths to back up when none are configured.
 73func BackupPaths() ([]string, error) {
 74	var raw string
 75	form := huh.NewForm(
 76		huh.NewGroup(
 77			huh.NewInput().
 78				Title("Paths to back up").
 79				Description("Space-separated list of files or directories.").
 80				Placeholder("e.g. /home/user /etc").
 81				Value(&raw).
 82				Validate(notEmpty("backup path")),
 83		),
 84	)
 85
 86	if err := wrapAbort(form.Run()); err != nil {
 87		return nil, err
 88	}
 89	return splitFields(raw), nil
 90}
 91
 92// notEmpty returns a validation function that rejects blank or
 93// whitespace-only input.
 94func notEmpty(fieldName string) func(string) error {
 95	return func(s string) error {
 96		if strings.TrimSpace(s) == "" {
 97			return fmt.Errorf("%s is required", fieldName)
 98		}
 99		return nil
100	}
101}
102
103// splitFields splits a string on whitespace.
104func splitFields(s string) []string {
105	return strings.Fields(s)
106}