summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/cmd.go129
-rw-r--r--complete.go (renamed from run.go)31
-rw-r--r--complete_test.go (renamed from run_test.go)0
-rw-r--r--example/self/main.go51
-rw-r--r--gocomplete/complete.go2
-rw-r--r--match/match_test.go150
-rw-r--r--readme.md9
7 files changed, 240 insertions, 132 deletions
diff --git a/cmd/cmd.go b/cmd/cmd.go
index d0b341c..f796ec8 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -11,81 +11,122 @@ import (
"github.com/posener/complete/cmd/install"
)
+// CLI for command line
+type CLI struct {
+ Name string
+
+ install bool
+ uninstall bool
+ yes bool
+}
+
+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 Run(cmd string) {
- c := parseFlags(cmd)
- err := c.validate()
+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()
+
+ err := f.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)
+
+ 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", c.action(), err)
+ fmt.Printf("%s failed! %s\n", f.action(), err)
os.Exit(3)
}
fmt.Println("Done!")
+ return true
}
// prompt use for approval
-func prompt(action, cmd string) bool {
- fmt.Printf("%s completion for %s? ", action, cmd)
+// exit if approval was not given
+func (f *CLI) prompt() {
+ defer fmt.Println(f.action() + "ing...")
+ if f.yes {
+ return
+ }
+ fmt.Printf("%s completion for %s? ", f.action(), f.Name)
var answer string
fmt.Scanln(&answer)
switch strings.ToLower(answer) {
case "y", "yes":
- return true
+ return
default:
- return false
+ fmt.Println("Cancelling...")
+ os.Exit(1)
}
}
-// config for command line
-type config struct {
- install bool
- uninstall bool
- yes bool
-}
+// 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
+ }
-// 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'")
- flag.Parse()
- return c
-}
+ if installName == "" {
+ installName = defaultInstallName
+ }
+ if uninstallName == "" {
+ uninstallName = defaultUninstallName
+ }
-// validate the config
-func (c config) validate() error {
- if c.install && c.uninstall {
- return errors.New("Install and uninstall are exclusive")
+ 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 !c.install && !c.uninstall {
- return errors.New("Must specify -install or -uninstall")
+ 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"
}
diff --git a/run.go b/complete.go
index 5d9706f..925c9a2 100644
--- a/run.go
+++ b/complete.go
@@ -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
diff --git a/run_test.go b/complete_test.go
index 147a361..147a361 100644
--- a/run_test.go
+++ b/complete_test.go
diff --git a/example/self/main.go b/example/self/main.go
new file mode 100644
index 0000000..068a0ac
--- /dev/null
+++ b/example/self/main.go
@@ -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)
+}
diff --git a/gocomplete/complete.go b/gocomplete/complete.go
index 1f94553..ac5f5ed 100644
--- a/gocomplete/complete.go
+++ b/gocomplete/complete.go
@@ -185,5 +185,5 @@ func main() {
},
}
- complete.Run("go", gogo)
+ complete.New("go", gogo).Run()
}
diff --git a/match/match_test.go b/match/match_test.go
index f9afd46..ae1ffea 100644
--- a/match/match_test.go
+++ b/match/match_test.go
@@ -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"),
- prefix: "ab",
- want: true,
- },
- {
- m: Prefix("abcd"),
- prefix: "ac",
- want: false,
- },
- {
- m: Prefix(""),
- prefix: "ac",
- 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: Prefix("abcd"),
+ tests: []matcherTest{
+ {prefix: "", want: true},
+ {prefix: "ab", want: true},
+ {prefix: "ac", want: false},
+ },
},
{
- m: File("/file.txt"),
- prefix: "file.txt",
- want: false,
+ m: Prefix(""),
+ tests: []matcherTest{
+ {prefix: "ac", want: false},
+ {prefix: "", want: true},
+ },
},
{
- m: File("/file.txt"),
- prefix: "./file.txt",
- 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: File("/file.txt"),
- prefix: "/file.txt",
- want: true,
+ 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: 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)
+ }
+ })
+ }
}
}
diff --git a/readme.md b/readme.md
index 81e0864..0010098 100644
--- a/readme.md
+++ b/readme.md
@@ -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)