Skip to content

Instantly share code, notes, and snippets.

@elliotmr
Created August 26, 2016 13:09
Show Gist options
  • Save elliotmr/8815e429a91bdc54109eeec9d5a53cbd to your computer and use it in GitHub Desktop.
Save elliotmr/8815e429a91bdc54109eeec9d5a53cbd to your computer and use it in GitHub Desktop.
package cmd
import (
"strings"
"github.com/chzyer/readline"
"github.com/spf13/cobra"
)
var shellCmd = &cobra.Command{
Use: "shell",
Short: "This is a shell version of this tool",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
runShell()
},
}
func init() {
RootCmd.AddCommand(shellCmd)
}
func pcFromCommands(parent readline.PrefixCompleterInterface, c *cobra.Command) {
pc := readline.PcItem(c.Use)
parent.SetChildren(append(parent.GetChildren(), pc))
for _, child := range c.Commands() {
pcFromCommands(pc, child)
}
}
func runShell() {
completer := readline.NewPrefixCompleter()
for _, child := range RootCmd.Commands() {
pcFromCommands(completer, child)
}
shell, err := readline.NewEx(&readline.Config{
Prompt: "> ",
AutoComplete: completer,
EOFPrompt: "exit",
})
if err != nil {
panic(err)
}
defer shell.Close()
shell_loop:
for {
l, err := shell.Readline()
if err != nil {
break shell_loop
}
cmd, flags, err := RootCmd.Find(strings.Fields(l))
if err != nil {
shell.Terminal.Write([]byte(err.Error()))
}
cmd.ParseFlags(flags)
cmd.Run(cmd, flags)
}
}
@clintoncampbell
Copy link

Your example is much appreciated. I'm currently looking at a revised version of your primary loop to mirror a standard call a bit more directly. In order to handle the call to cmd.Run directly, I found myself replicating much of the functionality already included in cmd.execute(...).

shell_loop:
	for {
		l, err := shell.Readline()
		if err != nil {
			break shell_loop
		}
		cmd.SetArgs(strings.Fields(l))
		cmd.Execute()
	}
}

Haven't run into any issues yet, but I'll post back if I do.

@scotchneat
Copy link

scotchneat commented Jul 27, 2019

Where is RootCmd supposed to be defined?
I've tried defining it in the shel.go file but I get circular reference errors:

RootCmd refers to
runShell refers to
RootCmd

@kirancerebras
Copy link

I think the assumption in the gist above is to leave the RootCmd in root.go and make shell a subcommand of RootCmd(as done in init())

@elliotmr
Copy link
Author

Honestly, I haven't looked at this in 3 years since I wrote it. What Kiran says sounds right to me, I believe that cobra should generate a global RootCmd struct and this registers the shell command. If there problems it could also be that cobra changed how it works in the last 3 years.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment