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}