summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEyal Posener <[email protected]>2019-11-19 05:28:26 +0200
committerEyal Posener <[email protected]>2019-11-19 05:28:26 +0200
commit3794f4987cf961143340f197318d540c44ed459a (patch)
treeb718badcea7aca8b1f09f6edfb67aacec4cefe9f
parentc3bfbddfe6b4d133259ee84a9f0f93a7d1b0971e (diff)
Actually adding the install package
-rw-r--r--install/bash.go37
-rw-r--r--install/fish.go69
-rw-r--r--install/install.go177
-rw-r--r--install/utils.go140
-rw-r--r--install/zsh.go44
5 files changed, 467 insertions, 0 deletions
diff --git a/install/bash.go b/install/bash.go
new file mode 100644
index 0000000..17c64de
--- /dev/null
+++ b/install/bash.go
@@ -0,0 +1,37 @@
+package install
+
+import "fmt"
+
+// (un)install in bash
+// basically adds/remove from .bashrc:
+//
+// complete -C </path/to/completion/command> <command>
+type bash struct {
+ rc string
+}
+
+func (b bash) IsInstalled(cmd, bin string) bool {
+ completeCmd := b.cmd(cmd, bin)
+ return lineInFile(b.rc, completeCmd)
+}
+
+func (b bash) Install(cmd, bin string) error {
+ if b.IsInstalled(cmd, bin) {
+ return fmt.Errorf("already installed in %s", b.rc)
+ }
+ completeCmd := b.cmd(cmd, bin)
+ return appendToFile(b.rc, completeCmd)
+}
+
+func (b bash) Uninstall(cmd, bin string) error {
+ if !b.IsInstalled(cmd, bin) {
+ return fmt.Errorf("does not installed in %s", b.rc)
+ }
+
+ completeCmd := b.cmd(cmd, bin)
+ return removeFromFile(b.rc, completeCmd)
+}
+
+func (bash) cmd(cmd, bin string) string {
+ return fmt.Sprintf("complete -C %s %s", bin, cmd)
+}
diff --git a/install/fish.go b/install/fish.go
new file mode 100644
index 0000000..2b64bfc
--- /dev/null
+++ b/install/fish.go
@@ -0,0 +1,69 @@
+package install
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+// (un)install in fish
+
+type fish struct {
+ configDir string
+}
+
+func (f fish) IsInstalled(cmd, bin string) bool {
+ completionFile := f.getCompletionFilePath(cmd)
+ if _, err := os.Stat(completionFile); err == nil {
+ return true
+ }
+ return false
+}
+
+func (f fish) Install(cmd, bin string) error {
+ if f.IsInstalled(cmd, bin) {
+ return fmt.Errorf("already installed at %s", f.getCompletionFilePath(cmd))
+ }
+
+ completionFile := f.getCompletionFilePath(cmd)
+ completeCmd, err := f.cmd(cmd, bin)
+ if err != nil {
+ return err
+ }
+
+ return createFile(completionFile, completeCmd)
+}
+
+func (f fish) Uninstall(cmd, bin string) error {
+ if !f.IsInstalled(cmd, bin) {
+ return fmt.Errorf("does not installed in %s", f.configDir)
+ }
+
+ completionFile := f.getCompletionFilePath(cmd)
+ return os.Remove(completionFile)
+}
+
+func (f fish) getCompletionFilePath(cmd string) string {
+ return filepath.Join(f.configDir, "completions", fmt.Sprintf("%s.fish", cmd))
+}
+
+func (f fish) cmd(cmd, bin string) (string, error) {
+ var buf bytes.Buffer
+ params := struct{ Cmd, Bin string }{cmd, bin}
+ tmpl := template.Must(template.New("cmd").Parse(`
+function __complete_{{.Cmd}}
+ set -lx COMP_LINE (commandline -cp)
+ test -z (commandline -ct)
+ and set COMP_LINE "$COMP_LINE "
+ {{.Bin}}
+end
+complete -f -c {{.Cmd}} -a "(__complete_{{.Cmd}})"
+`))
+ err := tmpl.Execute(&buf, params)
+ if err != nil {
+ return "", err
+ }
+ return buf.String(), nil
+}
diff --git a/install/install.go b/install/install.go
new file mode 100644
index 0000000..1da52c3
--- /dev/null
+++ b/install/install.go
@@ -0,0 +1,177 @@
+// Package install provide installation functions of command completion.
+package install
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/hashicorp/go-multierror"
+)
+
+func Run(name string, uninstall, yes bool, out io.Writer, in io.Reader) {
+ action := "install"
+ if uninstall {
+ action = "uninstall"
+ }
+ if !yes {
+ fmt.Fprintf(out, "%s completion for %s? ", action, name)
+ var answer string
+ fmt.Fscanln(in, &answer)
+ switch strings.ToLower(answer) {
+ case "y", "yes":
+ default:
+ fmt.Fprintf(out, "Cancelling...")
+ return
+ }
+ }
+ fmt.Fprintf(out, action+"ing...")
+
+ if uninstall {
+ Uninstall(name)
+ } else {
+ Install(name)
+ }
+}
+
+type installer interface {
+ IsInstalled(cmd, bin string) bool
+ Install(cmd, bin string) error
+ Uninstall(cmd, bin string) error
+}
+
+// Install complete command given:
+// cmd: is the command name
+func Install(cmd string) error {
+ is := installers()
+ if len(is) == 0 {
+ return errors.New("Did not find any shells to install")
+ }
+ bin, err := getBinaryPath()
+ if err != nil {
+ return err
+ }
+
+ for _, i := range is {
+ errI := i.Install(cmd, bin)
+ if errI != nil {
+ err = multierror.Append(err, errI)
+ }
+ }
+
+ return err
+}
+
+// IsInstalled returns true if the completion
+// for the given cmd is installed.
+func IsInstalled(cmd string) bool {
+ bin, err := getBinaryPath()
+ if err != nil {
+ return false
+ }
+
+ for _, i := range installers() {
+ installed := i.IsInstalled(cmd, bin)
+ if installed {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Uninstall complete command given:
+// cmd: is the command name
+func Uninstall(cmd string) error {
+ is := installers()
+ if len(is) == 0 {
+ return errors.New("Did not find any shells to uninstall")
+ }
+ bin, err := getBinaryPath()
+ if err != nil {
+ return err
+ }
+
+ for _, i := range is {
+ errI := i.Uninstall(cmd, bin)
+ if errI != nil {
+ err = multierror.Append(err, errI)
+ }
+ }
+
+ return err
+}
+
+func installers() (i []installer) {
+ // The list of bash config files candidates where it is
+ // possible to install the completion command.
+ var bashConfFiles []string
+ switch runtime.GOOS {
+ case "darwin":
+ bashConfFiles = []string{".bash_profile"}
+ default:
+ bashConfFiles = []string{".bashrc", ".bash_profile", ".bash_login", ".profile"}
+ }
+ for _, rc := range bashConfFiles {
+ if f := rcFile(rc); f != "" {
+ i = append(i, bash{f})
+ break
+ }
+ }
+ if f := rcFile(".zshrc"); f != "" {
+ i = append(i, zsh{f})
+ }
+ if d := fishConfigDir(); d != "" {
+ i = append(i, fish{d})
+ }
+ return
+}
+
+func fishConfigDir() string {
+ configDir := filepath.Join(getConfigHomePath(), "fish")
+ if configDir == "" {
+ return ""
+ }
+ if info, err := os.Stat(configDir); err != nil || !info.IsDir() {
+ return ""
+ }
+ return configDir
+}
+
+func getConfigHomePath() string {
+ u, err := user.Current()
+ if err != nil {
+ return ""
+ }
+
+ configHome := os.Getenv("XDG_CONFIG_HOME")
+ if configHome == "" {
+ return filepath.Join(u.HomeDir, ".config")
+ }
+ return configHome
+}
+
+func getBinaryPath() (string, error) {
+ bin, err := os.Executable()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Abs(bin)
+}
+
+func rcFile(name string) string {
+ u, err := user.Current()
+ if err != nil {
+ return ""
+ }
+ path := filepath.Join(u.HomeDir, name)
+ if _, err := os.Stat(path); err != nil {
+ return ""
+ }
+ return path
+}
diff --git a/install/utils.go b/install/utils.go
new file mode 100644
index 0000000..d34ac8c
--- /dev/null
+++ b/install/utils.go
@@ -0,0 +1,140 @@
+package install
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+)
+
+func lineInFile(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]
+ }
+}
+
+func createFile(name string, content string) error {
+ // make sure file directory exists
+ if err := os.MkdirAll(filepath.Dir(name), 0775); err != nil {
+ return err
+ }
+
+ // create the file
+ f, err := os.Create(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ // write file content
+ _, err = f.WriteString(fmt.Sprintf("%s\n", content))
+ return err
+}
+
+func appendToFile(name string, content string) error {
+ f, err := os.OpenFile(name, os.O_RDWR|os.O_APPEND, 0)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = f.WriteString(fmt.Sprintf("\n%s\n", content))
+ return err
+}
+
+func removeFromFile(name string, content string) error {
+ backup := name + ".bck"
+ err := copyFile(name, backup)
+ if err != nil {
+ return err
+ }
+ temp, err := removeContentToTempFile(name, content)
+ if err != nil {
+ return err
+ }
+
+ err = copyFile(temp, name)
+ if err != nil {
+ return err
+ }
+
+ return os.Remove(backup)
+}
+
+func removeContentToTempFile(name, content string) (string, error) {
+ rf, err := os.Open(name)
+ if err != nil {
+ return "", err
+ }
+ defer rf.Close()
+ wf, err := ioutil.TempFile("/tmp", "complete-")
+ 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 == content {
+ continue
+ }
+ _, err = wf.WriteString(str + "\n")
+ if err != nil {
+ return "", err
+ }
+ 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
+}
diff --git a/install/zsh.go b/install/zsh.go
new file mode 100644
index 0000000..29950ab
--- /dev/null
+++ b/install/zsh.go
@@ -0,0 +1,44 @@
+package install
+
+import "fmt"
+
+// (un)install in zsh
+// basically adds/remove from .zshrc:
+//
+// autoload -U +X bashcompinit && bashcompinit"
+// complete -C </path/to/completion/command> <command>
+type zsh struct {
+ rc string
+}
+
+func (z zsh) IsInstalled(cmd, bin string) bool {
+ completeCmd := z.cmd(cmd, bin)
+ return lineInFile(z.rc, completeCmd)
+}
+
+func (z zsh) Install(cmd, bin string) error {
+ if z.IsInstalled(cmd, bin) {
+ return fmt.Errorf("already installed in %s", z.rc)
+ }
+
+ completeCmd := z.cmd(cmd, bin)
+ bashCompInit := "autoload -U +X bashcompinit && bashcompinit"
+ if !lineInFile(z.rc, bashCompInit) {
+ completeCmd = bashCompInit + "\n" + completeCmd
+ }
+
+ return appendToFile(z.rc, completeCmd)
+}
+
+func (z zsh) Uninstall(cmd, bin string) error {
+ if !z.IsInstalled(cmd, bin) {
+ return fmt.Errorf("does not installed in %s", z.rc)
+ }
+
+ completeCmd := z.cmd(cmd, bin)
+ return removeFromFile(z.rc, completeCmd)
+}
+
+func (zsh) cmd(cmd, bin string) string {
+ return fmt.Sprintf("complete -o nospace -C %s %s", bin, cmd)
+}