2018-09-13 09:29:26 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2018-09-24 22:28:10 +02:00
|
|
|
"log"
|
2018-09-13 09:29:26 +02:00
|
|
|
"os"
|
2018-09-24 22:28:10 +02:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
2018-09-13 09:29:26 +02:00
|
|
|
|
|
|
|
"github.com/ncw/rclone/fs"
|
|
|
|
"github.com/ncw/rclone/fs/config/configflags"
|
|
|
|
"github.com/ncw/rclone/fs/filter/filterflags"
|
|
|
|
"github.com/ncw/rclone/fs/rc/rcflags"
|
|
|
|
"github.com/ncw/rclone/lib/atexit"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Root is the main rclone command
|
|
|
|
var Root = &cobra.Command{
|
|
|
|
Use: "rclone",
|
2018-09-24 22:28:10 +02:00
|
|
|
Short: "Show help for rclone commands, flags and backends.",
|
2018-09-13 09:29:26 +02:00
|
|
|
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()
|
|
|
|
},
|
2019-02-06 12:21:54 +01:00
|
|
|
BashCompletionFunction: bashCompletionFunc,
|
2018-09-13 09:29:26 +02:00
|
|
|
}
|
|
|
|
|
2019-02-06 12:21:54 +01:00
|
|
|
const (
|
|
|
|
bashCompletionFunc = `
|
2019-02-13 15:15:21 +01:00
|
|
|
__rclone_custom_func() {
|
2019-02-06 12:21:54 +01:00
|
|
|
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
|
cmd: fix completion of remotes
The previous behavior of the remotes completion was that only
alphanumeric characters were allowed in a remote name. This limitation
has been lifted somewhat by #2985, which also allowed an underscore.
With the new implementation introduced in this commit, the completion of
the remote name has been simplified: If there is no colon (":") in the
current word, then complete remote name. Otherwise, complete the path
inside the specified remote. This allows correct completion of all
remote names that are allowed by the config (including - and _).
Actually it matches much more than that, even remote names that are not
allowed by the config, but in such a case there already would be a wrong
identifier in the configuration file.
With this simpler string comparison, we can get rid of the regular
expression, which makes the completion multiple times faster. For a
sample benchmark, try the following:
# Old way
$ time bash -c 'for _ in {1..1000000}; do
[[ remote:path =~ ^[[:alnum:]]*$ ]]; done'
real 0m15,637s
user 0m15,613s
sys 0m0,024s
# New way
$ time bash -c 'for _ in {1..1000000}; do
[[ remote:path != *:* ]]; done'
real 0m1,324s
user 0m1,304s
sys 0m0,020s
2019-03-14 21:59:09 +01:00
|
|
|
if [[ $cur != *:* ]]; then
|
2019-02-06 12:21:54 +01:00
|
|
|
local remote
|
|
|
|
while IFS= read -r remote; do
|
|
|
|
[[ $remote != $cur* ]] || COMPREPLY+=("$remote")
|
|
|
|
done < <(command rclone listremotes)
|
|
|
|
if [[ ${COMPREPLY[@]} ]]; then
|
|
|
|
local paths=("$cur"*)
|
|
|
|
[[ ! -f ${paths[0]} ]] || COMPREPLY+=("${paths[@]}")
|
|
|
|
fi
|
cmd: fix completion of remotes
The previous behavior of the remotes completion was that only
alphanumeric characters were allowed in a remote name. This limitation
has been lifted somewhat by #2985, which also allowed an underscore.
With the new implementation introduced in this commit, the completion of
the remote name has been simplified: If there is no colon (":") in the
current word, then complete remote name. Otherwise, complete the path
inside the specified remote. This allows correct completion of all
remote names that are allowed by the config (including - and _).
Actually it matches much more than that, even remote names that are not
allowed by the config, but in such a case there already would be a wrong
identifier in the configuration file.
With this simpler string comparison, we can get rid of the regular
expression, which makes the completion multiple times faster. For a
sample benchmark, try the following:
# Old way
$ time bash -c 'for _ in {1..1000000}; do
[[ remote:path =~ ^[[:alnum:]]*$ ]]; done'
real 0m15,637s
user 0m15,613s
sys 0m0,024s
# New way
$ time bash -c 'for _ in {1..1000000}; do
[[ remote:path != *:* ]]; done'
real 0m1,324s
user 0m1,304s
sys 0m0,020s
2019-03-14 21:59:09 +01:00
|
|
|
else
|
2019-02-06 12:21:54 +01:00
|
|
|
local path=${cur#*:}
|
|
|
|
if [[ $path == */* ]]; then
|
2019-03-17 11:15:20 +01:00
|
|
|
local prefix=$(eval printf '%s' "${path%/*}")
|
2019-02-06 12:21:54 +01:00
|
|
|
else
|
|
|
|
local prefix=
|
|
|
|
fi
|
|
|
|
local line
|
|
|
|
while IFS= read -r line; do
|
|
|
|
local reply=${prefix:+$prefix/}$line
|
|
|
|
[[ $reply != $path* ]] || COMPREPLY+=("$reply")
|
|
|
|
done < <(rclone lsf "${cur%%:*}:$prefix" 2>/dev/null)
|
2019-03-17 11:15:20 +01:00
|
|
|
[[ ! ${COMPREPLY[@]} ]] || compopt -o filenames
|
2019-02-06 12:21:54 +01:00
|
|
|
fi
|
|
|
|
[[ ! ${COMPREPLY[@]} ]] || compopt -o nospace
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
2018-09-13 09:29:26 +02:00
|
|
|
// root help command
|
|
|
|
var helpCommand = &cobra.Command{
|
|
|
|
Use: "help",
|
|
|
|
Short: Root.Short,
|
|
|
|
Long: Root.Long,
|
2018-09-24 22:28:10 +02:00
|
|
|
Run: func(command *cobra.Command, args []string) {
|
|
|
|
Root.SetOutput(os.Stdout)
|
|
|
|
_ = Root.Usage()
|
|
|
|
},
|
2018-09-13 09:29:26 +02:00
|
|
|
}
|
|
|
|
|
2018-09-24 22:28:10 +02:00
|
|
|
// to filter the flags with
|
|
|
|
var flagsRe *regexp.Regexp
|
|
|
|
|
2018-09-13 09:29:26 +02:00
|
|
|
// Show the flags
|
|
|
|
var helpFlags = &cobra.Command{
|
2018-09-24 22:28:10 +02:00
|
|
|
Use: "flags [<regexp to match>]",
|
2018-09-13 09:29:26 +02:00
|
|
|
Short: "Show the global flags for rclone",
|
|
|
|
Run: func(command *cobra.Command, args []string) {
|
2018-09-24 22:28:10 +02:00
|
|
|
if len(args) > 0 {
|
|
|
|
re, err := regexp.Compile(args[0])
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to compile flags regexp: %v", err)
|
|
|
|
}
|
|
|
|
flagsRe = re
|
|
|
|
}
|
|
|
|
Root.SetOutput(os.Stdout)
|
2018-09-13 09:29:26 +02:00
|
|
|
_ = command.Usage()
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-24 22:28:10 +02:00
|
|
|
// 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])
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-13 09:29:26 +02:00
|
|
|
// 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: http://rtfcode.com/xref/moby-17.03.2-ce/cli/cobra.go
|
|
|
|
func setupRootCommand(rootCmd *cobra.Command) {
|
|
|
|
// Add global flags
|
|
|
|
configflags.AddFlags(pflag.CommandLine)
|
|
|
|
filterflags.AddFlags(pflag.CommandLine)
|
|
|
|
rcflags.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 {
|
2018-09-24 22:28:10 +02:00
|
|
|
// 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() != ""
|
2018-09-13 09:29:26 +02:00
|
|
|
})
|
2018-09-13 09:30:16 +02:00
|
|
|
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) {
|
2018-09-24 22:28:10 +02:00
|
|
|
matched := flagsRe == nil || flagsRe.MatchString(flag.Name)
|
|
|
|
if _, ok := backendFlags[flag.Name]; matched && ok == include {
|
2018-09-13 09:30:16 +02:00
|
|
|
backendFlagSet.AddFlag(flag)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return backendFlagSet
|
|
|
|
})
|
2018-09-13 09:29:26 +02:00
|
|
|
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)
|
2018-09-24 22:28:10 +02:00
|
|
|
helpCommand.AddCommand(helpBackends)
|
|
|
|
helpCommand.AddCommand(helpBackend)
|
2018-09-13 09:29:26 +02:00
|
|
|
|
|
|
|
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:
|
2018-09-13 09:30:16 +02:00
|
|
|
{{(backendFlags . false).FlagUsages | trimTrailingWhitespaces}}
|
|
|
|
|
|
|
|
Backend Flags:
|
|
|
|
{{(backendFlags . true).FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
|
2018-09-13 09:29:26 +02:00
|
|
|
|
|
|
|
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
|
|
|
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}
|
|
|
|
|
2018-09-24 22:28:10 +02:00
|
|
|
Use "rclone [command] --help" for more information about a command.
|
|
|
|
Use "rclone help flags" for to see the global flags.
|
2018-09-13 09:29:26 +02:00
|
|
|
Use "rclone help backends" for a list of supported services.
|
|
|
|
`
|
2018-09-24 22:28:10 +02:00
|
|
|
|
|
|
|
// 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 (eg 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 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fmt.Printf("### %s Options\n\n", strings.Title(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{}{}
|
|
|
|
fmt.Printf("#### --%s\n\n", opt.FlagName(backend.Prefix))
|
|
|
|
fmt.Printf("%s\n\n", opt.Help)
|
|
|
|
fmt.Printf("- Config: %s\n", opt.Name)
|
|
|
|
fmt.Printf("- Env Var: %s\n", opt.EnvVarName(backend.Prefix))
|
|
|
|
fmt.Printf("- Type: %s\n", opt.Type())
|
|
|
|
fmt.Printf("- Default: %s\n", quoteString(opt.GetValue()))
|
|
|
|
if len(opt.Examples) > 0 {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|