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_FILEfunc compLogf(format string, a ...any) { cobra.CompDebugln(fmt.Sprintf(format, a...), true)} // Add remotes to the completions being built upfunc 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 upfunc 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 upfunc 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 BashCompletionV2const 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}
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
}