mirror of
https://github.com/rclone/rclone.git
synced 2024-12-22 11:41:17 +08:00
173 lines
4.6 KiB
Go
173 lines
4.6 KiB
Go
|
package cmd
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/rclone/rclone/fs"
|
||
|
"github.com/rclone/rclone/fs/cache"
|
||
|
"github.com/rclone/rclone/fs/config"
|
||
|
"github.com/rclone/rclone/fs/fspath"
|
||
|
"github.com/spf13/cobra"
|
||
|
)
|
||
|
|
||
|
// Make a debug message while doing the completion.
|
||
|
//
|
||
|
// These end up in the file specified by BASH_COMP_DEBUG_FILE
|
||
|
func compLogf(format string, a ...any) {
|
||
|
cobra.CompDebugln(fmt.Sprintf(format, a...), true)
|
||
|
}
|
||
|
|
||
|
// Add remotes to the completions being built up
|
||
|
func addRemotes(toComplete string, completions []string) []string {
|
||
|
remotes := config.FileSections()
|
||
|
for _, remote := range remotes {
|
||
|
remote += ":"
|
||
|
if strings.HasPrefix(remote, toComplete) {
|
||
|
completions = append(completions, remote)
|
||
|
}
|
||
|
}
|
||
|
return completions
|
||
|
}
|
||
|
|
||
|
// Add local files to the completions being built up
|
||
|
func addLocalFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) {
|
||
|
path := filepath.Clean(toComplete)
|
||
|
dir, file := filepath.Split(path)
|
||
|
if dir == "" {
|
||
|
dir = "."
|
||
|
}
|
||
|
if len(dir) > 0 && dir[0] != filepath.Separator && dir[0] != '/' {
|
||
|
dir = strings.TrimRight(dir, string(filepath.Separator))
|
||
|
dir = strings.TrimRight(dir, "/")
|
||
|
}
|
||
|
fi, err := os.Stat(toComplete)
|
||
|
if err == nil {
|
||
|
if fi.IsDir() {
|
||
|
dir = toComplete
|
||
|
file = ""
|
||
|
}
|
||
|
}
|
||
|
fis, err := os.ReadDir(dir)
|
||
|
if err != nil {
|
||
|
compLogf("Failed to read directory %q: %v", dir, err)
|
||
|
return result, completions
|
||
|
}
|
||
|
for _, fi := range fis {
|
||
|
name := fi.Name()
|
||
|
if strings.HasPrefix(name, file) {
|
||
|
path := filepath.Join(dir, name)
|
||
|
if fi.IsDir() {
|
||
|
path += string(filepath.Separator)
|
||
|
result |= cobra.ShellCompDirectiveNoSpace
|
||
|
}
|
||
|
completions = append(completions, path)
|
||
|
}
|
||
|
}
|
||
|
return result, completions
|
||
|
}
|
||
|
|
||
|
// Add remote files to the completions being built up
|
||
|
func addRemoteFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) {
|
||
|
ctx := context.Background()
|
||
|
parent, _, err := fspath.Split(toComplete)
|
||
|
if err != nil {
|
||
|
compLogf("Failed to split path %q: %v", toComplete, err)
|
||
|
return result, completions
|
||
|
}
|
||
|
f, err := cache.Get(ctx, parent)
|
||
|
if err == fs.ErrorIsFile {
|
||
|
completions = append(completions, toComplete)
|
||
|
return result, completions
|
||
|
} else if err != nil {
|
||
|
compLogf("Failed to make Fs %q: %v", parent, err)
|
||
|
return result, completions
|
||
|
}
|
||
|
fis, err := f.List(ctx, "")
|
||
|
if err != nil {
|
||
|
compLogf("Failed to list Fs %q: %v", parent, err)
|
||
|
return result, completions
|
||
|
}
|
||
|
for _, fi := range fis {
|
||
|
remote := fi.Remote()
|
||
|
path := parent + remote
|
||
|
if strings.HasPrefix(path, toComplete) {
|
||
|
if _, ok := fi.(fs.Directory); ok {
|
||
|
path += "/"
|
||
|
result |= cobra.ShellCompDirectiveNoSpace
|
||
|
}
|
||
|
completions = append(completions, path)
|
||
|
}
|
||
|
}
|
||
|
return result, completions
|
||
|
}
|
||
|
|
||
|
// Workaround doesn't seem to be needed for BashCompletionV2
|
||
|
const useColonWorkaround = false
|
||
|
|
||
|
// do command completion
|
||
|
//
|
||
|
// This is called by the command completion scripts using a hidden __complete or __completeNoDesc commands.
|
||
|
func validArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
|
compLogf("ValidArgsFunction called with args=%q toComplete=%q", args, toComplete)
|
||
|
|
||
|
fixBug := -1
|
||
|
if useColonWorkaround {
|
||
|
// Work around what I think is a bug in cobra's bash
|
||
|
// completion which seems to be splitting the arguments on :
|
||
|
// Or there is something I don't understand - ncw
|
||
|
args = append(args, toComplete)
|
||
|
colonArg := -1
|
||
|
for i, arg := range args {
|
||
|
if arg == ":" {
|
||
|
colonArg = i
|
||
|
}
|
||
|
}
|
||
|
if colonArg > 0 {
|
||
|
newToComplete := strings.Join(args[colonArg-1:], "")
|
||
|
fixBug = len(newToComplete) - len(toComplete)
|
||
|
toComplete = newToComplete
|
||
|
}
|
||
|
compLogf("...shuffled args=%q toComplete=%q", args, toComplete)
|
||
|
}
|
||
|
|
||
|
result := cobra.ShellCompDirectiveDefault
|
||
|
completions := []string{}
|
||
|
|
||
|
// See whether we have a valid remote yet
|
||
|
_, err := fspath.Parse(toComplete)
|
||
|
parseOK := err == nil
|
||
|
hasColon := strings.ContainsRune(toComplete, ':')
|
||
|
validRemote := parseOK && hasColon
|
||
|
compLogf("valid remote = %v", validRemote)
|
||
|
|
||
|
// Add remotes for completion
|
||
|
if !validRemote {
|
||
|
completions = addRemotes(toComplete, completions)
|
||
|
}
|
||
|
|
||
|
// Add local files for completion
|
||
|
if !validRemote {
|
||
|
result, completions = addLocalFiles(toComplete, result, completions)
|
||
|
}
|
||
|
|
||
|
// Add remote files for completion
|
||
|
if validRemote {
|
||
|
result, completions = addRemoteFiles(toComplete, result, completions)
|
||
|
}
|
||
|
|
||
|
// If using bug workaround, adjust completions to start with :
|
||
|
if useColonWorkaround && fixBug >= 0 {
|
||
|
for i := range completions {
|
||
|
if len(completions[i]) >= fixBug {
|
||
|
completions[i] = completions[i][fixBug:]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return completions, result
|
||
|
}
|