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}