237 lines
4.7 KiB
Go
237 lines
4.7 KiB
Go
package envpath
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Paths parses the PATH.env file and returns a slice of valid paths
|
|
func Paths() ([]string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if nil != err {
|
|
return nil, err
|
|
}
|
|
home = filepath.ToSlash(home)
|
|
|
|
_, paths, err := getEnv(home, "PATH")
|
|
if nil != err {
|
|
return nil, err
|
|
}
|
|
|
|
// ":" on *nix
|
|
return paths, nil
|
|
}
|
|
|
|
// Add adds a path entry to the PATH env file
|
|
func Add(entry string) (bool, error) {
|
|
home, err := os.UserHomeDir()
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
home = filepath.ToSlash(home)
|
|
|
|
pathentry, err := normalizePathEntry(home, entry)
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
err = initializeShells(home)
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
fullpath, paths, err := getEnv(home, "PATH")
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
index := IndexOf(paths, pathentry)
|
|
if index >= 0 {
|
|
return false, nil
|
|
}
|
|
|
|
paths = append(paths, pathentry)
|
|
err = writeEnv(fullpath, paths)
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
fmt.Println("Wrote " + fullpath)
|
|
return true, nil
|
|
}
|
|
|
|
// Remove adds a path entry to the PATH env file
|
|
func Remove(entry string) (bool, error) {
|
|
home, err := os.UserHomeDir()
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
home = filepath.ToSlash(home)
|
|
|
|
pathentry, err := normalizePathEntry(home, entry)
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
err = initializeShells(home)
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
fullpath, oldpaths, err := getEnv(home, "PATH")
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
index := IndexOf(oldpaths, pathentry)
|
|
if index < 0 {
|
|
return false, nil
|
|
}
|
|
|
|
paths := []string{}
|
|
for i := range oldpaths {
|
|
if index != i {
|
|
paths = append(paths, oldpaths[i])
|
|
}
|
|
}
|
|
|
|
err = writeEnv(fullpath, paths)
|
|
if nil != err {
|
|
return false, err
|
|
}
|
|
|
|
fmt.Println("Wrote " + fullpath)
|
|
return true, nil
|
|
}
|
|
|
|
func getEnv(home string, env string) (string, []string, error) {
|
|
envmand := filepath.Join(home, ".config/envman")
|
|
err := os.MkdirAll(envmand, 0755)
|
|
if nil != err {
|
|
return "", nil, err
|
|
}
|
|
|
|
nodes, err := ioutil.ReadDir(envmand)
|
|
if nil != err {
|
|
return "", nil, err
|
|
}
|
|
|
|
//filename := fmt.Sprintf("00-%s.env", env)
|
|
filename := fmt.Sprintf("%s.env", env)
|
|
for i := range nodes {
|
|
name := nodes[i].Name()
|
|
if fmt.Sprintf("%s.env", env) == name || strings.HasSuffix(name, fmt.Sprintf("-%s.env", env)) {
|
|
filename = name
|
|
break
|
|
}
|
|
}
|
|
|
|
fullpath := filepath.Join(envmand, filename)
|
|
f, err := os.OpenFile(fullpath, os.O_CREATE|os.O_RDONLY, 0644)
|
|
if nil != err {
|
|
return "", nil, err
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(f)
|
|
f.Close()
|
|
if nil != err {
|
|
return "", nil, err
|
|
}
|
|
|
|
paths, warnings := Parse(b, env)
|
|
for i := range warnings {
|
|
w := warnings[i]
|
|
fmt.Printf("warning: dropped %q from %s:%d: %s\n", w.Line, filename, w.LineNumber, w.Message)
|
|
}
|
|
|
|
pathlines := []string{}
|
|
for i := range paths {
|
|
pathname := strings.TrimSuffix(paths[i], ":$PATH")
|
|
if strings.HasPrefix(pathname, "$PATH:") {
|
|
fixed := strings.TrimPrefix(pathname, "$PATH:")
|
|
fmt.Fprintf(os.Stderr, "warning: re-arranging $PATH:%s to %s:$PATH\n", fixed, fixed)
|
|
pathname = fixed
|
|
}
|
|
pathlines = append(pathlines, pathname)
|
|
}
|
|
|
|
if len(warnings) > 0 {
|
|
err := writeEnv(fullpath, pathlines)
|
|
if nil != err {
|
|
return "", nil, err
|
|
}
|
|
}
|
|
|
|
return fullpath, pathlines, nil
|
|
}
|
|
|
|
func writeEnv(fullpath string, paths []string) error {
|
|
f, err := os.OpenFile(fullpath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
if nil != err {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Write([]byte("# Generated for envman. Do not edit.\n"))
|
|
if nil != err {
|
|
return err
|
|
}
|
|
|
|
for i := range paths {
|
|
_, err := f.Write([]byte(fmt.Sprintf("export PATH=\"%s:$PATH\"\n", paths[i])))
|
|
if nil != err {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return f.Close()
|
|
}
|
|
|
|
// IndexOf searches the given path list for first occurence
|
|
// of the given path entry and returns the index, or -1
|
|
func IndexOf(paths []string, p string) int {
|
|
home, err := os.UserHomeDir()
|
|
if nil != err {
|
|
panic(err)
|
|
}
|
|
|
|
p, _ = normalizePathEntry(home, p)
|
|
index := -1
|
|
for i := range paths {
|
|
entry, _ := normalizePathEntry(home, paths[i])
|
|
if p == entry {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func normalizePathEntry(home, pathentry string) (string, error) {
|
|
var err error
|
|
|
|
// We add the slashes so that we don't get false matches
|
|
// ex: foo should match foo/bar, but should NOT match foobar
|
|
home, err = filepath.Abs(home)
|
|
if nil != err {
|
|
// I'm not sure how it's possible to get an error with Abs...
|
|
return "", err
|
|
}
|
|
home += "/"
|
|
pathentry, err = filepath.Abs(pathentry)
|
|
if nil != err {
|
|
return "", err
|
|
}
|
|
pathentry += "/"
|
|
|
|
// Next we make the path relative to / or ~/
|
|
// ex: /Users/me/.local/bin/ => .local/bin/
|
|
if strings.HasPrefix(pathentry, home) {
|
|
pathentry = "$HOME/" + strings.TrimPrefix(pathentry, home)
|
|
}
|
|
|
|
return strings.TrimSuffix(pathentry, "/"), nil
|
|
}
|