github.com/artpar/rclone@v1.67.3/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/artpar/rclone/fs"
    13  	"github.com/artpar/rclone/fs/config/configflags"
    14  	"github.com/artpar/rclone/fs/config/flags"
    15  	"github.com/artpar/rclone/fs/filter/filterflags"
    16  	"github.com/artpar/rclone/fs/log/logflags"
    17  	"github.com/artpar/rclone/fs/rc/rcflags"
    18  	"github.com/artpar/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.Println(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  }