From 42abaca45508aeab25f99da170e87101b2a327ab Mon Sep 17 00:00:00 2001 From: montag451 Date: Sun, 1 Dec 2024 21:09:53 +0100 Subject: [PATCH 1/2] incus: Improve instance and remote names completion The completion functions for instance and remote names now only return names prefixed by the string to be completed. This allows the caller of these functions to improve their decisions about their own completion behaviour. Signed-off-by: montag451 --- cmd/incus/cluster.go | 6 +++--- cmd/incus/cluster_group.go | 4 ++-- cmd/incus/completion.go | 38 +++++++++++++++++++++++++------------- cmd/incus/copy.go | 2 +- cmd/incus/image.go | 4 ++-- cmd/incus/list.go | 2 +- cmd/incus/move.go | 2 +- cmd/incus/network.go | 4 ++-- cmd/incus/network_acl.go | 2 +- cmd/incus/network_zone.go | 2 +- cmd/incus/profile.go | 6 +++--- cmd/incus/project.go | 4 ++-- cmd/incus/publish.go | 2 +- cmd/incus/storage.go | 4 ++-- 14 files changed, 47 insertions(+), 35 deletions(-) diff --git a/cmd/incus/cluster.go b/cmd/incus/cluster.go index b3898c218bf..c7a7d8a15ff 100644 --- a/cmd/incus/cluster.go +++ b/cmd/incus/cluster.go @@ -155,7 +155,7 @@ func (c *cmdClusterList) Command() *cobra.Command { cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -791,7 +791,7 @@ func (c *cmdClusterEnable) Command() *cobra.Command { cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -1095,7 +1095,7 @@ Pre-defined column shorthand chars: cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/cluster_group.go b/cmd/incus/cluster_group.go index 87f07ae54eb..12e4d253d4d 100644 --- a/cmd/incus/cluster_group.go +++ b/cmd/incus/cluster_group.go @@ -195,7 +195,7 @@ incus cluster group create g1 < config.yaml cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -481,7 +481,7 @@ Pre-defined column shorthand chars: cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/completion.go b/cmd/incus/completion.go index 1852070fc27..31814df102c 100644 --- a/cmd/incus/completion.go +++ b/cmd/incus/completion.go @@ -71,7 +71,7 @@ func (g *cmdGlobal) cmpClusterGroups(toComplete string) ([]string, cobra.ShellCo } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -192,7 +192,7 @@ func (g *cmdGlobal) cmpClusterMembers(toComplete string) ([]string, cobra.ShellC } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -230,7 +230,7 @@ func (g *cmdGlobal) cmpImages(toComplete string) ([]string, cobra.ShellCompDirec } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(true) + remotes, directives := g.cmpRemotes(toComplete, true) results = append(results, remotes...) cmpDirectives |= directives } @@ -330,12 +330,16 @@ func (g *cmdGlobal) cmpInstances(toComplete string) ([]string, cobra.ShellCompDi name = fmt.Sprintf("%s:%s", resource.remote, instName) } + if !strings.HasPrefix(name, toComplete) { + continue + } + results = append(results, name) } } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -375,7 +379,7 @@ func (g *cmdGlobal) cmpInstancesAndSnapshots(toComplete string) ([]string, cobra } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -453,7 +457,7 @@ func (g *cmdGlobal) cmpNetworkACLs(toComplete string) ([]string, cobra.ShellComp } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -607,7 +611,7 @@ func (g *cmdGlobal) cmpNetworks(toComplete string) ([]string, cobra.ShellCompDir } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -789,7 +793,7 @@ func (g *cmdGlobal) cmpNetworkZones(toComplete string) ([]string, cobra.ShellCom } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -882,7 +886,7 @@ func (g *cmdGlobal) cmpProfiles(toComplete string, includeRemotes bool) ([]strin } if includeRemotes && !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -940,7 +944,7 @@ func (g *cmdGlobal) cmpProjects(toComplete string) ([]string, cobra.ShellCompDir } if !strings.Contains(toComplete, ":") { - remotes, directives := g.cmpRemotes(false) + remotes, directives := g.cmpRemotes(toComplete, false) results = append(results, remotes...) cmpDirectives |= directives } @@ -948,7 +952,7 @@ func (g *cmdGlobal) cmpProjects(toComplete string) ([]string, cobra.ShellCompDir return results, cmpDirectives } -func (g *cmdGlobal) cmpRemotes(includeAll bool) ([]string, cobra.ShellCompDirective) { +func (g *cmdGlobal) cmpRemotes(toComplete string, includeAll bool) ([]string, cobra.ShellCompDirective) { results := []string{} for remoteName, rc := range g.conf.Remotes { @@ -956,10 +960,18 @@ func (g *cmdGlobal) cmpRemotes(includeAll bool) ([]string, cobra.ShellCompDirect continue } + if !strings.HasPrefix(remoteName, toComplete) { + continue + } + results = append(results, fmt.Sprintf("%s:", remoteName)) } - return results, cobra.ShellCompDirectiveNoSpace + if len(results) > 0 { + return results, cobra.ShellCompDirectiveNoSpace + } + + return results, cobra.ShellCompDirectiveNoFileComp } func (g *cmdGlobal) cmpRemoteNames() ([]string, cobra.ShellCompDirective) { @@ -1056,7 +1068,7 @@ func (g *cmdGlobal) cmpStoragePools(toComplete string) ([]string, cobra.ShellCom } if !strings.Contains(toComplete, ":") { - remotes, _ := g.cmpRemotes(false) + remotes, _ := g.cmpRemotes(toComplete, false) results = append(results, remotes...) } diff --git a/cmd/incus/copy.go b/cmd/incus/copy.go index 6e631ceed0f..fd288b68df0 100644 --- a/cmd/incus/copy.go +++ b/cmd/incus/copy.go @@ -71,7 +71,7 @@ The pull transfer mode is the default as it is compatible with all server versio } if len(args) == 1 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/image.go b/cmd/incus/image.go index 6d34c188223..80532bd8b61 100644 --- a/cmd/incus/image.go +++ b/cmd/incus/image.go @@ -167,7 +167,7 @@ It requires the source to be an alias and for it to be public.`)) } if len(args) == 1 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -680,7 +680,7 @@ Directory import is only available on Linux and must be performed as root.`)) } if len(args) == 1 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/list.go b/cmd/incus/list.go index f0f336edcf4..1edbac2894e 100644 --- a/cmd/incus/list.go +++ b/cmd/incus/list.go @@ -138,7 +138,7 @@ incus list -c ns,user.comment:comment cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/move.go b/cmd/incus/move.go index 067ef9985f2..8db82c51fc6 100644 --- a/cmd/incus/move.go +++ b/cmd/incus/move.go @@ -71,7 +71,7 @@ incus move / / } if len(args) == 1 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/network.go b/cmd/incus/network.go index 74096756e93..7c051aeb77e 100644 --- a/cmd/incus/network.go +++ b/cmd/incus/network.go @@ -354,7 +354,7 @@ incus network create bar network=baz --type ovn return nil, cobra.ShellCompDirectiveNoFileComp } - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return cmd @@ -1079,7 +1079,7 @@ u - Used by (count)`)) return nil, cobra.ShellCompDirectiveNoFileComp } - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return cmd diff --git a/cmd/incus/network_acl.go b/cmd/incus/network_acl.go index 571f66e4254..a2196c650ff 100644 --- a/cmd/incus/network_acl.go +++ b/cmd/incus/network_acl.go @@ -99,7 +99,7 @@ func (c *cmdNetworkACLList) Command() *cobra.Command { cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/network_zone.go b/cmd/incus/network_zone.go index 5f553b7ff4d..b9101fdd70a 100644 --- a/cmd/incus/network_zone.go +++ b/cmd/incus/network_zone.go @@ -116,7 +116,7 @@ Pre-defined column shorthand chars: cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/profile.go b/cmd/incus/profile.go index 543a77a5672..c5a160bfbad 100644 --- a/cmd/incus/profile.go +++ b/cmd/incus/profile.go @@ -285,7 +285,7 @@ func (c *cmdProfileCopy) Command() *cobra.Command { } if len(args) == 1 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -366,7 +366,7 @@ incus profile create p1 < config.yaml cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -728,7 +728,7 @@ u - Used By`)) cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/project.go b/cmd/incus/project.go index 9e5740878dd..3779a10566f 100644 --- a/cmd/incus/project.go +++ b/cmd/incus/project.go @@ -115,7 +115,7 @@ incus project create p1 < config.yaml cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -536,7 +536,7 @@ u - Used By`)) cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/publish.go b/cmd/incus/publish.go index 0cb7ddebe7a..433fb13e5ec 100644 --- a/cmd/incus/publish.go +++ b/cmd/incus/publish.go @@ -46,7 +46,7 @@ func (c *cmdPublish) Command() *cobra.Command { } if len(args) == 1 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/incus/storage.go b/cmd/incus/storage.go index dbb4a908b79..4b6d2a33798 100644 --- a/cmd/incus/storage.go +++ b/cmd/incus/storage.go @@ -110,7 +110,7 @@ incus create storage s1 dir < config.yaml cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp @@ -692,7 +692,7 @@ Pre-defined column shorthand chars: cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return c.global.cmpRemotes(false) + return c.global.cmpRemotes(toComplete, false) } return nil, cobra.ShellCompDirectiveNoFileComp From d09af5317ae61c8557ed95caae144bcb88d74acb Mon Sep 17 00:00:00 2001 From: montag451 Date: Sun, 1 Dec 2024 21:20:20 +0100 Subject: [PATCH 2/2] incus: Improve completion for `file push` and `file pull` Signed-off-by: montag451 --- cmd/incus/completion.go | 89 +++++++++++++++++++++++++++++++++++++++++ cmd/incus/file.go | 18 +++++++++ 2 files changed, 107 insertions(+) diff --git a/cmd/incus/completion.go b/cmd/incus/completion.go index 31814df102c..58f895498a2 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,89 @@ 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 { + if includeLocalFiles { + return nil, cobra.ShellCompDirectiveDefault + } + + return instances, directives + } + + 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 }