github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/help.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "regexp" 9 "sort" 10 "strings" 11 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/config/configflags" 14 "github.com/rclone/rclone/fs/config/flags" 15 "github.com/rclone/rclone/fs/filter/filterflags" 16 "github.com/rclone/rclone/fs/log/logflags" 17 "github.com/rclone/rclone/fs/rc/rcflags" 18 "github.com/rclone/rclone/lib/atexit" 19 "github.com/spf13/cobra" 20 "github.com/spf13/pflag" 21 "golang.org/x/text/cases" 22 "golang.org/x/text/language" 23 ) 24 25 // Root is the main rclone command 26 var Root = &cobra.Command{ 27 Use: "rclone", 28 Short: "Show help for rclone commands, flags and backends.", 29 Long: ` 30 Rclone syncs files to and from cloud storage providers as well as 31 mounting them, listing them in lots of different ways. 32 33 See the home page (https://rclone.org/) for installation, usage, 34 documentation, changelog and configuration walkthroughs. 35 36 `, 37 PersistentPostRun: func(cmd *cobra.Command, args []string) { 38 fs.Debugf("rclone", "Version %q finishing with parameters %q", fs.Version, os.Args) 39 atexit.Run() 40 }, 41 ValidArgsFunction: validArgs, 42 DisableAutoGenTag: true, 43 } 44 45 // GeneratingDocs is set by rclone gendocs to alter the format of the 46 // output suitable for the documentation. 47 var GeneratingDocs = false 48 49 // root help command 50 var helpCommand = &cobra.Command{ 51 Use: "help", 52 Short: Root.Short, 53 Long: Root.Long, 54 Run: func(command *cobra.Command, args []string) { 55 Root.SetOutput(os.Stdout) 56 _ = Root.Usage() 57 }, 58 } 59 60 // to filter the flags with 61 var flagsRe *regexp.Regexp 62 63 // Show the flags 64 var helpFlags = &cobra.Command{ 65 Use: "flags [<regexp to match>]", 66 Short: "Show the global flags for rclone", 67 Run: func(command *cobra.Command, args []string) { 68 if len(args) > 0 { 69 re, err := regexp.Compile(`(?i)` + args[0]) 70 if err != nil { 71 log.Fatalf("Failed to compile flags regexp: %v", err) 72 } 73 flagsRe = re 74 } 75 if GeneratingDocs { 76 Root.SetUsageTemplate(docFlagsTemplate) 77 } else { 78 Root.SetOutput(os.Stdout) 79 } 80 _ = command.Usage() 81 }, 82 } 83 84 // Show the backends 85 var helpBackends = &cobra.Command{ 86 Use: "backends", 87 Short: "List the backends available", 88 Run: func(command *cobra.Command, args []string) { 89 showBackends() 90 }, 91 } 92 93 // Show a single backend 94 var helpBackend = &cobra.Command{ 95 Use: "backend <name>", 96 Short: "List full info about a backend", 97 Run: func(command *cobra.Command, args []string) { 98 if len(args) == 0 { 99 Root.SetOutput(os.Stdout) 100 _ = command.Usage() 101 return 102 } 103 showBackend(args[0]) 104 }, 105 } 106 107 // runRoot implements the main rclone command with no subcommands 108 func runRoot(cmd *cobra.Command, args []string) { 109 if version { 110 ShowVersion() 111 resolveExitCode(nil) 112 } else { 113 _ = cmd.Usage() 114 if len(args) > 0 { 115 _, _ = fmt.Fprintf(os.Stderr, "Command not found.\n") 116 } 117 resolveExitCode(errorCommandNotFound) 118 } 119 } 120 121 // setupRootCommand sets default usage, help, and error handling for 122 // the root command. 123 // 124 // Helpful example: https://github.com/moby/moby/blob/master/cli/cobra.go 125 func setupRootCommand(rootCmd *cobra.Command) { 126 ci := fs.GetConfig(context.Background()) 127 // Add global flags 128 configflags.AddFlags(ci, pflag.CommandLine) 129 filterflags.AddFlags(pflag.CommandLine) 130 rcflags.AddFlags(pflag.CommandLine) 131 logflags.AddFlags(pflag.CommandLine) 132 133 Root.Run = runRoot 134 Root.Flags().BoolVarP(&version, "version", "V", false, "Print the version number") 135 136 cobra.AddTemplateFunc("showGlobalFlags", func(cmd *cobra.Command) bool { 137 return cmd.CalledAs() == "flags" || cmd.Annotations["groups"] != "" 138 }) 139 cobra.AddTemplateFunc("showCommands", func(cmd *cobra.Command) bool { 140 return cmd.CalledAs() != "flags" 141 }) 142 cobra.AddTemplateFunc("showLocalFlags", func(cmd *cobra.Command) bool { 143 // Don't show local flags (which are the global ones on the root) on "rclone" and 144 // "rclone help" (which shows the global help) 145 return cmd.CalledAs() != "rclone" && cmd.CalledAs() != "" 146 }) 147 cobra.AddTemplateFunc("flagGroups", func(cmd *cobra.Command) []*flags.Group { 148 // Add the backend flags and check all flags 149 backendGroup := flags.All.NewGroup("Backend", "Backend only flags. These can be set in the config file also.") 150 allRegistered := flags.All.AllRegistered() 151 cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { 152 if _, ok := backendFlags[flag.Name]; ok { 153 backendGroup.Add(flag) 154 } else if _, ok := allRegistered[flag]; ok { 155 // flag is in a group already 156 } else { 157 fs.Errorf(nil, "Flag --%s is unknown", flag.Name) 158 } 159 }) 160 groups := flags.All.Filter(flagsRe).Include(cmd.Annotations["groups"]) 161 return groups.Groups 162 }) 163 rootCmd.SetUsageTemplate(usageTemplate) 164 // rootCmd.SetHelpTemplate(helpTemplate) 165 // rootCmd.SetFlagErrorFunc(FlagErrorFunc) 166 rootCmd.SetHelpCommand(helpCommand) 167 // rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage") 168 // rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help") 169 170 rootCmd.AddCommand(helpCommand) 171 helpCommand.AddCommand(helpFlags) 172 helpCommand.AddCommand(helpBackends) 173 helpCommand.AddCommand(helpBackend) 174 175 // Set command completion for all functions to be the same 176 traverseCommands(rootCmd, func(cmd *cobra.Command) { 177 cmd.ValidArgsFunction = validArgs 178 }) 179 180 cobra.OnInitialize(initConfig) 181 182 } 183 184 // Traverse the tree of commands running fn on each 185 // 186 // I was surprised there wasn't a cobra command to do this 187 func traverseCommands(cmd *cobra.Command, fn func(*cobra.Command)) { 188 fn(cmd) 189 for _, childCmd := range cmd.Commands() { 190 traverseCommands(childCmd, fn) 191 } 192 } 193 194 var usageTemplate = `Usage:{{if .Runnable}} 195 {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} 196 {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} 197 198 Aliases: 199 {{.NameAndAliases}}{{end}}{{if .HasExample}} 200 201 Examples: 202 {{.Example}}{{end}}{{if and (showCommands .) .HasAvailableSubCommands}} 203 204 Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} 205 {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if and (showLocalFlags .) .HasAvailableLocalFlags}} 206 207 Flags: 208 {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if and (showGlobalFlags .) .HasAvailableInheritedFlags}} 209 210 {{ range flagGroups . }}{{ if .Flags.HasFlags }} 211 # {{ .Name }} Flags 212 213 {{ .Help }} 214 215 {{ .Flags.FlagUsages | trimTrailingWhitespaces}} 216 {{ end }}{{ end }} 217 218 Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} 219 {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}} 220 221 Use "rclone [command] --help" for more information about a command. 222 Use "rclone help flags" for to see the global flags. 223 Use "rclone help backends" for a list of supported services. 224 ` 225 226 var docFlagsTemplate = `--- 227 title: "Global Flags" 228 description: "Rclone Global Flags" 229 --- 230 231 # Global Flags 232 233 This describes the global flags available to every rclone command 234 split into groups. 235 236 {{ range flagGroups . }}{{ if .Flags.HasFlags }} 237 ## {{ .Name }} 238 239 {{ .Help }} 240 241 ` + "```" + ` 242 {{ .Flags.FlagUsages | trimTrailingWhitespaces}} 243 ` + "```" + ` 244 245 {{ end }}{{ end }} 246 ` 247 248 // show all the backends 249 func showBackends() { 250 fmt.Printf("All rclone backends:\n\n") 251 for _, backend := range fs.Registry { 252 fmt.Printf(" %-12s %s\n", backend.Prefix, backend.Description) 253 } 254 fmt.Printf("\nTo see more info about a particular backend use:\n") 255 fmt.Printf(" rclone help backend <name>\n") 256 } 257 258 func quoteString(v interface{}) string { 259 switch v.(type) { 260 case string: 261 return fmt.Sprintf("%q", v) 262 } 263 return fmt.Sprint(v) 264 } 265 266 // show a single backend 267 func showBackend(name string) { 268 backend, err := fs.Find(name) 269 if err != nil { 270 log.Fatal(err) 271 } 272 var standardOptions, advancedOptions fs.Options 273 done := map[string]struct{}{} 274 for _, opt := range backend.Options { 275 // Skip if done already (e.g. with Provider options) 276 if _, doneAlready := done[opt.Name]; doneAlready { 277 continue 278 } 279 done[opt.Name] = struct{}{} 280 if opt.Advanced { 281 advancedOptions = append(advancedOptions, opt) 282 } else { 283 standardOptions = append(standardOptions, opt) 284 } 285 } 286 optionsType := "standard" 287 for _, opts := range []fs.Options{standardOptions, advancedOptions} { 288 if len(opts) == 0 { 289 optionsType = "advanced" 290 continue 291 } 292 optionsType = cases.Title(language.Und, cases.NoLower).String(optionsType) 293 fmt.Printf("### %s options\n\n", optionsType) 294 fmt.Printf("Here are the %s options specific to %s (%s).\n\n", optionsType, backend.Name, backend.Description) 295 optionsType = "advanced" 296 for _, opt := range opts { 297 done[opt.Name] = struct{}{} 298 shortOpt := "" 299 if opt.ShortOpt != "" { 300 shortOpt = fmt.Sprintf(" / -%s", opt.ShortOpt) 301 } 302 fmt.Printf("#### --%s%s\n\n", opt.FlagName(backend.Prefix), shortOpt) 303 fmt.Printf("%s\n\n", opt.Help) 304 if opt.IsPassword { 305 fmt.Printf("**NB** Input to this must be obscured - see [rclone obscure](/commands/rclone_obscure/).\n\n") 306 } 307 fmt.Printf("Properties:\n\n") 308 fmt.Printf("- Config: %s\n", opt.Name) 309 fmt.Printf("- Env Var: %s\n", opt.EnvVarName(backend.Prefix)) 310 if opt.Provider != "" { 311 fmt.Printf("- Provider: %s\n", opt.Provider) 312 } 313 fmt.Printf("- Type: %s\n", opt.Type()) 314 defaultValue := opt.GetValue() 315 // Default value and Required are related: Required means option must 316 // have a value, but if there is a default then a value does not have 317 // to be explicitly set and then Required makes no difference. 318 if defaultValue != "" { 319 fmt.Printf("- Default: %s\n", quoteString(defaultValue)) 320 } else { 321 fmt.Printf("- Required: %v\n", opt.Required) 322 } 323 // List examples / possible choices 324 if len(opt.Examples) > 0 { 325 if opt.Exclusive { 326 fmt.Printf("- Choices:\n") 327 } else { 328 fmt.Printf("- Examples:\n") 329 } 330 for _, ex := range opt.Examples { 331 fmt.Printf(" - %s\n", quoteString(ex.Value)) 332 for _, line := range strings.Split(ex.Help, "\n") { 333 fmt.Printf(" - %s\n", line) 334 } 335 } 336 } 337 fmt.Printf("\n") 338 } 339 } 340 if backend.MetadataInfo != nil { 341 fmt.Printf("### Metadata\n\n") 342 fmt.Printf("%s\n\n", strings.TrimSpace(backend.MetadataInfo.Help)) 343 if len(backend.MetadataInfo.System) > 0 { 344 fmt.Printf("Here are the possible system metadata items for the %s backend.\n\n", backend.Name) 345 keys := []string{} 346 for k := range backend.MetadataInfo.System { 347 keys = append(keys, k) 348 } 349 sort.Strings(keys) 350 fmt.Printf("| Name | Help | Type | Example | Read Only |\n") 351 fmt.Printf("|------|------|------|---------|-----------|\n") 352 for _, k := range keys { 353 v := backend.MetadataInfo.System[k] 354 ro := "N" 355 if v.ReadOnly { 356 ro = "**Y**" 357 } 358 fmt.Printf("| %s | %s | %s | %s | %s |\n", k, v.Help, v.Type, v.Example, ro) 359 } 360 fmt.Printf("\n") 361 } 362 fmt.Printf("See the [metadata](/docs/#metadata) docs for more info.\n\n") 363 } 364 }