Merge pull request #10 from posener/alongside
Enable completion and executable be the same command
This commit is contained in:
commit
1c743d8c0b
163
cmd/cmd.go
163
cmd/cmd.go
|
@ -11,81 +11,122 @@ import (
|
|||
"github.com/posener/complete/cmd/install"
|
||||
)
|
||||
|
||||
// Run is used when running complete in command line mode.
|
||||
// this is used when the complete is not completing words, but to
|
||||
// install it or uninstall it.
|
||||
func Run(cmd string) {
|
||||
c := parseFlags(cmd)
|
||||
err := c.validate()
|
||||
if err != nil {
|
||||
os.Stderr.WriteString(err.Error() + "\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if !c.yes && !prompt(c.action(), cmd) {
|
||||
fmt.Println("Cancelling...")
|
||||
os.Exit(2)
|
||||
}
|
||||
fmt.Println(c.action() + "ing...")
|
||||
if c.install {
|
||||
err = install.Install(cmd)
|
||||
} else {
|
||||
err = install.Uninstall(cmd)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("%s failed! %s\n", c.action(), err)
|
||||
os.Exit(3)
|
||||
}
|
||||
fmt.Println("Done!")
|
||||
}
|
||||
// CLI for command line
|
||||
type CLI struct {
|
||||
Name string
|
||||
|
||||
// prompt use for approval
|
||||
func prompt(action, cmd string) bool {
|
||||
fmt.Printf("%s completion for %s? ", action, cmd)
|
||||
var answer string
|
||||
fmt.Scanln(&answer)
|
||||
|
||||
switch strings.ToLower(answer) {
|
||||
case "y", "yes":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// config for command line
|
||||
type config struct {
|
||||
install bool
|
||||
uninstall bool
|
||||
yes bool
|
||||
}
|
||||
|
||||
// create a config from command line arguments
|
||||
func parseFlags(cmd string) config {
|
||||
var c config
|
||||
flag.BoolVar(&c.install, "install", false,
|
||||
fmt.Sprintf("Install completion for %s command", cmd))
|
||||
flag.BoolVar(&c.uninstall, "uninstall", false,
|
||||
fmt.Sprintf("Uninstall completion for %s command", cmd))
|
||||
flag.BoolVar(&c.yes, "y", false, "Don't prompt user for typing 'yes'")
|
||||
const (
|
||||
defaultInstallName = "install"
|
||||
defaultUninstallName = "uninstall"
|
||||
)
|
||||
|
||||
// Run is used when running complete in command line mode.
|
||||
// this is used when the complete is not completing words, but to
|
||||
// install it or uninstall it.
|
||||
func (f *CLI) Run() bool {
|
||||
|
||||
// add flags and parse them in case they were not added and parsed
|
||||
// by the main program
|
||||
f.AddFlags(nil, "", "")
|
||||
flag.Parse()
|
||||
return c
|
||||
|
||||
err := f.validate()
|
||||
if err != nil {
|
||||
os.Stderr.WriteString(err.Error() + "\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch {
|
||||
case f.install:
|
||||
f.prompt()
|
||||
err = install.Install(f.Name)
|
||||
case f.uninstall:
|
||||
f.prompt()
|
||||
err = install.Uninstall(f.Name)
|
||||
default:
|
||||
// non of the action flags matched,
|
||||
// returning false should make the real program execute
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("%s failed! %s\n", f.action(), err)
|
||||
os.Exit(3)
|
||||
}
|
||||
fmt.Println("Done!")
|
||||
return true
|
||||
}
|
||||
|
||||
// validate the config
|
||||
func (c config) validate() error {
|
||||
if c.install && c.uninstall {
|
||||
return errors.New("Install and uninstall are exclusive")
|
||||
// prompt use for approval
|
||||
// exit if approval was not given
|
||||
func (f *CLI) prompt() {
|
||||
defer fmt.Println(f.action() + "ing...")
|
||||
if f.yes {
|
||||
return
|
||||
}
|
||||
if !c.install && !c.uninstall {
|
||||
return errors.New("Must specify -install or -uninstall")
|
||||
fmt.Printf("%s completion for %s? ", f.action(), f.Name)
|
||||
var answer string
|
||||
fmt.Scanln(&answer)
|
||||
|
||||
switch strings.ToLower(answer) {
|
||||
case "y", "yes":
|
||||
return
|
||||
default:
|
||||
fmt.Println("Cancelling...")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// AddFlags adds the CLI flags to the flag set.
|
||||
// If flags is nil, the default command line flags will be taken.
|
||||
// Pass non-empty strings as installName and uninstallName to override the default
|
||||
// flag names.
|
||||
func (f *CLI) AddFlags(flags *flag.FlagSet, installName, uninstallName string) {
|
||||
if flags == nil {
|
||||
flags = flag.CommandLine
|
||||
}
|
||||
|
||||
if installName == "" {
|
||||
installName = defaultInstallName
|
||||
}
|
||||
if uninstallName == "" {
|
||||
uninstallName = defaultUninstallName
|
||||
}
|
||||
|
||||
if flags.Lookup(installName) == nil {
|
||||
flags.BoolVar(&f.install, installName, false,
|
||||
fmt.Sprintf("Install completion for %s command", f.Name))
|
||||
}
|
||||
if flags.Lookup(uninstallName) == nil {
|
||||
flags.BoolVar(&f.uninstall, uninstallName, false,
|
||||
fmt.Sprintf("Uninstall completion for %s command", f.Name))
|
||||
}
|
||||
if flags.Lookup("y") == nil {
|
||||
flags.BoolVar(&f.yes, "y", false, "Don't prompt user for typing 'yes'")
|
||||
}
|
||||
}
|
||||
|
||||
// validate the CLI
|
||||
func (f *CLI) validate() error {
|
||||
if f.install && f.uninstall {
|
||||
return errors.New("Install and uninstall are mutually exclusive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// action name according to the config values.
|
||||
func (c config) action() string {
|
||||
if c.install {
|
||||
// action name according to the CLI values.
|
||||
func (f *CLI) action() string {
|
||||
switch {
|
||||
case f.install:
|
||||
return "Install"
|
||||
case f.uninstall:
|
||||
return "Uninstall"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
return "Uninstall"
|
||||
}
|
||||
|
|
|
@ -18,23 +18,42 @@ const (
|
|||
envDebug = "COMP_DEBUG"
|
||||
)
|
||||
|
||||
// Run get a command, get the typed arguments from environment
|
||||
// variable, and print out the complete options
|
||||
// Complete structs define completion for a command with CLI options
|
||||
type Complete struct {
|
||||
Command Command
|
||||
cmd.CLI
|
||||
}
|
||||
|
||||
// New creates a new complete command.
|
||||
// name is the name of command we want to auto complete.
|
||||
// IMPORTANT: it must be the same name - if the auto complete
|
||||
// completes the 'go' command, name must be equal to "go".
|
||||
func Run(name string, c Command) {
|
||||
// command is the struct of the command completion.
|
||||
func New(name string, command Command) *Complete {
|
||||
return &Complete{
|
||||
Command: command,
|
||||
CLI: cmd.CLI{Name: name},
|
||||
}
|
||||
}
|
||||
|
||||
// Run get a command, get the typed arguments from environment
|
||||
// variable, and print out the complete options
|
||||
// returns success if the completion ran or if the cli matched
|
||||
// any of the given flags, false otherwise
|
||||
func (c *Complete) Run() bool {
|
||||
args, ok := getLine()
|
||||
if !ok {
|
||||
cmd.Run(name)
|
||||
return
|
||||
// make sure flags parsed,
|
||||
// in case they were not added in the main program
|
||||
return c.CLI.Run()
|
||||
}
|
||||
Log("Completing args: %s", args)
|
||||
|
||||
options := complete(c, args)
|
||||
options := complete(c.Command, args)
|
||||
|
||||
Log("Completion: %s", options)
|
||||
output(options)
|
||||
return true
|
||||
}
|
||||
|
||||
// complete get a command an command line arguments and returns
|
|
@ -0,0 +1,51 @@
|
|||
// Package self
|
||||
// a program that complete itself
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// add a variable to the program
|
||||
var name string
|
||||
flag.StringVar(&name, "name", "", "Give your name")
|
||||
|
||||
// create the complete command
|
||||
cmp := complete.New(
|
||||
"self",
|
||||
complete.Command{Flags: complete.Flags{"name": complete.PredictAnything}},
|
||||
)
|
||||
|
||||
// AddFlags adds the completion flags to the program flags,
|
||||
// in case of using non-default flag set, it is possible to pass
|
||||
// it as an argument.
|
||||
// it is possible to set custom flags name
|
||||
// so when one will type 'self -h', he will see '-complete' to install the
|
||||
// completion and -uncomplete to uninstall it.
|
||||
cmp.AddFlags(nil, "complete", "uncomplete")
|
||||
|
||||
// parse the flags - both the program's flags and the completion flags
|
||||
flag.Parse()
|
||||
|
||||
// run the completion, in case that the completion was invoked
|
||||
// and ran as a completion script or handled a flag that passed
|
||||
// as argument, the Run method will return true,
|
||||
// in that case, our program have nothing to do and should return.
|
||||
if cmp.Run() {
|
||||
return
|
||||
}
|
||||
|
||||
// if the completion did not do anything, we can run our program logic here.
|
||||
if name == "" {
|
||||
fmt.Println("Your name is missing")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Hi,", name)
|
||||
}
|
|
@ -185,5 +185,5 @@ func main() {
|
|||
},
|
||||
}
|
||||
|
||||
complete.Run("go", gogo)
|
||||
complete.New("go", gogo).Run()
|
||||
}
|
||||
|
|
|
@ -15,100 +15,92 @@ func TestMatch(t *testing.T) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
m Matcher
|
||||
type matcherTest struct {
|
||||
prefix string
|
||||
want bool
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
m Matcher
|
||||
tests []matcherTest
|
||||
}{
|
||||
{
|
||||
m: Prefix("abcd"),
|
||||
prefix: "",
|
||||
want: true,
|
||||
m: Prefix("abcd"),
|
||||
tests: []matcherTest{
|
||||
{prefix: "", want: true},
|
||||
{prefix: "ab", want: true},
|
||||
{prefix: "ac", want: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
m: Prefix("abcd"),
|
||||
prefix: "ab",
|
||||
want: true,
|
||||
m: Prefix(""),
|
||||
tests: []matcherTest{
|
||||
{prefix: "ac", want: false},
|
||||
{prefix: "", want: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
m: Prefix("abcd"),
|
||||
prefix: "ac",
|
||||
want: false,
|
||||
m: File("file.txt"),
|
||||
tests: []matcherTest{
|
||||
{prefix: "", want: true},
|
||||
{prefix: "f", want: true},
|
||||
{prefix: "./f", want: true},
|
||||
{prefix: "file.", want: true},
|
||||
{prefix: "./file.", want: true},
|
||||
{prefix: "file.txt", want: true},
|
||||
{prefix: "./file.txt", want: true},
|
||||
{prefix: "other.txt", want: false},
|
||||
{prefix: "/other.txt", want: false},
|
||||
{prefix: "/file.txt", want: false},
|
||||
{prefix: "/fil", want: false},
|
||||
{prefix: "/file.txt2", want: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
m: Prefix(""),
|
||||
prefix: "ac",
|
||||
want: false,
|
||||
m: File("./file.txt"),
|
||||
tests: []matcherTest{
|
||||
{prefix: "", want: true},
|
||||
{prefix: "f", want: true},
|
||||
{prefix: "./f", want: true},
|
||||
{prefix: "file.", want: true},
|
||||
{prefix: "./file.", want: true},
|
||||
{prefix: "file.txt", want: true},
|
||||
{prefix: "./file.txt", want: true},
|
||||
{prefix: "other.txt", want: false},
|
||||
{prefix: "/other.txt", want: false},
|
||||
{prefix: "/file.txt", want: false},
|
||||
{prefix: "/fil", want: false},
|
||||
{prefix: "/file.txt2", want: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
m: Prefix(""),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("file.txt"),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("./file.txt"),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("./file.txt"),
|
||||
prefix: "f",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("./file.txt"),
|
||||
prefix: "file.",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("./file.txt"),
|
||||
prefix: "./f",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("./file.txt"),
|
||||
prefix: "other.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: File("./file.txt"),
|
||||
prefix: "/file.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: File("/file.txt"),
|
||||
prefix: "file.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: File("/file.txt"),
|
||||
prefix: "./file.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: File("/file.txt"),
|
||||
prefix: "/file.txt",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: File("/file.txt"),
|
||||
prefix: "/fil",
|
||||
want: true,
|
||||
m: File("/file.txt"),
|
||||
tests: []matcherTest{
|
||||
{prefix: "", want: false},
|
||||
{prefix: "f", want: false},
|
||||
{prefix: "./f", want: false},
|
||||
{prefix: "file.", want: false},
|
||||
{prefix: "./file.", want: false},
|
||||
{prefix: "file.txt", want: false},
|
||||
{prefix: "./file.txt", want: false},
|
||||
{prefix: "other.txt", want: false},
|
||||
{prefix: "/other.txt", want: false},
|
||||
{prefix: "/file.txt", want: true},
|
||||
{prefix: "/fil", want: true},
|
||||
{prefix: "/file.txt2", want: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
name := tt.m.String() + "/" + tt.prefix
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tt.m.Match(tt.prefix)
|
||||
if got != tt.want {
|
||||
t.Errorf("Failed %s: got = %t, want: %t", name, got, tt.want)
|
||||
}
|
||||
})
|
||||
for _, ttt := range tt.tests {
|
||||
name := "matcher:" + tt.m.String() + "/prefix:" + ttt.prefix
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tt.m.Match(ttt.prefix)
|
||||
if got != ttt.want {
|
||||
t.Errorf("Failed %s: got = %t, want: %t", name, got, ttt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,12 @@ func main() {
|
|||
|
||||
// run the command completion, as part of the main() function.
|
||||
// this triggers the autocompletion when needed.
|
||||
// name must be exactly as the binary that we want to complete.
|
||||
complete.Run("run", run)
|
||||
// name must be exactly as the binary that we want to complete.
|
||||
complete.New("run", run).Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Self completing program
|
||||
|
||||
In case that the program that we want to complete is written in go we
|
||||
can make it self completing. Here is an [example](./example/self/main.go)
|
||||
|
|
Loading…
Reference in New Issue