Compare commits
	
		
			No commits in common. "0a5f44eca765b8cc4c626a6b00ca0c62d2c37f7d" and "7ca8158a1c55bc19ffa61a760bbb7719c16c607f" have entirely different histories.
		
	
	
		
			0a5f44eca7
			...
			7ca8158a1c
		
	
		
| @ -1,374 +0,0 @@ | |||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ppsep is used as the replacement for slashes in path |  | ||||||
| // ex: ~/bin => ~bin |  | ||||||
| // ex: ~/bin => home~bin |  | ||||||
| // ex: ~/.local/opt/foo/bin => ~.local»opt»foo»bin |  | ||||||
| // other patterns considered: |  | ||||||
| //   ~/.local/opt/foo/bin => ~.local·opt·foo·bin |  | ||||||
| //   ~/.local/opt/foo/bin => home¬.local¬opt¬foo¬bin |  | ||||||
| //   ~/.local/opt/foo/bin => home».local»opt»foo»bin |  | ||||||
| //   ~/.local/opt/foo/bin => HOME•.local•opt•foo•bin |  | ||||||
| //   ~/.local/opt/foo/bin => HOME_.local_opt_foo_bin |  | ||||||
| //   ~/.local/opt/foo/bin => HOME·.local·opt·foo·bin |  | ||||||
| const ppsep = "-" |  | ||||||
| 
 |  | ||||||
| func usage() { |  | ||||||
| 	//fmt.Fprintf(os.Stderr, "Usage: envpath show|add|append|remove <path>\n") |  | ||||||
| 	fmt.Fprintf(os.Stderr, "Usage: envpath show|add|remove <path>\n") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	// TODO --system to add to the system PATH rather than the user PATH |  | ||||||
| 
 |  | ||||||
| 	// Usage: |  | ||||||
| 	if len(os.Args) < 2 { |  | ||||||
| 		usage() |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 	action := os.Args[1] |  | ||||||
| 
 |  | ||||||
| 	paths := Paths() |  | ||||||
| 
 |  | ||||||
| 	if "show" == action { |  | ||||||
| 		if len(os.Args) > 2 { |  | ||||||
| 			usage() |  | ||||||
| 		} |  | ||||||
| 		fmt.Println() |  | ||||||
| 		for i := range paths { |  | ||||||
| 			fmt.Println("\t" + paths[i]) |  | ||||||
| 		} |  | ||||||
| 		fmt.Println() |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(os.Args) < 3 { |  | ||||||
| 		usage() |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pathentry := os.Args[2] |  | ||||||
| 	if "remove" != action { |  | ||||||
| 		stat, err := os.Stat(pathentry) |  | ||||||
| 		if nil != err { |  | ||||||
| 			// TODO --force |  | ||||||
| 			fmt.Fprintf(os.Stderr, "%s\n", err) |  | ||||||
| 			os.Exit(2) |  | ||||||
| 		} |  | ||||||
| 		if !stat.IsDir() { |  | ||||||
| 			fmt.Fprintf(os.Stderr, "%q is not a directory (folder)\n", pathentry) |  | ||||||
| 			os.Exit(2) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch action { |  | ||||||
| 	default: |  | ||||||
| 		usage() |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	/* |  | ||||||
| 		case "append": |  | ||||||
| 			newpath, _, err := addPath(pathentry, appendOrder) |  | ||||||
| 			if nil != err { |  | ||||||
| 				fmt.Fprintf(os.Stderr, "%s\n", err) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			fmt.Println("New sessions will have " + pathentry + " in their PATH.") |  | ||||||
| 			fmt.Println("To update this session run\n") |  | ||||||
| 			//fmt.Println("\tsource", pathfile) |  | ||||||
| 			fmt.Printf(`%sexport PATH="$PATH:%s"%s`, "\t", newpath, "\n") |  | ||||||
| 	*/ |  | ||||||
| 	case "add": |  | ||||||
| 		_, pathfile, err := addPath(pathentry, prependOrder) |  | ||||||
| 		if nil != err { |  | ||||||
| 			fmt.Fprintf(os.Stderr, "%s\n", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		fmt.Println("\nRun this command (or open a new shell) to finish:\n") |  | ||||||
| 		fmt.Printf("\tsource %s\n\n", pathfile) |  | ||||||
| 		//fmt.Printf(`%sexport PATH="%s:$PATH"%s`, "\t", newpath, "\n\n") |  | ||||||
| 	case "remove": |  | ||||||
| 		msg, err := removePath(pathentry) |  | ||||||
| 		if nil != err { |  | ||||||
| 			fmt.Fprintf(os.Stderr, "%s\n", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		fmt.Println(msg) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Paths returns the slice of PATHs from the Environment |  | ||||||
| func Paths() []string { |  | ||||||
| 	// ":" on *nix |  | ||||||
| 	return strings.Split(os.Getenv("PATH"), string(os.PathListSeparator)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type setOrder bool |  | ||||||
| 
 |  | ||||||
| const prependOrder setOrder = true |  | ||||||
| const appendOrder setOrder = false |  | ||||||
| 
 |  | ||||||
| // returns newpath, error |  | ||||||
| func addPath(oldpathentry string, order setOrder) (string, string, error) { |  | ||||||
| 	home, err := os.UserHomeDir() |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 	home = filepath.ToSlash(home) |  | ||||||
| 
 |  | ||||||
| 	pathentry, fname, err := normalizeEntryAndFile(home, oldpathentry) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	envpathd := filepath.Join(home, ".config/envpath/path.d") |  | ||||||
| 	err = os.MkdirAll(envpathd, 0755) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = initializeShells(home) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	nodes, err := ioutil.ReadDir(envpathd) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var priority int |  | ||||||
| 	if prependOrder == order { |  | ||||||
| 		// Counter-intuitively later PATHs, prepended are placed earlier |  | ||||||
| 		priority, err = getOrder(nodes, pathentry, fname, envpathd, sortForward) |  | ||||||
| 	} else { |  | ||||||
| 		// Counter-intuitively earlier PATHs are placed later |  | ||||||
| 		priority, err = getOrder(nodes, pathentry, fname, envpathd, sortBackward) |  | ||||||
| 	} |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if index, ok := isInPath(home, pathentry); ok { |  | ||||||
| 		return "", "", fmt.Errorf( |  | ||||||
| 			"%q is in your PATH at position %d and must be removed manually to re-order\n", |  | ||||||
| 			pathentry, |  | ||||||
| 			index, |  | ||||||
| 		) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// ex: 100-opt»foo»bin.sh |  | ||||||
| 	// ex: 105-home»bar»bin.sh |  | ||||||
| 	pathfile := fmt.Sprintf("%03d-%s", priority, fname) |  | ||||||
| 
 |  | ||||||
| 	fullname := filepath.Join(envpathd, pathfile) |  | ||||||
| 	export := []byte(fmt.Sprintf("# Generated for envpath. Do not edit.\nexport PATH=\"%s:$PATH\"\n", pathentry)) |  | ||||||
| 	err = ioutil.WriteFile(fullname, export, 0755) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 	// If we change from having the user source the path directory |  | ||||||
| 	// then we should uncomment this so the user knows where the path files are |  | ||||||
| 	//fmt.Printf("Wrote %s\n", fullname) |  | ||||||
| 
 |  | ||||||
| 	return pathentry, fullname, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func removePath(oldpathentry string) (string, error) { |  | ||||||
| 	home, err := os.UserHomeDir() |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	home = filepath.ToSlash(home) |  | ||||||
| 
 |  | ||||||
| 	pathentry, fname, err := normalizeEntryAndFile(home, oldpathentry) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	envpathd := filepath.Join(home, ".config/envpath/path.d") |  | ||||||
| 	err = os.MkdirAll(envpathd, 0755) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	nodes, err := ioutil.ReadDir(envpathd) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var fullname string |  | ||||||
| 	for i := range nodes { |  | ||||||
| 		node := nodes[i] |  | ||||||
| 		// 000-foo-bin.sh vs foo-bin.sh |  | ||||||
| 		if strings.HasSuffix(node.Name(), "-"+fname) { |  | ||||||
| 			if len(strings.Split(node.Name(), "-"))-1 == len(strings.Split(fname, "-")) { |  | ||||||
| 				fullname = node.Name() |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	paths := Paths() |  | ||||||
| 	index, exists := isInPath(home, pathentry) |  | ||||||
| 
 |  | ||||||
| 	if "" == fullname { |  | ||||||
| 		if exists { |  | ||||||
| 			return "", fmt.Errorf("%q is in your PATH, but is NOT managed by envpath", pathentry) |  | ||||||
| 		} |  | ||||||
| 		return "", fmt.Errorf("%q is NOT in your PATH, and NOT managed by envpath", pathentry) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = os.Remove(filepath.Join(envpathd, fullname)) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !exists { |  | ||||||
| 		return fmt.Sprintf("Removed %s", filepath.Join(envpathd, fullname)), nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	newpaths := []string{} |  | ||||||
| 	for i := range paths { |  | ||||||
| 		if i == index { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		newpaths = append(newpaths, paths[i]) |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf( |  | ||||||
| 		"Removed %s. To update the current shell re-export the new PATH:\n\n"+ |  | ||||||
| 			"\texport PATH=%q\n", |  | ||||||
| 		fullname, |  | ||||||
| 		strings.Join(newpaths, ":"), |  | ||||||
| 	), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isInPath(home, pathentry string) (int, bool) { |  | ||||||
| 	paths := Paths() |  | ||||||
| 
 |  | ||||||
| 	index := -1 |  | ||||||
| 	for i := range paths { |  | ||||||
| 		entry, _ := normalizePathEntry(home, paths[i]) |  | ||||||
| 		if pathentry == entry { |  | ||||||
| 			index = i |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if index >= 0 { |  | ||||||
| 		return index, true |  | ||||||
| 	} |  | ||||||
| 	return -1, false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func normalizeEntryAndFile(home, pathentry string) (string, string, error) { |  | ||||||
| 	var err error |  | ||||||
| 
 |  | ||||||
| 	pathentry, err = normalizePathEntry(home, pathentry) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return "", "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Now we split and rejoin the paths as a unique name |  | ||||||
| 	// ex: /opt/foo/bin/ => opt/foo/bin => [opt foo bin] |  | ||||||
| 	// ex: ~/bar/bin/ => bar/bin => [bar bin] |  | ||||||
| 	names := strings.Split(strings.Trim(filepath.ToSlash(pathentry), "/"), "/") |  | ||||||
| 	if strings.HasPrefix(pathentry, "$HOME/") { |  | ||||||
| 		// ~/bar/bin/ => [home bar bin] |  | ||||||
| 		names[0] = "home" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// ex: /opt/foo/bin/ => opt»foo»bin.sh |  | ||||||
| 	fname := strings.Join(names, ppsep) + ".sh" |  | ||||||
| 
 |  | ||||||
| 	return pathentry, fname, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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 pathentry, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func sortForward(priority, n int) int { |  | ||||||
| 	// Pick a number such that 99 > newpriority > priority |  | ||||||
| 	if n >= priority { |  | ||||||
| 		m := n % 5 |  | ||||||
| 		if 0 == m { |  | ||||||
| 			priority += 5 |  | ||||||
| 		} else { |  | ||||||
| 			priority += (5 - m) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return priority |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func sortBackward(priority, n int) int { |  | ||||||
| 	// Pick a number such that 0 < newpriority < priority |  | ||||||
| 	if n <= priority { |  | ||||||
| 		m := n % 5 |  | ||||||
| 		if 0 == m { |  | ||||||
| 			m = 5 |  | ||||||
| 		} |  | ||||||
| 		priority -= m |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return priority |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type sorter = func(int, int) int |  | ||||||
| 
 |  | ||||||
| func getOrder(nodes []os.FileInfo, pathentry, fname, envpathd string, fn sorter) (int, error) { |  | ||||||
| 	// assuming people will append more often than prepend |  | ||||||
| 	// default the priority to less than halfway |  | ||||||
| 	priority := 100 |  | ||||||
| 	for i := range nodes { |  | ||||||
| 		f := nodes[i] |  | ||||||
| 		name := f.Name() |  | ||||||
| 		if !strings.HasSuffix(name, ".sh") { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if strings.HasSuffix(name, "-"+fname) { |  | ||||||
| 			return 0, fmt.Errorf( |  | ||||||
| 				"Error: %s already exports %s", |  | ||||||
| 				filepath.Join("~/.config/envpath/path.d", f.Name()), |  | ||||||
| 				pathentry, |  | ||||||
| 			) |  | ||||||
| 		} |  | ||||||
| 		n, err := strconv.Atoi(strings.Split(name, "-")[0]) |  | ||||||
| 		if nil != err { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		priority = fn(priority, n) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return priority, nil |  | ||||||
| } |  | ||||||
| @ -1,248 +0,0 @@ | |||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"runtime" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type envConfig struct { |  | ||||||
| 	shell      string |  | ||||||
| 	shellDesc  string |  | ||||||
| 	home       string |  | ||||||
| 	rcFile     string |  | ||||||
| 	rcScript   string |  | ||||||
| 	loadFile   string |  | ||||||
| 	loadScript string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var confs []*envConfig |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	home, err := os.UserHomeDir() |  | ||||||
| 	if nil != err { |  | ||||||
| 		panic(err) // Must get home directory |  | ||||||
| 	} |  | ||||||
| 	home = filepath.ToSlash(home) |  | ||||||
| 
 |  | ||||||
| 	confs = []*envConfig{ |  | ||||||
| 		&envConfig{ |  | ||||||
| 			home:       home, |  | ||||||
| 			shell:      "bash", |  | ||||||
| 			shellDesc:  "bourne-compatible shell (bash)", |  | ||||||
| 			rcFile:     ".bashrc", |  | ||||||
| 			rcScript:   "[ -s \"$HOME/.config/envpath/load.sh\" ] && source \"$HOME/.config/envpath/load.sh\"\n", |  | ||||||
| 			loadFile:   ".config/envpath/load.sh", |  | ||||||
| 			loadScript: "for x in ~/.config/envpath/path.d/*.sh; do\n\tsource \"$x\"\ndone\n", |  | ||||||
| 		}, |  | ||||||
| 		&envConfig{ |  | ||||||
| 			home:       home, |  | ||||||
| 			shell:      "zsh", |  | ||||||
| 			shellDesc:  "bourne-compatible shell (zsh)", |  | ||||||
| 			rcFile:     ".zshrc", |  | ||||||
| 			rcScript:   "[ -s \"$HOME/.config/envpath/load.sh\" ] && source \"$HOME/.config/envpath/load.sh\"\n", |  | ||||||
| 			loadFile:   ".config/envpath/load.sh", |  | ||||||
| 			loadScript: "for x in ~/.config/envpath/path.d/*.sh; do\n\tsource \"$x\"\ndone\n", |  | ||||||
| 		}, |  | ||||||
| 		&envConfig{ |  | ||||||
| 			home:       home, |  | ||||||
| 			shell:      "fish", |  | ||||||
| 			shellDesc:  "fish shell", |  | ||||||
| 			rcFile:     ".config/fish/config.fish", |  | ||||||
| 			rcScript:   "test -s \"$HOME/.config/envpath/load.fish\"; and source \"$HOME/.config/envpath/load.fish\"\n", |  | ||||||
| 			loadFile:   ".config/envpath/load.fish", |  | ||||||
| 			loadScript: "for x in ~/.config/envpath/path.d/*.sh\n\tsource \"$x\"\nend\n", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func initializeShells(home string) error { |  | ||||||
| 	var hasRC bool |  | ||||||
| 	var nativeMatch *envConfig |  | ||||||
| 	for i := range confs { |  | ||||||
| 		c := confs[i] |  | ||||||
| 
 |  | ||||||
| 		if os.Getenv("SHELL") == c.shell { |  | ||||||
| 			nativeMatch = c |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		_, err := os.Stat(filepath.Join(home, c.rcFile)) |  | ||||||
| 		if nil != err { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		hasRC = true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// ensure rc |  | ||||||
| 	if !hasRC { |  | ||||||
| 		if nil == nativeMatch { |  | ||||||
| 			return fmt.Errorf( |  | ||||||
| 				"%q is not a recognized shell and found none of .bashrc, .zshrc, .config/fish/config.fish", |  | ||||||
| 				os.Getenv("SHELL"), |  | ||||||
| 			) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// touch the rc file |  | ||||||
| 		f, err := os.OpenFile(filepath.Join(home, nativeMatch.rcFile), os.O_CREATE|os.O_WRONLY, 0644) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if err := f.Close(); nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// MacOS is special. It *requires* .bash_profile in order to read .bashrc |  | ||||||
| 	if "darwin" == runtime.GOOS && "bash" == os.Getenv("SHELL") { |  | ||||||
| 		if err := ensureBashProfile(home); nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// |  | ||||||
| 	// Bash (sh, dash, zsh, ksh) |  | ||||||
| 	// |  | ||||||
| 	// http://www.joshstaiger.org/archives/2005/07/bash_profile_vs.html |  | ||||||
| 	for i := range confs { |  | ||||||
| 		c := confs[i] |  | ||||||
| 		err := c.maybeInitializeShell() |  | ||||||
| 		if nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *envConfig) maybeInitializeShell() error { |  | ||||||
| 	if _, err := os.Stat(filepath.Join(c.home, c.rcFile)); nil != err { |  | ||||||
| 		if !os.IsNotExist(err) { |  | ||||||
| 			fmt.Fprintf(os.Stderr, "%s\n", err) |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	changed, err := c.initializeShell() |  | ||||||
| 	if nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if changed { |  | ||||||
| 		fmt.Printf( |  | ||||||
| 			"Detected %s shell and updated ~/%s\n", |  | ||||||
| 			c.shellDesc, |  | ||||||
| 			strings.TrimPrefix(c.rcFile, c.home), |  | ||||||
| 		) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *envConfig) initializeShell() (bool, error) { |  | ||||||
| 	if err := c.ensurePathsLoader(); err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Get current config |  | ||||||
| 	// ex: ~/.bashrc |  | ||||||
| 	// ex: ~/.config/fish/config.fish |  | ||||||
| 	b, err := ioutil.ReadFile(filepath.Join(c.home, c.rcFile)) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// For Windows, just in case |  | ||||||
| 	s := strings.Replace(string(b), "\r\n", "\n", -1) |  | ||||||
| 
 |  | ||||||
| 	// Looking to see if loader script has been added to rc file |  | ||||||
| 	lines := strings.Split(strings.TrimSpace(s), "\n") |  | ||||||
| 	for i := range lines { |  | ||||||
| 		line := lines[i] |  | ||||||
| 		if line == strings.TrimSpace(c.rcScript) { |  | ||||||
| 			// indicate that it was not neccesary to change the rc file |  | ||||||
| 			return false, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Open rc file to append and write |  | ||||||
| 	f, err := os.OpenFile(filepath.Join(c.home, c.rcFile), os.O_APPEND|os.O_WRONLY, 0644) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Generate our script |  | ||||||
| 	script := fmt.Sprintf("# Generated for envpath. Do not edit.\n%s\n", c.rcScript) |  | ||||||
| 
 |  | ||||||
| 	// If there's not a newline before our template, |  | ||||||
| 	// include it in the template. We want nice things. |  | ||||||
| 	n := len(lines) |  | ||||||
| 	if "" != strings.TrimSpace(lines[n-1]) { |  | ||||||
| 		script = "\n" + script |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Write and close the rc file |  | ||||||
| 	if _, err := f.Write([]byte(script)); err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 	if err := f.Close(); err != nil { |  | ||||||
| 		return true, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// indicate that we have changed the rc file |  | ||||||
| 	return true, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *envConfig) ensurePathsLoader() error { |  | ||||||
| 	loadFile := filepath.Join(c.home, c.loadFile) |  | ||||||
| 
 |  | ||||||
| 	if _, err := os.Stat(loadFile); nil != err { |  | ||||||
| 		// Write the loop file. For example: |  | ||||||
| 		// $HOME/.config/envpath/load.sh |  | ||||||
| 		// $HOME/.config/envpath/load.fish |  | ||||||
| 		// TODO maybe don't write every time |  | ||||||
| 		if err := ioutil.WriteFile( |  | ||||||
| 			loadFile, |  | ||||||
| 			[]byte(fmt.Sprintf("# Generated for envpath. Do not edit.\n%s\n", c.loadScript)), |  | ||||||
| 			os.FileMode(0755), |  | ||||||
| 		); nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		fmt.Printf("Created %s\n", "~/"+c.loadFile) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // I think this issue only affects darwin users with bash as the default shell |  | ||||||
| func ensureBashProfile(home string) error { |  | ||||||
| 	profileFile := filepath.Join(home, ".bash_profile") |  | ||||||
| 
 |  | ||||||
| 	// touch the profile file |  | ||||||
| 	f, err := os.OpenFile(profileFile, os.O_CREATE|os.O_WRONLY, 0644) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if err := f.Close(); nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b, err := ioutil.ReadFile(profileFile) |  | ||||||
| 	if !bytes.Contains(b, []byte(".bashrc")) { |  | ||||||
| 		f, err := os.OpenFile(profileFile, os.O_APPEND|os.O_WRONLY, 0644) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		sourceBashRC := "[ -s \"$HOME/.bashrc\" ] && source \"$HOME/.bashrc\"\n" |  | ||||||
| 		b := []byte(fmt.Sprintf("# Generated for MacOS bash. Do not edit.\n%s\n", sourceBashRC)) |  | ||||||
| 		_, err = f.Write(b) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		fmt.Printf("Updated ~/.bash_profile to source ~/.bashrc\n") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user