snapshots.go

  1package form
  2
  3import (
  4	"charm.land/huh/v2"
  5
  6	"git.secluded.site/keld/internal/restic"
  7)
  8
  9// manualEntryValue is the sentinel returned by SelectSnapshot when the
 10// user chooses to type a snapshot ID manually instead of picking from
 11// the list.
 12const manualEntryValue = "__manual__"
 13
 14// IsManualEntry reports whether the value returned by SelectSnapshot
 15// indicates the user chose manual entry.
 16func IsManualEntry(v string) bool {
 17	return v == manualEntryValue
 18}
 19
 20// SelectSnapshot presents an interactive picker for the given snapshots.
 21// Each snapshot is shown as a formatted summary line; the selected
 22// snapshot's short ID is returned.
 23//
 24// An "Enter ID manually…" option is always included at the end so users
 25// can type arbitrary IDs (including the snapshotID:subfolder syntax).
 26//
 27// Returns the selected short ID, or the manualEntryValue sentinel
 28// (check with IsManualEntry), or ErrAborted if the user cancels.
 29func SelectSnapshot(snapshots []restic.Snapshot) (string, error) {
 30	opts := make([]huh.Option[string], 0, len(snapshots)+1)
 31	for _, s := range snapshots {
 32		opts = append(opts, huh.NewOption(restic.FormatSnapshotLine(s), s.ShortID))
 33	}
 34	opts = append(opts, huh.NewOption("Enter ID manually…", manualEntryValue))
 35
 36	var selected string
 37	form := huh.NewForm(
 38		huh.NewGroup(
 39			huh.NewSelect[string]().
 40				Title("Select a snapshot").
 41				Options(opts...).
 42				Filtering(true).
 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// ManualSnapshotID prompts the user to type a snapshot ID. This is used
 54// as the fallback when snapshot listing fails or when the user chooses
 55// "Enter ID manually…" from the picker.
 56func ManualSnapshotID() (string, error) {
 57	var id string
 58	form := huh.NewForm(
 59		huh.NewGroup(
 60			huh.NewInput().
 61				Title("Snapshot ID").
 62				Description("Supports snapshotID:subfolder syntax, or \"latest\".").
 63				Placeholder("e.g. latest or a1b2c3d4 or a1b2c3d4:/home/user").
 64				Value(&id).
 65				Validate(notEmpty("snapshot ID")),
 66		),
 67	)
 68
 69	if err := wrapAbort(form.Run()); err != nil {
 70		return "", err
 71	}
 72	return id, nil
 73}
 74
 75// overwriteOptions defines the choices for --overwrite in the order
 76// they're presented to the user.
 77var overwriteOptions = []huh.Option[string]{
 78	huh.NewOption("if-changed  (recommended — only restore what differs)", "if-changed"),
 79	huh.NewOption("if-newer    (only overwrite older files)", "if-newer"),
 80	huh.NewOption("never       (skip existing files entirely)", "never"),
 81	huh.NewOption("always      (restic default — overwrite everything)", "always"),
 82}
 83
 84// SelectOverwrite presents an interactive picker for the --overwrite
 85// behavior. The cursor starts on "if-changed".
 86//
 87// Returns the selected value (one of: if-changed, if-newer, never,
 88// always), or ErrAborted if the user cancels.
 89func SelectOverwrite() (string, error) {
 90	var selected string
 91	form := huh.NewForm(
 92		huh.NewGroup(
 93			huh.NewSelect[string]().
 94				Title("Overwrite existing files?").
 95				Options(overwriteOptions...).
 96				Value(&selected),
 97		),
 98	)
 99
100	if err := wrapAbort(form.Run()); err != nil {
101		return "", err
102	}
103	return selected, nil
104}