1// Package install provide installation functions of command completion.
2package install
3
4import (
5	"errors"
6	"fmt"
7	"io"
8	"os"
9	"os/user"
10	"path/filepath"
11	"runtime"
12	"strings"
13
14	"github.com/hashicorp/go-multierror"
15)
16
17var binPath string
18
19func init() {
20	binPath, _ = getBinaryPath()
21}
22
23func Run(name string, uninstall, yes bool, out io.Writer, in io.Reader) {
24	action := "install"
25	if uninstall {
26		action = "uninstall"
27	}
28	if !yes {
29		fmt.Fprintf(out, "%s completion for %s? ", action, name)
30		var answer string
31		fmt.Fscanln(in, &answer)
32		switch strings.ToLower(answer) {
33		case "y", "yes":
34		default:
35			fmt.Fprintf(out, "Cancelling...\n")
36			return
37		}
38	}
39	fmt.Fprintf(out, action+"ing...\n")
40
41	var err error
42	if uninstall {
43		err = Uninstall(name)
44	} else {
45		err = Install(name)
46	}
47	if err != nil {
48		fmt.Fprintf(out, "%s failed: %s\n", action, err)
49		os.Exit(1)
50	}
51}
52
53type installer interface {
54	IsInstalled(cmd string) bool
55	Install(cmd string) error
56	Uninstall(cmd string) error
57}
58
59// Install complete command given:
60// cmd: is the command name
61func Install(cmd string) (err error) {
62	is := installers()
63	if len(is) == 0 {
64		return errors.New("Did not find any shells to install")
65	}
66
67	for _, i := range is {
68		errI := i.Install(cmd)
69		if errI != nil {
70			err = multierror.Append(err, errI)
71		}
72	}
73
74	return err
75}
76
77// IsInstalled returns true if the completion
78// for the given cmd is installed.
79func IsInstalled(cmd string) bool {
80	for _, i := range installers() {
81		installed := i.IsInstalled(cmd)
82		if installed {
83			return true
84		}
85	}
86
87	return false
88}
89
90// Uninstall complete command given:
91// cmd: is the command name
92func Uninstall(cmd string) (err error) {
93	is := installers()
94	if len(is) == 0 {
95		return errors.New("Did not find any shells to uninstall")
96	}
97
98	for _, i := range is {
99		errI := i.Uninstall(cmd)
100		if errI != nil {
101			err = multierror.Append(err, errI)
102		}
103	}
104
105	return err
106}
107
108func installers() (i []installer) {
109	// The list of bash config files candidates where it is
110	// possible to install the completion command.
111	var bashConfFiles []string
112	switch runtime.GOOS {
113	case "darwin":
114		bashConfFiles = []string{".bash_profile"}
115	default:
116		bashConfFiles = []string{".bashrc", ".bash_profile", ".bash_login", ".profile"}
117	}
118	for _, rc := range bashConfFiles {
119		if f := rcFile(rc); f != "" {
120			i = append(i, bash{f})
121			break
122		}
123	}
124	if f := rcFile(".zshrc"); f != "" {
125		i = append(i, zsh{f})
126	}
127	if d := fishConfigDir(); d != "" {
128		i = append(i, fish{d})
129	}
130	return
131}
132
133func fishConfigDir() string {
134	configDir := filepath.Join(getConfigHomePath(), "fish")
135	if configDir == "" {
136		return ""
137	}
138	if info, err := os.Stat(configDir); err != nil || !info.IsDir() {
139		return ""
140	}
141	return configDir
142}
143
144func getConfigHomePath() string {
145	u, err := user.Current()
146	if err != nil {
147		return ""
148	}
149
150	configHome := os.Getenv("XDG_CONFIG_HOME")
151	if configHome == "" {
152		return filepath.Join(u.HomeDir, ".config")
153	}
154	return configHome
155}
156
157func getBinaryPath() (string, error) {
158	bin, err := os.Executable()
159	if err != nil {
160		return "", err
161	}
162	return filepath.Abs(bin)
163}
164
165func rcFile(name string) string {
166	u, err := user.Current()
167	if err != nil {
168		return ""
169	}
170	path := filepath.Join(u.HomeDir, name)
171	if _, err := os.Stat(path); err != nil {
172		return ""
173	}
174	return path
175}
176