From 68a936a52eda992218ae03a95f39db7ff93f634b Mon Sep 17 00:00:00 2001 From: montag451 Date: Sun, 1 Dec 2024 21:20:20 +0100 Subject: [PATCH] incus: Improve completion for `file push` and `file pull` Signed-off-by: montag451 --- cmd/incus/completion.go | 83 +++++++++++++++++++++++++++++++++++++++++ cmd/incus/file.go | 18 +++++++++ 2 files changed, 101 insertions(+) diff --git a/cmd/incus/completion.go b/cmd/incus/completion.go index 31814df102c..52f2b666848 100644 --- a/cmd/incus/completion.go +++ b/cmd/incus/completion.go @@ -2,6 +2,9 @@ package main import ( "fmt" + "io/fs" + "os" + "path/filepath" "regexp" "strings" @@ -1222,3 +1225,83 @@ func (g *cmdGlobal) cmpStoragePoolVolumes(poolName string) ([]string, cobra.Shel return volumes, cobra.ShellCompDirectiveNoFileComp } + +func isSymlinkToDir(path string, d fs.DirEntry) bool { + if d.Type()&fs.ModeSymlink == 0 { + return false + } + + info, err := os.Stat(path) + if err != nil || !info.IsDir() { + return false + } + + return true +} + +func (g *cmdGlobal) cmpFiles(toComplete string, includeLocalFiles bool) ([]string, cobra.ShellCompDirective) { + instances, directives := g.cmpInstances(toComplete) + for i := range instances { + if strings.HasSuffix(instances[i], ":") { + continue + } + + instances[i] += "/" + } + + if len(instances) > 0 { + directives |= cobra.ShellCompDirectiveNoSpace + } + + if !includeLocalFiles { + return instances, directives + } + + var files []string + sep := string(filepath.Separator) + dir, prefix := filepath.Split(toComplete) + switch prefix { + case ".": + files = append(files, dir+"."+sep) + fallthrough + case "..": + files = append(files, dir+".."+sep) + directives |= cobra.ShellCompDirectiveNoSpace + } + + root, err := filepath.EvalSymlinks(filepath.Dir(dir)) + if err != nil { + return append(instances, files...), directives + } + + _ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil || path == root { + return err + } + + base := filepath.Base(path) + if strings.HasPrefix(base, prefix) { + file := dir + base + switch { + case d.IsDir(): + directives |= cobra.ShellCompDirectiveNoSpace + file += sep + case isSymlinkToDir(path, d): + directives |= cobra.ShellCompDirectiveNoSpace + if base == prefix { + file += sep + } + } + + files = append(files, file) + } + + if d.IsDir() { + return fs.SkipDir + } + + return nil + }) + + return append(instances, files...), directives +} diff --git a/cmd/incus/file.go b/cmd/incus/file.go index e9424d186c4..1d623c8c3f9 100644 --- a/cmd/incus/file.go +++ b/cmd/incus/file.go @@ -475,8 +475,17 @@ func (c *cmdFilePull) Command() *cobra.Command { cmd.Flags().BoolVarP(&c.file.flagMkdir, "create-dirs", "p", false, i18n.G("Create any directories necessary")) cmd.Flags().BoolVarP(&c.file.flagRecursive, "recursive", "r", false, i18n.G("Recursively transfer files")) + cmd.RunE = c.Run + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return c.global.cmpFiles(toComplete, false) + } + + return c.global.cmpFiles(toComplete, true) + } + return cmd } @@ -697,8 +706,17 @@ func (c *cmdFilePush) Command() *cobra.Command { cmd.Flags().IntVar(&c.file.flagUID, "uid", -1, i18n.G("Set the file's uid on push")+"``") cmd.Flags().IntVar(&c.file.flagGID, "gid", -1, i18n.G("Set the file's gid on push")+"``") cmd.Flags().StringVar(&c.file.flagMode, "mode", "", i18n.G("Set the file's perms on push")+"``") + cmd.RunE = c.Run + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return nil, cobra.ShellCompDirectiveDefault + } + + return c.global.cmpFiles(toComplete, true) + } + return cmd }