diff options
| -rw-r--r-- | cmd.go | 87 | ||||
| -rw-r--r-- | command.go | 1 | ||||
| -rw-r--r-- | gocomplete/complete.go | 1 | ||||
| -rw-r--r-- | install/home.go | 153 | ||||
| -rw-r--r-- | install/install.go | 43 | ||||
| -rw-r--r-- | install/root.go | 30 | ||||
| -rw-r--r-- | readme.md | 21 | ||||
| -rw-r--r-- | run.go | 12 | ||||
| -rw-r--r-- | run_test.go | 2 |
9 files changed, 337 insertions, 13 deletions
@@ -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" +} @@ -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 +} + @@ -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 ``` - @@ -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) |
