github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/cmd/help.go (about)

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