cmd: Add shell completion command & generate completion
Cobra (the CLI library) has an advanced support for generating shell completion. It support Bash, Zsh, Fish and PowerShell. This offering covers the majority of use cases with some exceptions, of course. The generated completion scripts have one behavioral difference when compared to the existing solution: flags (--xxx) are not shown by default. User needs to type '-' first to get the completion. https://github.com/containers/toolbox/pull/840 Co-authored-by: Ondřej Míchal <harrymichal@seznam.cz>
This commit is contained in:
parent
5c8ad7a7ec
commit
d69ce6794b
10 changed files with 266 additions and 21 deletions
209
src/cmd/completion.go
Normal file
209
src/cmd/completion.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/toolbox/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate completion script",
|
||||
Long: `To load completions:
|
||||
|
||||
Bash:
|
||||
|
||||
$ source <(toolbox completion bash)
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
# Linux:
|
||||
$ toolbox completion bash > /etc/bash_completion.d/toolbox
|
||||
# macOS:
|
||||
$ toolbox completion bash > /usr/local/etc/bash_completion.d/toolbox
|
||||
|
||||
Zsh:
|
||||
|
||||
# If shell completion is not already enabled in your environment,
|
||||
# you will need to enable it. You can execute the following once:
|
||||
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ toolbox completion zsh > "${fpath[1]}/_toolbox"
|
||||
|
||||
# You will need to start a new shell for this setup to take effect.
|
||||
|
||||
fish:
|
||||
|
||||
$ toolbox completion fish | source
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ toolbox completion fish > ~/.config/fish/completions/toolbox.fish
|
||||
|
||||
`,
|
||||
Hidden: true,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish"},
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
if err := cmd.Root().GenBashCompletion(os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v", err)
|
||||
}
|
||||
case "zsh":
|
||||
if err := cmd.Root().GenZshCompletion(os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v", err)
|
||||
}
|
||||
case "fish":
|
||||
if err := cmd.Root().GenFishCompletion(os.Stdout, true); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v", err)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(completionCmd)
|
||||
}
|
||||
|
||||
func completionEmpty(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func completionCommands(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
commandNames := []string{}
|
||||
commands := cmd.Root().Commands()
|
||||
for _, command := range commands {
|
||||
if strings.Contains(command.Name(), "complet") {
|
||||
continue
|
||||
}
|
||||
commandNames = append(commandNames, command.Name())
|
||||
}
|
||||
|
||||
return commandNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func completionContainerNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
containerNames := []string{}
|
||||
if containers, err := getContainers(); err == nil {
|
||||
for _, container := range containers {
|
||||
containerNames = append(containerNames, container.Names[0])
|
||||
}
|
||||
}
|
||||
|
||||
if len(containerNames) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
return containerNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func completionContainerNamesFiltered(cmd *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
if cmd.Name() == "enter" && len(args) >= 1 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
containerNames := []string{}
|
||||
if containers, err := getContainers(); err == nil {
|
||||
for _, container := range containers {
|
||||
skip := false
|
||||
for _, arg := range args {
|
||||
if container.Names[0] == arg {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
|
||||
containerNames = append(containerNames, container.Names[0])
|
||||
}
|
||||
}
|
||||
|
||||
if len(containerNames) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
return containerNames, cobra.ShellCompDirectiveNoFileComp
|
||||
|
||||
}
|
||||
|
||||
func completionDistroNames(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
imageFlag := cmd.Flag("image")
|
||||
if imageFlag != nil && imageFlag.Changed {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
supportedDistros := utils.GetSupportedDistros()
|
||||
|
||||
return supportedDistros, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func completionImageNames(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
distroFlag := cmd.Flag("distro")
|
||||
if distroFlag != nil && distroFlag.Changed {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
imageNames := []string{}
|
||||
if images, err := getImages(); err == nil {
|
||||
for _, image := range images {
|
||||
if len(image.Names) > 0 {
|
||||
imageNames = append(imageNames, image.Names[0])
|
||||
} else {
|
||||
imageNames = append(imageNames, image.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(imageNames) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
return imageNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func completionImageNamesFiltered(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
imageNames := []string{}
|
||||
if images, err := getImages(); err == nil {
|
||||
for _, image := range images {
|
||||
skip := false
|
||||
var imageName string
|
||||
|
||||
if len(image.Names) > 0 {
|
||||
imageName = image.Names[0]
|
||||
} else {
|
||||
imageName = image.ID
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if arg == imageName {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
|
||||
imageNames = append(imageNames, imageName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(imageNames) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
return imageNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func completionLogLevels(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
|
@ -61,6 +61,7 @@ var createCmd = &cobra.Command{
|
|||
Use: "create",
|
||||
Short: "Create a new toolbox container",
|
||||
RunE: create,
|
||||
ValidArgsFunction: completionEmpty,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -91,6 +92,14 @@ func init() {
|
|||
"Create a toolbox container for a different operating system release than the host")
|
||||
|
||||
createCmd.SetHelpFunc(createHelp)
|
||||
|
||||
if err := createCmd.RegisterFlagCompletionFunc("distro", completionDistroNames); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
if err := createCmd.RegisterFlagCompletionFunc("image", completionImageNames); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(createCmd)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/containers/toolbox/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -38,6 +39,7 @@ var enterCmd = &cobra.Command{
|
|||
Use: "enter",
|
||||
Short: "Enter a toolbox container for interactive use",
|
||||
RunE: enter,
|
||||
ValidArgsFunction: completionContainerNamesFiltered,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -61,6 +63,13 @@ func init() {
|
|||
"",
|
||||
"Enter a toolbox container for a different operating system release than the host")
|
||||
|
||||
if err := enterCmd.RegisterFlagCompletionFunc("container", completionContainerNames); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
if err := enterCmd.RegisterFlagCompletionFunc("distro", completionDistroNames); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
|
||||
enterCmd.SetHelpFunc(enterHelp)
|
||||
rootCmd.AddCommand(enterCmd)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ var helpCmd = &cobra.Command{
|
|||
Use: "help",
|
||||
Short: "Display help information about Toolbox",
|
||||
RunE: help,
|
||||
ValidArgsFunction: completionCommands,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -63,6 +63,7 @@ var listCmd = &cobra.Command{
|
|||
Use: "list",
|
||||
Short: "List existing toolbox containers and images",
|
||||
RunE: list,
|
||||
ValidArgsFunction: completionEmpty,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -38,6 +38,7 @@ var rmCmd = &cobra.Command{
|
|||
Use: "rm",
|
||||
Short: "Remove one or more toolbox containers",
|
||||
RunE: rm,
|
||||
ValidArgsFunction: completionContainerNamesFiltered,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -38,6 +38,7 @@ var rmiCmd = &cobra.Command{
|
|||
Use: "rmi",
|
||||
Short: "Remove one or more toolbox images",
|
||||
RunE: rmi,
|
||||
ValidArgsFunction: completionImageNamesFiltered,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -95,6 +95,10 @@ func init() {
|
|||
|
||||
persistentFlags.CountVarP(&rootFlags.verbose, "verbose", "v", "Set log-level to 'debug'")
|
||||
|
||||
if err := rootCmd.RegisterFlagCompletionFunc("log-level", completionLogLevels); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
|
||||
rootCmd.SetHelpFunc(rootHelp)
|
||||
|
||||
usageTemplate := fmt.Sprintf("Run '%s --help' for usage.", executableBase)
|
||||
|
|
|
@ -45,6 +45,7 @@ var runCmd = &cobra.Command{
|
|||
Use: "run",
|
||||
Short: "Run a command in an existing toolbox container",
|
||||
RunE: run,
|
||||
ValidArgsFunction: completionEmpty,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -70,6 +71,14 @@ func init() {
|
|||
"Run command inside a toolbox container for a different operating system release than the host")
|
||||
|
||||
runCmd.SetHelpFunc(runHelp)
|
||||
|
||||
if err := runCmd.RegisterFlagCompletionFunc("container", completionContainerNames); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
if err := runCmd.RegisterFlagCompletionFunc("distro", completionDistroNames); err != nil {
|
||||
logrus.Panicf("failed to register flag completion function: %v", err)
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(runCmd)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ meson_go_fmt_program = find_program('meson_go_fmt.py')
|
|||
|
||||
sources = files(
|
||||
'toolbox.go',
|
||||
'cmd/completion.go',
|
||||
'cmd/create.go',
|
||||
'cmd/enter.go',
|
||||
'cmd/help.go',
|
||||
|
|
Loading…
Reference in a new issue