diff --git a/cmd/root.go b/cmd/root.go index bc21152ae2bdacaca49664d6124e26876b4c1fd4..ac2bc46ddfda078044b04ebdd2dbba2cce07858b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -107,7 +107,14 @@ var rootCmd = &cobra.Command{ } preset = p - cmdOverrides, err := promptForCommand(command) + // Resolve config before prompting so we can see what the + // preset already provides and skip unnecessary prompts. + peek, err := config.Resolve(preset, command, overrides) + if err != nil { + return err + } + + cmdOverrides, err := promptForCommand(command, peek) if err != nil { return err } @@ -350,34 +357,51 @@ func promptPreset() (string, error) { } // promptForCommand collects required inputs for commands that need them. +// The resolved config is checked first so prompts are skipped for values +// the preset already provides. // Returns CLI overrides to merge, or nil if the command needs no extra input. -func promptForCommand(command string) (map[string][]string, error) { +func promptForCommand(command string, cfg *config.ResolvedConfig) (map[string][]string, error) { switch command { case "restore": - return promptRestore() + return promptRestore(cfg) case "backup": - return promptBackup() + return promptBackup(cfg) default: return nil, nil } } -// promptRestore collects the snapshot ID and target directory for restore. -func promptRestore() (map[string][]string, error) { - snapshotID, target, err := form.RestoreInputs() +// promptRestore collects the snapshot ID and target directory for restore, +// skipping prompts for values already present in the resolved config. +func promptRestore(cfg *config.ResolvedConfig) (map[string][]string, error) { + hasSnapshotID := len(cfg.Arguments) > 0 + hasTarget := cfg.HasFlag("target") + + if hasSnapshotID && hasTarget { + return nil, nil + } + + snapshotID, target, err := form.RestoreInputs(hasSnapshotID, hasTarget) if err != nil { return nil, fmt.Errorf("restore inputs: %w", err) } - return map[string][]string{ - overrideArgumentsKey: {snapshotID}, - "target": {target}, - }, nil + + overrides := make(map[string][]string) + if !hasSnapshotID { + overrides[overrideArgumentsKey] = []string{snapshotID} + } + if !hasTarget { + overrides["target"] = []string{target} + } + return overrides, nil } -// promptBackup collects backup paths if none are likely to come from config. -// The user is always offered the chance to provide paths; config-defined -// _arguments will still take precedence during resolution. -func promptBackup() (map[string][]string, error) { +// promptBackup collects backup paths when none are configured in the preset. +func promptBackup(cfg *config.ResolvedConfig) (map[string][]string, error) { + if len(cfg.Arguments) > 0 { + return nil, nil + } + paths, err := form.BackupPaths() if err != nil { return nil, fmt.Errorf("backup paths: %w", err) diff --git a/internal/config/config.go b/internal/config/config.go index ee446e02b91dccf82432e0f6c8909e6cb6ae48ff..a4aa91988842fa54cf3be1cafc4148519c33c965 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -51,6 +51,18 @@ type Flag struct { Value string // empty for boolean switches } +// HasFlag reports whether the resolved config contains a flag with the given +// name (without leading dashes, e.g. "target" not "--target"). +func (rc *ResolvedConfig) HasFlag(name string) bool { + dashed := flagName(name) + for _, f := range rc.Flags { + if f.Name == dashed { + return true + } + } + return false +} + // rawConfig is the entire parsed TOML file as nested string-keyed maps. type rawConfig map[string]any diff --git a/internal/form/form.go b/internal/form/form.go index 2e8bf16ad362820816c0411a3708ded599d3c531..5e13b2ff6a9ff9fad37c32813f23a571e2f47929 100644 --- a/internal/form/form.go +++ b/internal/form/form.go @@ -50,23 +50,29 @@ func SelectPreset(presets []string) (string, error) { } // RestoreInputs collects the required inputs for `restic restore`: -// a snapshot ID and a target directory. -func RestoreInputs() (snapshotID, target string, err error) { - form := huh.NewForm( - huh.NewGroup( - huh.NewInput(). - Title("Snapshot ID"). - Placeholder("e.g. latest or a1b2c3d4"). - Value(&snapshotID). - Validate(notEmpty("snapshot ID")), - huh.NewInput(). - Title("Target directory"). - Placeholder("e.g. /tmp/restore"). - Value(&target). - Validate(notEmpty("target directory")), - ), - ) +// a snapshot ID and a target directory. Fields whose corresponding +// "has" parameter is true are skipped (already provided by config). +func RestoreInputs(hasSnapshotID, hasTarget bool) (snapshotID, target string, err error) { + var fields []huh.Field + if !hasSnapshotID { + fields = append(fields, huh.NewInput(). + Title("Snapshot ID"). + Placeholder("e.g. latest or a1b2c3d4"). + Value(&snapshotID). + Validate(notEmpty("snapshot ID"))) + } + if !hasTarget { + fields = append(fields, huh.NewInput(). + Title("Target directory"). + Placeholder("e.g. /tmp/restore"). + Value(&target). + Validate(notEmpty("target directory"))) + } + if len(fields) == 0 { + return "", "", nil + } + form := huh.NewForm(huh.NewGroup(fields...)) if err := wrapAbort(form.Run()); err != nil { return "", "", err }