summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd.go87
-rw-r--r--command.go1
-rw-r--r--gocomplete/complete.go1
-rw-r--r--install/home.go153
-rw-r--r--install/install.go43
-rw-r--r--install/root.go30
-rw-r--r--readme.md21
-rw-r--r--run.go12
-rw-r--r--run_test.go2
9 files changed, 337 insertions, 13 deletions
diff --git a/cmd.go b/cmd.go
new file mode 100644
index 0000000..a9024b2
--- /dev/null
+++ b/cmd.go
@@ -0,0 +1,87 @@
+package complete
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/posener/complete/install"
+)
+
+func runCommandLine(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, c.root)
+ } else {
+ err = install.Uninstall(cmd, c.root)
+ }
+ if err != nil {
+ fmt.Printf("%s failed! %s\n", c.action(), err)
+ os.Exit(3)
+ }
+ fmt.Println("Done!")
+}
+
+func prompt(action, cmd string) bool {
+ fmt.Printf("%s bash completion for %s? ", action, cmd)
+ var answer string
+ fmt.Scanln(&answer)
+
+ switch strings.ToLower(answer) {
+ case "y", "yes":
+ return true
+ default:
+ return false
+ }
+}
+
+type config struct {
+ install bool
+ uninstall bool
+ root bool
+ yes bool
+}
+
+func parseFlags(cmd string) config {
+ var c config
+ flag.BoolVar(&c.install, "install", false,
+ fmt.Sprintf("Install bash completion for %s command", cmd))
+ flag.BoolVar(&c.uninstall, "uninstall", false,
+ fmt.Sprintf("Uninstall bash completion for %s command", cmd))
+ flag.BoolVar(&c.root, "root", false,
+ "(Un)Install as root:\n"+
+ " (Un)Install at /etc/bash_completion.d/ (user should have write permissions to that directory).\n"+
+ " If not set, a complete command will be added(removed) to ~/.bashrc")
+ flag.BoolVar(&c.yes, "y", false, "Don't prompt user for typing 'yes'")
+ flag.Parse()
+ return c
+}
+
+func (c config) validate() error {
+ if c.install && c.uninstall {
+ return errors.New("Install and uninstall are exclusive")
+ }
+ if !c.install && !c.uninstall {
+ return errors.New("Must specify -install or -uninstall")
+ }
+ return nil
+}
+
+func (c config) action() string {
+ if c.install {
+ return "Install"
+ }
+ return "Uninstall"
+}
diff --git a/command.go b/command.go
index 32c5456..253f8e7 100644
--- a/command.go
+++ b/command.go
@@ -5,6 +5,7 @@ type Commands map[string]Command
type Flags map[string]Predicate
type Command struct {
+ Name string
Sub Commands
Flags Flags
Args Predicate
diff --git a/gocomplete/complete.go b/gocomplete/complete.go
index ca87f14..7dc1694 100644
--- a/gocomplete/complete.go
+++ b/gocomplete/complete.go
@@ -164,6 +164,7 @@ func main() {
}
gogo := complete.Command{
+ Name: "go",
Sub: complete.Commands{
"build": build,
"install": build, // install and build have the same flags
diff --git a/install/home.go b/install/home.go
new file mode 100644
index 0000000..1850a05
--- /dev/null
+++ b/install/home.go
@@ -0,0 +1,153 @@
+package install
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/user"
+ "path/filepath"
+ "io/ioutil"
+)
+
+type home struct{}
+
+func (home) Install(cmd, bin string) error {
+ bashRCFileName, err := bashRCFileName()
+ if err != nil {
+ return err
+ }
+ completeCmd := completeCmd(cmd, bin)
+ if isInFile(bashRCFileName, completeCmd) {
+ return errors.New("Already installed in ~/.bashrc")
+ }
+
+ bashRC, err := os.OpenFile(bashRCFileName, os.O_RDWR|os.O_APPEND, 0)
+ if err != nil {
+ return err
+ }
+ defer bashRC.Close()
+ _, err = bashRC.WriteString(fmt.Sprintf("\n%s\n", completeCmd))
+ return err
+}
+
+func (home) Uninstall(cmd, bin string) error {
+ bashRC, err := bashRCFileName()
+ if err != nil {
+ return err
+ }
+ backup := bashRC + ".bck"
+ err = copyFile(bashRC, backup)
+ if err != nil {
+ return err
+ }
+ completeCmd := completeCmd(cmd, bin)
+ if !isInFile(bashRC, completeCmd) {
+ return errors.New("Does not installed in ~/.bashrc")
+ }
+ temp, err := uninstallToTemp(bashRC, completeCmd)
+ if err != nil {
+ return err
+ }
+
+ err = copyFile(temp, bashRC)
+ if err != nil {
+ return err
+ }
+
+ return os.Remove(backup)
+}
+
+func completeCmd(cmd, bin string) string {
+ return fmt.Sprintf("complete -C %s %s", bin, cmd)
+}
+
+func bashRCFileName() (string, error) {
+ u, err := user.Current()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(u.HomeDir, ".bashrc"), nil
+}
+
+func isInFile(name string, lookFor string) bool {
+ f, err := os.Open(name)
+ if err != nil {
+ return false
+ }
+ defer f.Close()
+ r := bufio.NewReader(f)
+ prefix := []byte{}
+ for {
+ line, isPrefix, err := r.ReadLine()
+ if err == io.EOF {
+ return false
+ }
+ if err != nil {
+ return false
+ }
+ if isPrefix {
+ prefix = append(prefix, line...)
+ continue
+ }
+ line = append(prefix, line...)
+ if string(line) == lookFor {
+ return true
+ }
+ prefix = prefix[:0]
+ }
+ return false
+}
+
+func uninstallToTemp(bashRCFileName, completeCmd string) (string, error) {
+ rf, err := os.Open(bashRCFileName)
+ if err != nil {
+ return "", err
+ }
+ defer rf.Close()
+ wf, err := ioutil.TempFile("/tmp", "bashrc-")
+ if err != nil {
+ return "", err
+ }
+ defer wf.Close()
+
+ r := bufio.NewReader(rf)
+ prefix := []byte{}
+ for {
+ line, isPrefix, err := r.ReadLine()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return "", err
+ }
+ if isPrefix {
+ prefix = append(prefix, line...)
+ continue
+ }
+ line = append(prefix, line...)
+ str := string(line)
+ if str == completeCmd {
+ continue
+ }
+ wf.WriteString(str + "\n")
+ prefix = prefix[:0]
+ }
+ return wf.Name(), nil
+}
+
+func copyFile(src string, dst string) error {
+ in, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+ out, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+ _, err = io.Copy(out, in)
+ return err
+} \ No newline at end of file
diff --git a/install/install.go b/install/install.go
new file mode 100644
index 0000000..cef11f0
--- /dev/null
+++ b/install/install.go
@@ -0,0 +1,43 @@
+package install
+
+import (
+ "os"
+ "path/filepath"
+)
+
+type installer interface {
+ Install(cmd, bin string) error
+ Uninstall(cmd, bin string) error
+}
+
+func Install(cmd string, asRoot bool) error {
+ bin, err := getBinaryPath()
+ if err != nil {
+ return err
+ }
+ return getInstaller(asRoot).Install(cmd, bin)
+}
+
+func Uninstall(cmd string, asRoot bool) error {
+ bin, err := getBinaryPath()
+ if err != nil {
+ return err
+ }
+ return getInstaller(asRoot).Uninstall(cmd, bin)
+}
+
+func getInstaller(asRoot bool) installer {
+ if asRoot {
+ return root{}
+ } else {
+ return home{}
+ }
+}
+
+func getBinaryPath() (string, error) {
+ bin, err := os.Executable()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Abs(bin)
+}
diff --git a/install/root.go b/install/root.go
new file mode 100644
index 0000000..b128a0d
--- /dev/null
+++ b/install/root.go
@@ -0,0 +1,30 @@
+package install
+
+import "os"
+
+type root struct{}
+
+func (r root) Install(cmd string, bin string) error {
+ completeLink := getBashCompletionDLink(cmd)
+ err := r.Uninstall(cmd, bin)
+ if err != nil {
+ return err
+ }
+ return os.Symlink(bin, completeLink)
+}
+
+func (root) Uninstall(cmd string, bin string) error {
+ completeLink := getBashCompletionDLink(cmd)
+ if _, err := os.Stat(completeLink); err == nil {
+ err := os.Remove(completeLink)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getBashCompletionDLink(cmd string) string {
+ return "/etc/bash_completion.d/"+cmd
+}
+
diff --git a/readme.md b/readme.md
index 4c86bf7..f9902d0 100644
--- a/readme.md
+++ b/readme.md
@@ -5,20 +5,25 @@
WIP
-a tool for bash writing bash completion in go.
+A tool for bash writing bash completion in go.
-## example: `go` command bash completion
+Writing bash completion scripts is a hard work. This package provides an easy way
+to create bash completion scripts for any command, and also an easy way to install/uninstall
+the completion of the command.
-Install in you home directory:
+## go command bash completion
+
+In [gocomplete](./gocomplete) there is an example for bash completion for the `go` command line.
+
+### Install
```
-go build -o ~/.bash_completion/go ./gocomplete
-echo "complete -C ~/.bash_completion/go go" >> ~/.bashrc
+go get github.com/posener/complete/gocomplete
+gocomplete -install
```
-Or, install in the root directory:
+### Uninstall
```
-sudo go build -o /etc/bash_completion.d/go ./gocomplete
+gocomplete -uninstall
```
-
diff --git a/run.go b/run.go
index d0c9a57..bd9f662 100644
--- a/run.go
+++ b/run.go
@@ -14,7 +14,11 @@ const (
// Run get a command, get the typed arguments from environment
// variable, and print out the complete options
func Run(c Command) {
- args := getLine()
+ args, ok := getLine()
+ if !ok {
+ runCommandLine(c.Name)
+ return
+ }
Log("Completing args: %s", args)
options := complete(c, args)
@@ -38,12 +42,12 @@ func complete(c Command, args []string) (matching []string) {
return
}
-func getLine() []string {
+func getLine() ([]string, bool) {
line := os.Getenv(envComplete)
if line == "" {
- panic("should be run as a complete script")
+ return nil, false
}
- return strings.Split(line, " ")
+ return strings.Split(line, " "), true
}
func last(args []string) (last string) {
diff --git a/run_test.go b/run_test.go
index 8f47431..0e18850 100644
--- a/run_test.go
+++ b/run_test.go
@@ -177,7 +177,7 @@ func TestCompleter_Complete(t *testing.T) {
tt.args = "cmd " + tt.args
os.Setenv(envComplete, tt.args)
- args := getLine()
+ args, _ := getLine()
got := complete(c, args)