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 }