subcommands.go

  1package cmd
  2
  3import (
  4	"sort"
  5	"strconv"
  6
  7	"github.com/spf13/cobra"
  8	"github.com/spf13/pflag"
  9
 10	"git.secluded.site/keld/internal/restic"
 11)
 12
 13func init() {
 14	registerRootFlags()
 15	registerGlobalFlags()
 16	registerSubcommands()
 17
 18	if err := rootCmd.RegisterFlagCompletionFunc("preset", completePreset); err != nil {
 19		panic(err)
 20	}
 21}
 22
 23func registerSubcommands() {
 24	names := make([]string, 0, len(restic.Commands))
 25	for name := range restic.Commands {
 26		if name == "global" {
 27			continue
 28		}
 29		names = append(names, name)
 30	}
 31	sort.Strings(names)
 32
 33	for _, name := range names {
 34		def := restic.Commands[name]
 35		commandName := name
 36
 37		subCmd := &cobra.Command{
 38			Use:                commandName + " [flags...] [args...]",
 39			Short:              def.Description,
 40			DisableFlagParsing: true,
 41			SilenceUsage:       true,
 42			SilenceErrors:      true,
 43			RunE: func(cmd *cobra.Command, args []string) error {
 44				if wantsCommandHelp(args) {
 45					return cmd.Help()
 46				}
 47
 48				// When invoked with no flags or arguments and the command
 49				// is wrapped (has TUI screens), enter the interactive
 50				// session with the command pre-selected, skipping the
 51				// menu screen. Only do this when running interactively.
 52				if len(args) == 0 && isWrappedCommand(commandName) && isInteractive() {
 53					cmdName, preset, overrides, err := runInteractive(commandName)
 54					if err != nil {
 55						return err
 56					}
 57					if cmdName == "" {
 58						return nil
 59					}
 60					flagPreset = preset
 61					return runCommand(cmdName, cmd, nil, overrides, &preset)
 62				}
 63
 64				return runCommand(commandName, cmd, args, nil, nil)
 65			},
 66		}
 67
 68		registerResticOptions(subCmd.Flags(), def.Options)
 69		rootCmd.AddCommand(subCmd)
 70	}
 71}
 72
 73func registerGlobalFlags() {
 74	global, ok := restic.Commands["global"]
 75	if !ok {
 76		panic("restic command metadata missing global command")
 77	}
 78
 79	flags := rootCmd.PersistentFlags()
 80	for _, opt := range global.Options {
 81		switch opt.Name {
 82		case "help":
 83			continue
 84		case "verbose":
 85			registerCountOption(flags, opt)
 86			continue
 87		}
 88
 89		registerResticOption(flags, opt)
 90	}
 91}
 92
 93func registerResticOptions(flags *pflag.FlagSet, options []restic.Option) {
 94	for _, opt := range options {
 95		if opt.Name == "help" {
 96			continue
 97		}
 98		registerResticOption(flags, opt)
 99	}
100}
101
102func registerResticOption(flags *pflag.FlagSet, opt restic.Option) {
103	shorthand := sanitizeShorthand(opt.Alias)
104
105	switch {
106	case opt.IsBool:
107		flags.BoolP(opt.Name, shorthand, optionDefaultBool(opt.Default), opt.Description)
108	case opt.Repeatable:
109		flags.StringArrayP(opt.Name, shorthand, nil, opt.Description)
110	default:
111		flags.StringP(opt.Name, shorthand, opt.Default, opt.Description)
112	}
113}
114
115func registerCountOption(flags *pflag.FlagSet, opt restic.Option) {
116	shorthand := sanitizeShorthand(opt.Alias)
117	value := flags.CountP(opt.Name, shorthand, opt.Description)
118
119	level, err := strconv.Atoi(opt.Default)
120	if err != nil || level == 0 {
121		return
122	}
123
124	*value = level
125	_ = flags.Set(opt.Name, strconv.Itoa(level))
126}
127
128func sanitizeShorthand(alias string) string {
129	if len(alias) == 1 {
130		return alias
131	}
132	return ""
133}
134
135func optionDefaultBool(raw string) bool {
136	v, err := strconv.ParseBool(raw)
137	if err != nil {
138		return false
139	}
140	return v
141}
142
143func wantsCommandHelp(args []string) bool {
144	for _, arg := range args {
145		if arg == "--" {
146			return false
147		}
148		if arg == "--help" || arg == "-h" {
149			return true
150		}
151	}
152	return false
153}