diff --git a/README.md b/README.md index d97b531..933204d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Manage PATH on Windows, Mac, and Linux with various Shells pathman list pathman add ~/.local/bin pathman remove ~/.local/bin +pathman version +pathman help ``` Windows: stores PATH in the registry. @@ -109,7 +111,7 @@ curl https://rootprojects.org/pathman/dist/linux/armv5/pathman -o pathman ``` mkdir %userprofile%\bin move pathman.exe %userprofile%\bin\pathman.exe -%userprofile%\bin\pathman.exe ~\bin +%userprofile%\bin\pathman.exe add ~\bin ``` **All Others** diff --git a/envpath/manager.go b/envpath/manager.go index 1520c32..5687afc 100644 --- a/envpath/manager.go +++ b/envpath/manager.go @@ -69,10 +69,11 @@ func initializeShells(home string) error { var hasRC bool var nativeMatch *envConfig + shell := strings.TrimSuffix(filepath.Base(os.Getenv("SHELL")), ".exe") for i := range confs { c := confs[i] - if filepath.Base(os.Getenv("SHELL")) == c.shell { + if shell == c.shell { nativeMatch = c } @@ -103,7 +104,7 @@ func initializeShells(home string) error { } // MacOS is special. It *requires* .bash_profile in order to read .bashrc - if "darwin" == runtime.GOOS && "bash" == os.Getenv("SHELL") { + if "darwin" == runtime.GOOS && "bash" == shell { if err := ensureBashProfile(home); nil != err { return err } diff --git a/pathman.go b/pathman.go index 846b306..a33f5a8 100644 --- a/pathman.go +++ b/pathman.go @@ -46,36 +46,10 @@ func main() { entry = os.Args[2] } - // https://superuser.com/a/69190/73857 - // https://github.com/rust-lang-nursery/rustup.rs/issues/686#issuecomment-253982841 - // exec source $HOME/.profile - shell := os.Getenv("SHELL") - shell = filepath.Base(shell) - switch shell { - case "": - if strings.HasSuffix(os.Getenv("COMSPEC"), "/cmd.exe") { - shell = "cmd" - } - case "fish": - // ignore - case "zsh": - // ignore - case "bash": - // ignore - default: - // warn and try anyway - fmt.Fprintf( - os.Stderr, - "%q isn't a recognized shell. Please open an issue at https://git.rootprojects.org/root/pathman/issues?q=%s", - shell, - shell, - ) - } - home, _ := os.UserHomeDir() if "" != entry && '~' == entry[0] { // Let windows users not to have to type %USERPROFILE% or \Users\me every time - entry = strings.Replace(entry, "~", home, 0) + entry = strings.Replace(entry, "~", home, 1) } switch action { default: @@ -92,8 +66,10 @@ func main() { } list() case "add": + checkShell() add(entry) case "remove": + checkShell() remove(entry) } } @@ -157,7 +133,7 @@ func add(entry string) { modified, err := addPath(entry) if nil != err { - fmt.Fprintf(os.Stderr, "failed to add %q to PATH: %s", entry, err) + fmt.Fprintf(os.Stderr, "%sfailed to add %q to PATH: %s", pathstore, entry, err) os.Exit(1) } @@ -223,6 +199,38 @@ func remove(entry string) { fmt.Println(msg + "\n") } +// warns if this is an unknown / untested shell +func checkShell() { + // https://superuser.com/a/69190/73857 + // https://github.com/rust-lang-nursery/rustup.rs/issues/686#issuecomment-253982841 + // exec source $HOME/.profile + shellexe := filepath.Base(os.Getenv("SHELL")) + shell := strings.TrimSuffix(shellexe, ".exe") + switch shell { + case ".": + shell = "" + fallthrough + case "": + if strings.HasSuffix(os.Getenv("COMSPEC"), "\\cmd.exe") { + shell = "cmd" + } + case "fish": + // ignore + case "zsh": + // ignore + case "bash": + // ignore + default: + // warn and try anyway + fmt.Fprintf( + os.Stderr, + "%q isn't a recognized shell. Please open an issue at https://git.rootprojects.org/root/pathman/issues?q=%s\n", + shellexe, + shellexe, + ) + } +} + // Paths returns path entries in the current environment func Paths() []string { cur := os.Getenv("PATH") @@ -242,7 +250,7 @@ func Paths() []string { // path entry to the current shell session func Add(p string) string { if isCmdExe() { - return fmt.Sprintf(`PATH %s;%PATH%`, p) + return fmt.Sprintf(`PATH %s;%%PATH%%`, strings.Replace(p, "%", "%%", -1)) } return fmt.Sprintf(`export PATH="%s:$PATH"`, p) } @@ -257,5 +265,5 @@ func Remove(entries []string) string { } func isCmdExe() bool { - return "" == os.Getenv("SHELL") && strings.Contains(strings.ToLower(os.Getenv("COMSPEC")), "/cmd.exe") + return "" == os.Getenv("SHELL") && strings.HasSuffix(strings.ToLower(os.Getenv("COMSPEC")), "\\cmd.exe") } diff --git a/pathman_unixes.go b/pathman_unixes.go index 455e70e..a285a16 100644 --- a/pathman_unixes.go +++ b/pathman_unixes.go @@ -6,6 +6,8 @@ import ( "git.rootprojects.org/root/pathman/envpath" ) +var pathstore = "" + func addPath(p string) (bool, error) { return envpath.Add(p) } diff --git a/pathman_windows.go b/pathman_windows.go index 4ae403f..b3e9710 100644 --- a/pathman_windows.go +++ b/pathman_windows.go @@ -6,6 +6,8 @@ import ( "git.rootprojects.org/root/pathman/winpath" ) +var pathstore = "[winpath] " + func addPath(p string) (bool, error) { return winpath.Add(p) } diff --git a/winpath/winpath.go b/winpath/winpath.go index 04618e6..d0b82a7 100644 --- a/winpath/winpath.go +++ b/winpath/winpath.go @@ -56,7 +56,7 @@ func NormalizePathEntry(pathentry string) (string, string) { if strings.HasPrefix(strings.ToLower(absentry)+sep, strings.ToLower(home)+sep) { // %USERPROFILE% is allowed, but only for user PATH // https://superuser.com/a/442163/73857 - homeentry = `%USERPROFILE%` + pathentry[len(home):] + homeentry = `%USERPROFILE%` + absentry[len(home):] } if absentry == pathentry { diff --git a/winpath/winpath_unsafe.go b/winpath/winpath_unsafe.go index 850a8d7..e03b94e 100644 --- a/winpath/winpath_unsafe.go +++ b/winpath/winpath_unsafe.go @@ -1,4 +1,4 @@ -// +build windows,unsafe +// +build windows,!nounsafe package winpath diff --git a/winpath/winpath_windows.go b/winpath/winpath_windows.go index 3d0fbee..8b5aa90 100644 --- a/winpath/winpath_windows.go +++ b/winpath/winpath_windows.go @@ -29,12 +29,6 @@ func add(p string) (bool, error) { return false, nil } - k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.SET_VALUE) - if err != nil { - return false, err - } - defer k.Close() - cur = append([]string{p}, cur...) err = write(cur) if nil != err { @@ -63,7 +57,7 @@ func remove(p string) (bool, error) { } } - err = write(cur) + err = write(newpaths) if nil != err { return false, err } @@ -74,15 +68,15 @@ func remove(p string) (bool, error) { func write(cur []string) error { // TODO --system to add to the system PATH rather than the user PATH - k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.QUERY_VALUE) + k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.SET_VALUE) if err != nil { - return err + return fmt.Errorf("Can't open HKCU Environment for writes: %s", err) } defer k.Close() err = k.SetStringValue(`Path`, strings.Join(cur, string(os.PathListSeparator))) if nil != err { - return err + return fmt.Errorf("Can't set HKCU Environment[Path]: %s", err) } err = k.Close() @@ -104,7 +98,7 @@ func paths() ([]string, error) { // TBH, it's a mess to do this on *nix systems. k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.QUERY_VALUE) if err != nil { - return nil, err + return nil, fmt.Errorf("Can't open HKCU Environment for reads: %s", err) } defer k.Close() @@ -112,7 +106,7 @@ func paths() ([]string, error) { // PATH, Path, path will all work. s, _, err := k.GetStringValue("Path") if err != nil { - return nil, err + return nil, fmt.Errorf("Can't query HKCU Environment[Path]: %s", err) } // ";" on Windows