Manage PATH on Windows, Mac, and Linux with various Shells
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

236 lines
4.7 KiB

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
}