mirror of
https://github.com/rclone/rclone.git
synced 2026-10-09 14:39:28 -04:00
cmd: complete enum flag values from their choices - fixes #5847
This commit is contained in:
1 parent
aa308e3d7c
commit
f01af78486
4 files changed
+131
-1
No files matched your search
@@ -557,6 +557,9 @@ func AddBackendFlags() {
|
||||
func Main() {
|
||||
setupRootCommand(Root)
|
||||
AddBackendFlags()
|
||||
if err := registerEnumFlagCompletions(Root); err != nil {
|
||||
fs.Fatalf(nil, "Failed to register flag completions: %v", err)
|
||||
}
|
||||
if err := Root.Execute(); err != nil {
|
||||
if strings.HasPrefix(err.Error(), "unknown command") && selfupdateEnabled {
|
||||
Root.PrintErrf("You could use '%s selfupdate' to get latest features.\n\n", Root.CommandPath())
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/rclone/rclone/fs/config"
|
||||
"github.com/rclone/rclone/fs/fspath"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Make a debug message while doing the completion.
|
||||
@@ -170,3 +171,41 @@ func validArgs(cmd *cobra.Command, args []string, toComplete string) ([]string,
|
||||
|
||||
return completions, result
|
||||
}
|
||||
|
||||
// registerEnumFlagCompletions adds value completion for scalar enums.
|
||||
func registerEnumFlagCompletions(root *cobra.Command) (err error) {
|
||||
// Cobra normally merges these flags during Execute, after registration.
|
||||
root.PersistentFlags().AddFlagSet(pflag.CommandLine)
|
||||
seen := make(map[*pflag.Flag]bool)
|
||||
traverseCommands(root, func(command *cobra.Command) {
|
||||
register := func(flag *pflag.Flag) {
|
||||
if err != nil || seen[flag] || flag.Hidden {
|
||||
return
|
||||
}
|
||||
seen[flag] = true
|
||||
var value any = flag.Value
|
||||
if option, ok := value.(*fs.Option); ok {
|
||||
value = option.Default
|
||||
}
|
||||
choices, ok := value.(fs.Choices)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, exists := command.GetFlagCompletionFunc(flag.Name); exists {
|
||||
return
|
||||
}
|
||||
err = command.RegisterFlagCompletionFunc(flag.Name, func(_ *cobra.Command, _ []string, prefix string) ([]string, cobra.ShellCompDirective) {
|
||||
var matches []string
|
||||
for _, choice := range choices.Choices() {
|
||||
if strings.HasPrefix(strings.ToLower(choice), strings.ToLower(prefix)) {
|
||||
matches = append(matches, choice)
|
||||
}
|
||||
}
|
||||
return matches, cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
command.Flags().VisitAll(register)
|
||||
command.PersistentFlags().VisitAll(register)
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/vfs/vfscommon"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEnumFlagCompletion(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{"option", []string{"serve", "--cache-mode", ""}, "off\nminimal\nwrites\nfull\n:4\n"},
|
||||
{"prefix", []string{"serve", "--cache-mode", "f"}, "full\n:4\n"},
|
||||
{"equals", []string{"serve", "--cache-mode=f"}, "full\n:4\n"},
|
||||
{"invalid", []string{"serve", "--cache-mode", "invalid"}, ":4\n"},
|
||||
{"global", []string{"serve", "--log-level", "d"}, "DEBUG\n:4\n"},
|
||||
{"inherited", []string{"serve", "http", "--log-level=D"}, "DEBUG\n:4\n"},
|
||||
{"local", []string{"serve", "http", "--local-mode", ""}, "off\nminimal\nwrites\nfull\n:4\n"},
|
||||
{"custom-inherited", []string{"serve", "http", "--custom-mode", ""}, "custom\n:4\n"},
|
||||
{"custom", []string{"serve", "--custom-mode", ""}, "custom\n:4\n"},
|
||||
{"string", []string{"serve", "--name", ""}, ":0\n"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
root := &cobra.Command{Use: "rclone"}
|
||||
serve := &cobra.Command{Use: "serve"}
|
||||
http := &cobra.Command{Use: "http", Run: func(*cobra.Command, []string) {}}
|
||||
local := vfscommon.CacheModeOff
|
||||
http.Flags().Var(&local, "local-mode", "Local mode")
|
||||
serve.AddCommand(http)
|
||||
root.AddCommand(serve)
|
||||
level := fs.LogLevelNotice
|
||||
root.PersistentFlags().Var(&level, "log-level", "Log level")
|
||||
option := &fs.Option{Default: vfscommon.CacheModeOff}
|
||||
serve.PersistentFlags().Var(option, "cache-mode", "Cache mode")
|
||||
custom := vfscommon.CacheModeOff
|
||||
serve.PersistentFlags().Var(&custom, "custom-mode", "Custom mode")
|
||||
require.NoError(t, serve.RegisterFlagCompletionFunc("custom-mode", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"custom"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}))
|
||||
serve.PersistentFlags().String("name", "", "Name")
|
||||
require.NoError(t, registerEnumFlagCompletions(root))
|
||||
var out bytes.Buffer
|
||||
root.SetOut(&out)
|
||||
root.SetErr(&bytes.Buffer{})
|
||||
root.SetArgs(append([]string{"__complete"}, test.args...))
|
||||
require.NoError(t, root.Execute())
|
||||
assert.Equal(t, test.want, out.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumGlobalFlagCompletion(t *testing.T) {
|
||||
previous := pflag.CommandLine
|
||||
pflag.CommandLine = pflag.NewFlagSet("global", pflag.ContinueOnError)
|
||||
t.Cleanup(func() { pflag.CommandLine = previous })
|
||||
level := fs.LogLevelNotice
|
||||
pflag.CommandLine.Var(&level, "log-level", "Log level")
|
||||
root := &cobra.Command{Use: "rclone"}
|
||||
root.AddCommand(&cobra.Command{Use: "copy", Run: func(*cobra.Command, []string) {}})
|
||||
require.NoError(t, registerEnumFlagCompletions(root))
|
||||
var out bytes.Buffer
|
||||
root.SetOut(&out)
|
||||
root.SetErr(&bytes.Buffer{})
|
||||
root.SetArgs([]string{"__complete", "copy", "--log-level", "d"})
|
||||
require.NoError(t, root.Execute())
|
||||
assert.Equal(t, "DEBUG\n:4\n", out.String())
|
||||
}
|
||||
|
||||
func TestEnumFlagCompletionHidden(t *testing.T) {
|
||||
root := &cobra.Command{Use: "rclone"}
|
||||
mode := vfscommon.CacheModeOff
|
||||
root.Flags().Var(&mode, "hidden-mode", "Hidden mode")
|
||||
require.NoError(t, root.Flags().MarkHidden("hidden-mode"))
|
||||
require.NoError(t, registerEnumFlagCompletions(root))
|
||||
_, exists := root.GetFlagCompletionFunc("hidden-mode")
|
||||
assert.False(t, exists)
|
||||
}
|
||||
@@ -14,7 +14,9 @@ var completionDefinition = &cobra.Command{
|
||||
Use: "completion [shell]",
|
||||
Short: `Output completion script for a given shell.`,
|
||||
Long: `Generates a shell completion script for rclone.
|
||||
Run with ` + "`--help`" + ` to list the supported shells.`,
|
||||
Run with ` + "`--help`" + ` to list the supported shells.
|
||||
Enum-valued flags such as ` + "`--log-level`" + ` and ` + "`--vfs-cache-mode`" + `
|
||||
also complete their supported values.`,
|
||||
Annotations: map[string]string{
|
||||
"versionIntroduced": "v1.33",
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user