1
mirror of https://github.com/rclone/rclone synced 2024-09-29 04:49:43 +02:00

cmd: Implement specialised help for flags and backends - fixes #2541

Instead of showing all flags/backends all the time, you can type

    rclone help flags
    rclone help flags <regexp>
    rclone help backends
    rclone help backend <name>
This commit is contained in:
Nick Craig-Wood 2018-09-24 21:28:10 +01:00
parent 1557287c64
commit 14128656db

View File

@ -2,7 +2,10 @@ package cmd
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"regexp"
"strings"
"github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs"
"github.com/ncw/rclone/fs/config/configflags" "github.com/ncw/rclone/fs/config/configflags"
@ -16,7 +19,7 @@ import (
// Root is the main rclone command // Root is the main rclone command
var Root = &cobra.Command{ var Root = &cobra.Command{
Use: "rclone", Use: "rclone",
Short: "Sync files and directories to and from local and remote object stores - " + fs.Version, Short: "Show help for rclone commands, flags and backends.",
Long: ` Long: `
Rclone syncs files to and from cloud storage providers as well as Rclone syncs files to and from cloud storage providers as well as
mounting them, listing them in lots of different ways. mounting them, listing them in lots of different ways.
@ -36,17 +39,55 @@ var helpCommand = &cobra.Command{
Use: "help", Use: "help",
Short: Root.Short, Short: Root.Short,
Long: Root.Long, 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 // Show the flags
var helpFlags = &cobra.Command{ var helpFlags = &cobra.Command{
Use: "flags", Use: "flags [<regexp to match>]",
Short: "Show the global flags for rclone", Short: "Show the global flags for rclone",
Run: func(command *cobra.Command, args []string) { 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
}
Root.SetOutput(os.Stdout)
_ = command.Usage() _ = 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 // runRoot implements the main rclone command with no subcommands
func runRoot(cmd *cobra.Command, args []string) { func runRoot(cmd *cobra.Command, args []string) {
if version { if version {
@ -81,12 +122,15 @@ func setupRootCommand(rootCmd *cobra.Command) {
return cmd.CalledAs() != "flags" return cmd.CalledAs() != "flags"
}) })
cobra.AddTemplateFunc("showLocalFlags", func(cmd *cobra.Command) bool { cobra.AddTemplateFunc("showLocalFlags", func(cmd *cobra.Command) bool {
return cmd.CalledAs() != "rclone" // 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 { cobra.AddTemplateFunc("backendFlags", func(cmd *cobra.Command, include bool) *pflag.FlagSet {
backendFlagSet := pflag.NewFlagSet("Backend Flags", pflag.ExitOnError) backendFlagSet := pflag.NewFlagSet("Backend Flags", pflag.ExitOnError)
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
if _, ok := backendFlags[flag.Name]; ok == include { matched := flagsRe == nil || flagsRe.MatchString(flag.Name)
if _, ok := backendFlags[flag.Name]; matched && ok == include {
backendFlagSet.AddFlag(flag) backendFlagSet.AddFlag(flag)
} }
}) })
@ -101,8 +145,8 @@ func setupRootCommand(rootCmd *cobra.Command) {
rootCmd.AddCommand(helpCommand) rootCmd.AddCommand(helpCommand)
helpCommand.AddCommand(helpFlags) helpCommand.AddCommand(helpFlags)
// rootCmd.AddCommand(helpBackend) helpCommand.AddCommand(helpBackends)
// rootCmd.AddCommand(helpBackends) helpCommand.AddCommand(helpBackend)
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
@ -133,7 +177,74 @@ Backend Flags:
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}
Use "{{.CommandPath}} [command] --help" for more information about a command. Use "rclone [command] --help" for more information about a command.
Use "rclone help flags" for more information about global flags. Use "rclone help flags" for to see the global flags.
Use "rclone help backends" for a list of supported services. Use "rclone help backends" for a list of supported services.
` `
// 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")
}
}
}