github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/cmd/help.go (about)

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