From 14128656db209582ee6558048e73adcba7aa9516 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 24 Sep 2018 21:28:10 +0100 Subject: [PATCH] cmd: Implement specialised help for flags and backends - fixes #2541 Instead of showing all flags/backends all the time, you can type rclone help flags rclone help flags rclone help backends rclone help backend --- cmd/help.go | 127 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 8 deletions(-) diff --git a/cmd/help.go b/cmd/help.go index bdd768c81..10c00ac23 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -2,7 +2,10 @@ package cmd import ( "fmt" + "log" "os" + "regexp" + "strings" "github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs/config/configflags" @@ -16,7 +19,7 @@ import ( // Root is the main rclone command var Root = &cobra.Command{ Use: "rclone", - Short: "Sync files and directories to and from local and remote object stores - " + fs.Version, + Short: "Show help for rclone commands, flags and backends.", Long: ` Rclone syncs files to and from cloud storage providers as well as mounting them, listing them in lots of different ways. @@ -36,17 +39,55 @@ var helpCommand = &cobra.Command{ Use: "help", Short: Root.Short, Long: Root.Long, + Run: func(command *cobra.Command, args []string) { + Root.SetOutput(os.Stdout) + _ = Root.Usage() + }, } +// to filter the flags with +var flagsRe *regexp.Regexp + // Show the flags var helpFlags = &cobra.Command{ - Use: "flags", + Use: "flags []", Short: "Show the global flags for rclone", Run: func(command *cobra.Command, args []string) { + if len(args) > 0 { + re, err := regexp.Compile(args[0]) + if err != nil { + log.Fatalf("Failed to compile flags regexp: %v", err) + } + flagsRe = re + } + Root.SetOutput(os.Stdout) _ = command.Usage() }, } +// Show the backends +var helpBackends = &cobra.Command{ + Use: "backends", + Short: "List the backends available", + Run: func(command *cobra.Command, args []string) { + showBackends() + }, +} + +// Show a single backend +var helpBackend = &cobra.Command{ + Use: "backend ", + Short: "List full info about a backend", + Run: func(command *cobra.Command, args []string) { + if len(args) == 0 { + Root.SetOutput(os.Stdout) + _ = command.Usage() + return + } + showBackend(args[0]) + }, +} + // runRoot implements the main rclone command with no subcommands func runRoot(cmd *cobra.Command, args []string) { if version { @@ -81,12 +122,15 @@ func setupRootCommand(rootCmd *cobra.Command) { return cmd.CalledAs() != "flags" }) cobra.AddTemplateFunc("showLocalFlags", func(cmd *cobra.Command) bool { - return cmd.CalledAs() != "rclone" + // Don't show local flags (which are the global ones on the root) on "rclone" and + // "rclone help" (which shows the global help) + return cmd.CalledAs() != "rclone" && cmd.CalledAs() != "" }) cobra.AddTemplateFunc("backendFlags", func(cmd *cobra.Command, include bool) *pflag.FlagSet { backendFlagSet := pflag.NewFlagSet("Backend Flags", pflag.ExitOnError) cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { - if _, ok := backendFlags[flag.Name]; ok == include { + matched := flagsRe == nil || flagsRe.MatchString(flag.Name) + if _, ok := backendFlags[flag.Name]; matched && ok == include { backendFlagSet.AddFlag(flag) } }) @@ -101,8 +145,8 @@ func setupRootCommand(rootCmd *cobra.Command) { rootCmd.AddCommand(helpCommand) helpCommand.AddCommand(helpFlags) - // rootCmd.AddCommand(helpBackend) - // rootCmd.AddCommand(helpBackends) + helpCommand.AddCommand(helpBackends) + helpCommand.AddCommand(helpBackend) cobra.OnInitialize(initConfig) @@ -133,7 +177,74 @@ Backend Flags: Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}} -Use "{{.CommandPath}} [command] --help" for more information about a command. -Use "rclone help flags" for more information about global flags. +Use "rclone [command] --help" for more information about a command. +Use "rclone help flags" for to see the global flags. Use "rclone help backends" for a list of supported services. ` + +// show all the backends +func showBackends() { + fmt.Printf("All rclone backends:\n\n") + for _, backend := range fs.Registry { + fmt.Printf(" %-12s %s\n", backend.Prefix, backend.Description) + } + fmt.Printf("\nTo see more info about a particular backend use:\n") + fmt.Printf(" rclone help backend \n") +} + +func quoteString(v interface{}) string { + switch v.(type) { + case string: + return fmt.Sprintf("%q", v) + } + return fmt.Sprint(v) +} + +// show a single backend +func showBackend(name string) { + backend, err := fs.Find(name) + if err != nil { + log.Fatal(err) + } + var standardOptions, advancedOptions fs.Options + done := map[string]struct{}{} + for _, opt := range backend.Options { + // Skip if done already (eg with Provider options) + if _, doneAlready := done[opt.Name]; doneAlready { + continue + } + if opt.Advanced { + advancedOptions = append(advancedOptions, opt) + } else { + standardOptions = append(standardOptions, opt) + } + } + optionsType := "standard" + for _, opts := range []fs.Options{standardOptions, advancedOptions} { + if len(opts) == 0 { + continue + } + fmt.Printf("### %s Options\n\n", strings.Title(optionsType)) + fmt.Printf("Here are the %s options specific to %s (%s).\n\n", optionsType, backend.Name, backend.Description) + optionsType = "advanced" + for _, opt := range opts { + done[opt.Name] = struct{}{} + fmt.Printf("#### --%s\n\n", opt.FlagName(backend.Prefix)) + fmt.Printf("%s\n\n", opt.Help) + fmt.Printf("- Config: %s\n", opt.Name) + fmt.Printf("- Env Var: %s\n", opt.EnvVarName(backend.Prefix)) + fmt.Printf("- Type: %s\n", opt.Type()) + fmt.Printf("- Default: %s\n", quoteString(opt.GetValue())) + if len(opt.Examples) > 0 { + fmt.Printf("- Examples:\n") + for _, ex := range opt.Examples { + fmt.Printf(" - %s\n", quoteString(ex.Value)) + for _, line := range strings.Split(ex.Help, "\n") { + fmt.Printf(" - %s\n", line) + } + } + } + fmt.Printf("\n") + } + } +}