0df7466d2b
This change addresses two issues with commands that re-used flags from common packages: 1) cobra.Command definitions did not include the command specific prefix in doc strings. 2) Command specific flag prefixes were added after generating command doc strings.
391 lines
12 KiB
Go
391 lines
12 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/config/configflags"
|
|
"github.com/rclone/rclone/fs/filter/filterflags"
|
|
"github.com/rclone/rclone/fs/log/logflags"
|
|
"github.com/rclone/rclone/lib/atexit"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// Root is the main rclone command
|
|
var Root = &cobra.Command{
|
|
Use: "rclone",
|
|
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.
|
|
|
|
See the home page (https://rclone.org/) for installation, usage,
|
|
documentation, changelog and configuration walkthroughs.
|
|
|
|
`,
|
|
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
|
fs.Debugf("rclone", "Version %q finishing with parameters %q", fs.Version, os.Args)
|
|
atexit.Run()
|
|
},
|
|
BashCompletionFunction: bashCompletionFunc,
|
|
DisableAutoGenTag: true,
|
|
}
|
|
|
|
const (
|
|
bashCompletionFunc = `
|
|
__rclone_custom_func() {
|
|
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
|
local cur cword prev words
|
|
if declare -F _init_completion > /dev/null; then
|
|
_init_completion -n : || return
|
|
else
|
|
__rclone_init_completion -n : || return
|
|
fi
|
|
local rclone=(command rclone --ask-password=false)
|
|
if [[ $cur != *:* ]]; then
|
|
local ifs=$IFS
|
|
IFS=$'\n'
|
|
local remotes=($("${rclone[@]}" listremotes 2> /dev/null))
|
|
IFS=$ifs
|
|
local remote
|
|
for remote in "${remotes[@]}"; do
|
|
[[ $remote != $cur* ]] || COMPREPLY+=("$remote")
|
|
done
|
|
if [[ ${COMPREPLY[@]} ]]; then
|
|
local paths=("$cur"*)
|
|
[[ ! -f ${paths[0]} ]] || COMPREPLY+=("${paths[@]}")
|
|
fi
|
|
else
|
|
local path=${cur#*:}
|
|
if [[ $path == */* ]]; then
|
|
local prefix=$(eval printf '%s' "${path%/*}")
|
|
else
|
|
local prefix=
|
|
fi
|
|
local ifs=$IFS
|
|
IFS=$'\n'
|
|
local lines=($("${rclone[@]}" lsf "${cur%%:*}:$prefix" 2> /dev/null))
|
|
IFS=$ifs
|
|
local line
|
|
for line in "${lines[@]}"; do
|
|
local reply=${prefix:+$prefix/}$line
|
|
[[ $reply != $path* ]] || COMPREPLY+=("$reply")
|
|
done
|
|
[[ ! ${COMPREPLY[@]} || $(type -t compopt) != builtin ]] || compopt -o filenames
|
|
fi
|
|
[[ ! ${COMPREPLY[@]} || $(type -t compopt) != builtin ]] || compopt -o nospace
|
|
fi
|
|
}
|
|
`
|
|
)
|
|
|
|
// GeneratingDocs is set by rclone gendocs to alter the format of the
|
|
// output suitable for the documentation.
|
|
var GeneratingDocs = false
|
|
|
|
// root help command
|
|
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 [<regexp to match>]",
|
|
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
|
|
}
|
|
if GeneratingDocs {
|
|
Root.SetUsageTemplate(docFlagsTemplate)
|
|
} else {
|
|
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 <name>",
|
|
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 {
|
|
ShowVersion()
|
|
resolveExitCode(nil)
|
|
} else {
|
|
_ = cmd.Usage()
|
|
if len(args) > 0 {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Command not found.\n")
|
|
}
|
|
resolveExitCode(errorCommandNotFound)
|
|
}
|
|
}
|
|
|
|
// setupRootCommand sets default usage, help, and error handling for
|
|
// the root command.
|
|
//
|
|
// Helpful example: https://github.com/moby/moby/blob/master/cli/cobra.go
|
|
func setupRootCommand(rootCmd *cobra.Command) {
|
|
ci := fs.GetConfig(context.Background())
|
|
// Add global flags
|
|
configflags.AddFlags(ci, pflag.CommandLine)
|
|
filterflags.AddFlags(pflag.CommandLine)
|
|
logflags.AddFlags(pflag.CommandLine)
|
|
|
|
Root.Run = runRoot
|
|
Root.Flags().BoolVarP(&version, "version", "V", false, "Print the version number")
|
|
|
|
cobra.AddTemplateFunc("showGlobalFlags", func(cmd *cobra.Command) bool {
|
|
return cmd.CalledAs() == "flags"
|
|
})
|
|
cobra.AddTemplateFunc("showCommands", func(cmd *cobra.Command) bool {
|
|
return cmd.CalledAs() != "flags"
|
|
})
|
|
cobra.AddTemplateFunc("showLocalFlags", func(cmd *cobra.Command) bool {
|
|
// 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) {
|
|
matched := flagsRe == nil || flagsRe.MatchString(flag.Name)
|
|
if _, ok := backendFlags[flag.Name]; matched && ok == include {
|
|
backendFlagSet.AddFlag(flag)
|
|
}
|
|
})
|
|
return backendFlagSet
|
|
})
|
|
rootCmd.SetUsageTemplate(usageTemplate)
|
|
// rootCmd.SetHelpTemplate(helpTemplate)
|
|
// rootCmd.SetFlagErrorFunc(FlagErrorFunc)
|
|
rootCmd.SetHelpCommand(helpCommand)
|
|
// rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
|
// rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
|
|
|
|
rootCmd.AddCommand(helpCommand)
|
|
helpCommand.AddCommand(helpFlags)
|
|
helpCommand.AddCommand(helpBackends)
|
|
helpCommand.AddCommand(helpBackend)
|
|
|
|
cobra.OnInitialize(initConfig)
|
|
|
|
}
|
|
|
|
var usageTemplate = `Usage:{{if .Runnable}}
|
|
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
|
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
|
|
|
Aliases:
|
|
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
|
|
|
Examples:
|
|
{{.Example}}{{end}}{{if and (showCommands .) .HasAvailableSubCommands}}
|
|
|
|
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if and (showLocalFlags .) .HasAvailableLocalFlags}}
|
|
|
|
Flags:
|
|
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if and (showGlobalFlags .) .HasAvailableInheritedFlags}}
|
|
|
|
Global Flags:
|
|
{{(backendFlags . false).FlagUsages | trimTrailingWhitespaces}}
|
|
|
|
Backend Flags:
|
|
{{(backendFlags . true).FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
|
|
|
|
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
|
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}
|
|
|
|
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.
|
|
`
|
|
|
|
var docFlagsTemplate = `---
|
|
title: "Global Flags"
|
|
description: "Rclone Global Flags"
|
|
---
|
|
|
|
# Global Flags
|
|
|
|
This describes the global flags available to every rclone command
|
|
split into two groups, non backend and backend flags.
|
|
|
|
## Non Backend Flags
|
|
|
|
These flags are available for every command.
|
|
|
|
` + "```" + `
|
|
{{(backendFlags . false).FlagUsages | trimTrailingWhitespaces}}
|
|
` + "```" + `
|
|
|
|
## Backend Flags
|
|
|
|
These flags are available for every command. They control the backends
|
|
and may be set in the config file.
|
|
|
|
` + "```" + `
|
|
{{(backendFlags . true).FlagUsages | trimTrailingWhitespaces}}
|
|
` + "```" + `
|
|
`
|
|
|
|
// 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 <name>\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 (e.g. 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 {
|
|
optionsType = "advanced"
|
|
continue
|
|
}
|
|
optionsType = cases.Title(language.Und, cases.NoLower).String(optionsType)
|
|
fmt.Printf("### %s options\n\n", 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{}{}
|
|
shortOpt := ""
|
|
if opt.ShortOpt != "" {
|
|
shortOpt = fmt.Sprintf(" / -%s", opt.ShortOpt)
|
|
}
|
|
fmt.Printf("#### --%s%s\n\n", opt.FlagName(backend.Prefix), shortOpt)
|
|
fmt.Printf("%s\n\n", opt.Help)
|
|
if opt.IsPassword {
|
|
fmt.Printf("**NB** Input to this must be obscured - see [rclone obscure](/commands/rclone_obscure/).\n\n")
|
|
}
|
|
fmt.Printf("Properties:\n\n")
|
|
fmt.Printf("- Config: %s\n", opt.Name)
|
|
fmt.Printf("- Env Var: %s\n", opt.EnvVarName(backend.Prefix))
|
|
if opt.Provider != "" {
|
|
fmt.Printf("- Provider: %s\n", opt.Provider)
|
|
}
|
|
fmt.Printf("- Type: %s\n", opt.Type())
|
|
defaultValue := opt.GetValue()
|
|
// Default value and Required are related: Required means option must
|
|
// have a value, but if there is a default then a value does not have
|
|
// to be explicitly set and then Required makes no difference.
|
|
if defaultValue != "" {
|
|
fmt.Printf("- Default: %s\n", quoteString(defaultValue))
|
|
} else {
|
|
fmt.Printf("- Required: %v\n", opt.Required)
|
|
}
|
|
// List examples / possible choices
|
|
if len(opt.Examples) > 0 {
|
|
if opt.Exclusive {
|
|
fmt.Printf("- Choices:\n")
|
|
} else {
|
|
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")
|
|
}
|
|
}
|
|
if backend.MetadataInfo != nil {
|
|
fmt.Printf("### Metadata\n\n")
|
|
fmt.Printf("%s\n\n", strings.TrimSpace(backend.MetadataInfo.Help))
|
|
if len(backend.MetadataInfo.System) > 0 {
|
|
fmt.Printf("Here are the possible system metadata items for the %s backend.\n\n", backend.Name)
|
|
keys := []string{}
|
|
for k := range backend.MetadataInfo.System {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
fmt.Printf("| Name | Help | Type | Example | Read Only |\n")
|
|
fmt.Printf("|------|------|------|---------|-----------|\n")
|
|
for _, k := range keys {
|
|
v := backend.MetadataInfo.System[k]
|
|
ro := "N"
|
|
if v.ReadOnly {
|
|
ro = "**Y**"
|
|
}
|
|
fmt.Printf("| %s | %s | %s | %s | %s |\n", k, v.Help, v.Type, v.Example, ro)
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
fmt.Printf("See the [metadata](/docs/#metadata) docs for more info.\n\n")
|
|
}
|
|
}
|