AJ ONeal
5 years ago
13 changed files with 796 additions and 0 deletions
@ -0,0 +1,97 @@ |
|||
package envpath |
|||
|
|||
import ( |
|||
"fmt" |
|||
"strings" |
|||
) |
|||
|
|||
type Warning struct { |
|||
LineNumber int |
|||
Line string |
|||
Message string |
|||
} |
|||
|
|||
// Parse will return a list of paths from an export file
|
|||
func Parse(envname string, b []byte) ([]string, []Warning) { |
|||
s := string(b) |
|||
s = strings.Replace(s, "\r\n", "\n", -1) |
|||
|
|||
badlines := []Warning{} |
|||
newlines := []string{} |
|||
entries := make(map[string]bool) |
|||
lines := strings.Split(s, "\n") |
|||
for i := range lines { |
|||
line := strings.TrimPrefix(strings.TrimSpace(lines[i]), "export ") |
|||
if "" == line { |
|||
continue |
|||
} |
|||
if "# Generated for envman. Do not edit." == line { |
|||
continue |
|||
} |
|||
|
|||
if '#' == line[0] { |
|||
badlines = append(badlines, Warning{ |
|||
LineNumber: i, |
|||
Line: line, |
|||
Message: "comment", |
|||
}) |
|||
continue |
|||
} |
|||
|
|||
index := strings.Index(line, "=") |
|||
if index < 1 { |
|||
badlines = append(badlines, Warning{ |
|||
LineNumber: i, |
|||
Line: line, |
|||
Message: "invalid assignment", |
|||
}) |
|||
continue |
|||
} |
|||
|
|||
env := line[:index] |
|||
if env != envname { |
|||
badlines = append(badlines, Warning{ |
|||
LineNumber: i, |
|||
Line: line, |
|||
Message: fmt.Sprintf("wrong name (%s != %s)", env, envname), |
|||
}) |
|||
continue |
|||
} |
|||
|
|||
val := line[index+1:] |
|||
if len(val) < 2 || '"' != val[0] || '"' != val[len(val)-1] { |
|||
badlines = append(badlines, Warning{ |
|||
LineNumber: i, |
|||
Line: line, |
|||
Message: "value not quoted", |
|||
}) |
|||
continue |
|||
} |
|||
val = val[1 : len(val)-1] |
|||
|
|||
if strings.Contains(val, `"`) { |
|||
badlines = append(badlines, Warning{ |
|||
LineNumber: i, |
|||
Line: line, |
|||
Message: "invalid quotes", |
|||
}) |
|||
|
|||
continue |
|||
} |
|||
|
|||
// TODO normalize $HOME
|
|||
if entries[val] { |
|||
badlines = append(badlines, Warning{ |
|||
LineNumber: i, |
|||
Line: line, |
|||
Message: "duplicate entry", |
|||
}) |
|||
continue |
|||
} |
|||
entries[val] = true |
|||
|
|||
newlines = append(newlines, val) |
|||
} |
|||
|
|||
return newlines, badlines |
|||
} |
@ -0,0 +1,56 @@ |
|||
package envpath |
|||
|
|||
import ( |
|||
"fmt" |
|||
"strings" |
|||
"testing" |
|||
) |
|||
|
|||
const file = `# Generated for envman. Do not edit. |
|||
PATH="/foo" |
|||
|
|||
|
|||
# ignore |
|||
# ignore |
|||
|
|||
PATH="/foo" |
|||
PATH="/foo:$PATH" |
|||
PATH="/foo:$PATH" |
|||
PATH="/foo:"$PATH" |
|||
PATH="/foo:""$PATH" |
|||
PATH="" |
|||
|
|||
PATH= |
|||
|
|||
JUNK="" |
|||
JUNK= |
|||
="" |
|||
= |
|||
|
|||
whatever |
|||
|
|||
|
|||
PATH="/boo:$PATH" |
|||
PATH="" |
|||
|
|||
` |
|||
|
|||
var paths = []string{ |
|||
`PATH="/foo"`, |
|||
`PATH="/foo:$PATH"`, |
|||
`PATH=""`, |
|||
`PATH="/boo:$PATH"`, |
|||
} |
|||
|
|||
func TestParse(t *testing.T) { |
|||
newlines, warnings := Parse("PATH", []byte(file)) |
|||
newfile := `PATH="` + strings.Join(newlines, "\"\n\tPATH=\"") + `"` |
|||
expfile := strings.Join(paths, "\n\t") |
|||
if newfile != expfile { |
|||
t.Errorf("\nExpected:\n\t%s\nGot:\n\t%s", expfile, newfile) |
|||
} |
|||
for i := range warnings { |
|||
w := warnings[i] |
|||
fmt.Printf("warning dropping %q from line %d: %s\n", w.Message, w.LineNumber, w.Line) |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
module git.rootprojects.org/root/pathman |
|||
|
|||
go 1.12 |
|||
|
|||
require golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 |
@ -0,0 +1,229 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"path/filepath" |
|||
"strings" |
|||
) |
|||
|
|||
func usage() { |
|||
fmt.Fprintf(os.Stdout, "Usage: envpath <action> [path]\n") |
|||
fmt.Fprintf(os.Stdout, "\tex: envpath list\n") |
|||
fmt.Fprintf(os.Stdout, "\tex: envpath add ~/.local/bin\n") |
|||
fmt.Fprintf(os.Stdout, "\tex: envpath remove ~/.local/bin\n") |
|||
} |
|||
|
|||
func main() { |
|||
var action string |
|||
var entry string |
|||
|
|||
if len(os.Args) < 2 { |
|||
usage() |
|||
os.Exit(1) |
|||
return |
|||
} else if len(os.Args) > 3 { |
|||
usage() |
|||
os.Exit(1) |
|||
return |
|||
} |
|||
|
|||
action = os.Args[1] |
|||
if 2 == len(os.Args) { |
|||
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") |
|||
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/envpath/issues?q=%s", |
|||
shell, |
|||
shell, |
|||
) |
|||
} |
|||
|
|||
switch action { |
|||
case "list": |
|||
if 2 == len(os.Args) { |
|||
usage() |
|||
os.Exit(1) |
|||
} |
|||
list() |
|||
case "add": |
|||
add(entry) |
|||
case "remove": |
|||
remove(entry) |
|||
} |
|||
} |
|||
|
|||
func list() { |
|||
managedpaths, err := listPaths() |
|||
if nil != err { |
|||
fmt.Fprintf(os.Stderr, "%s", err) |
|||
os.Exit(1) |
|||
} |
|||
|
|||
fmt.Println("pathman-managed PATH entries:\n") |
|||
for i := range managedpaths { |
|||
fmt.Println("\t" + managedpaths[i]) |
|||
} |
|||
if 0 == len(managedpaths) { |
|||
fmt.Println("\t(none)") |
|||
} |
|||
fmt.Println("") |
|||
|
|||
fmt.Println("other PATH entries:\n") |
|||
// All managed paths
|
|||
pathsmap := map[string]bool{} |
|||
for i := range managedpaths { |
|||
// TODO normalize
|
|||
pathsmap[managedpaths[i]] = true |
|||
} |
|||
|
|||
// Paths in the environment which are not managed
|
|||
var hasExtras bool |
|||
envpaths := Paths() |
|||
for i := range envpaths { |
|||
// TODO normalize
|
|||
path := envpaths[i] |
|||
if !pathsmap[path] { |
|||
hasExtras = true |
|||
fmt.Println("\t" + path) |
|||
} |
|||
} |
|||
if !hasExtras { |
|||
fmt.Println("\t(none)") |
|||
} |
|||
fmt.Println("") |
|||
} |
|||
|
|||
func add(entry string) { |
|||
// TODO noramlize away $HOME, %USERPROFILE%, etc
|
|||
abspath, err := filepath.Abs(entry) |
|||
stat, err := os.Stat(entry) |
|||
if nil != err { |
|||
fmt.Fprintf(os.Stderr, "warning: couldn't access %q: %s\n", abspath, err) |
|||
} else if !stat.IsDir() { |
|||
fmt.Fprintf(os.Stderr, "warning: %q is not a directory", abspath) |
|||
} |
|||
|
|||
modified, err := addPath(entry) |
|||
if nil != err { |
|||
fmt.Fprintf(os.Stderr, "failed to add %q to PATH: %s", entry, err) |
|||
os.Exit(1) |
|||
} |
|||
|
|||
var msg string |
|||
if modified { |
|||
msg = "Saved PATH changes." |
|||
} else { |
|||
msg = "PATH not changed." |
|||
} |
|||
|
|||
paths := Paths() |
|||
index := indexOfPath(Paths(), entry) |
|||
if -1 == index { |
|||
// TODO is os.PathListSeparator correct in MINGW / git bash?
|
|||
// generally this has no effect, but just in case this is included in a library with children processes
|
|||
paths = append([]string{entry}, paths...) |
|||
err = os.Setenv(`PATH`, strings.Join(paths, string(os.PathListSeparator))) |
|||
if nil != err { |
|||
// ignore and carry on, as this is optional
|
|||
fmt.Fprintf(os.Stderr, "%s", err) |
|||
} |
|||
|
|||
msg += " To set the PATH immediately, update the current session:\n\n\t" + Add(entry) + "\n" |
|||
} |
|||
|
|||
fmt.Println(msg + "\n") |
|||
} |
|||
|
|||
func remove(entry string) { |
|||
modified, err := removePath(entry) |
|||
if nil != err { |
|||
fmt.Fprintf(os.Stderr, "failed to add %q to PATH: %s", entry, err) |
|||
os.Exit(1) |
|||
} |
|||
|
|||
var msg string |
|||
if modified { |
|||
msg = "Saved PATH changes." |
|||
} else { |
|||
msg = "PATH not changed." |
|||
} |
|||
|
|||
paths := Paths() |
|||
index := indexOfPath(Paths(), entry) |
|||
if index >= 0 { |
|||
newpaths := []string{} |
|||
for i := range paths { |
|||
if i != index { |
|||
newpaths = append(newpaths, paths[i]) |
|||
} |
|||
} |
|||
// TODO is os.PathListSeparator correct in MINGW / git bash?
|
|||
// generally this has no effect, but just in case this is included in a library with children processes
|
|||
err = os.Setenv(`PATH`, strings.Join(newpaths, string(os.PathListSeparator))) |
|||
if nil != err { |
|||
// ignore and carry on, as this is optional
|
|||
fmt.Fprintf(os.Stderr, "%s", err) |
|||
} |
|||
|
|||
msg += " To set the PATH immediately, update the current session:\n\n\t" + Remove(entry) + "\n" |
|||
} |
|||
|
|||
fmt.Println(msg + "\n") |
|||
} |
|||
|
|||
// Paths returns path entries in the current environment
|
|||
func Paths() []string { |
|||
cur := os.Getenv("PATH") |
|||
if "" == cur { |
|||
// unlikely, but possible... so whatever
|
|||
return nil |
|||
} |
|||
|
|||
if isCmdExe() { |
|||
//return strings.Split(cur, string(os.PathListSeparator))
|
|||
return strings.Split(cur, ";") |
|||
} |
|||
return strings.Split(cur, string(os.PathListSeparator)) |
|||
} |
|||
|
|||
// Add returns a string which can be used to add the given
|
|||
// path entry to the current shell session
|
|||
func Add(p string) string { |
|||
if isCmdExe() { |
|||
return fmt.Sprintf(`PATH %s;%PATH%`, p) |
|||
} |
|||
return fmt.Sprintf(`export PATH="%s:$PATH"`, p) |
|||
} |
|||
|
|||
// Remove returns a string which can be used to remove the given
|
|||
// path entry from the current shell session
|
|||
func Remove(entries []string) string { |
|||
if isCmdExe() { |
|||
return fmt.Sprintf(`PATH %s`, strings.Join(entries, ";")) |
|||
} |
|||
return fmt.Sprintf(`export PATH="%s"`, strings.Join(entries, ":")) |
|||
} |
|||
|
|||
func isCmdExe() { |
|||
return "" == os.Getenv("SHELL") && strings.Contains(strings.ToLower(os.Getenv("COMSPEC")), "/cmd.exe") |
|||
} |
@ -0,0 +1,23 @@ |
|||
// +build windows
|
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"git.rootprojects.org/root/pathman/envpath" |
|||
) |
|||
|
|||
func addPath(p string) (bool, error) { |
|||
return envpath.Add(p) |
|||
} |
|||
|
|||
func removePath(p string) (bool, error) { |
|||
return envpath.Remove(p) |
|||
} |
|||
|
|||
func listPaths() ([]string, error) { |
|||
return envpath.List() |
|||
} |
|||
|
|||
func indexOfPath(cur []string, p string) int { |
|||
return envpath.IndexOf(cur, p) |
|||
} |
@ -0,0 +1,23 @@ |
|||
// +build windows
|
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"git.rootprojects.org/root/pathman/winpath" |
|||
) |
|||
|
|||
func addPath(p string) (bool, error) { |
|||
return winpath.Add(p) |
|||
} |
|||
|
|||
func removePath(p string) (bool, error) { |
|||
return winpath.Remove(p) |
|||
} |
|||
|
|||
func listPaths() ([]string, error) { |
|||
return winpath.List() |
|||
} |
|||
|
|||
func indexOfPath(cur []string, p string) int { |
|||
return winpath.IndexOf(cur, p) |
|||
} |
@ -0,0 +1,63 @@ |
|||
# winpath |
|||
|
|||
An example of getting, setting, and broadcasting PATHs on Windows. |
|||
|
|||
This requires the `unsafe` package to use a syscall with special message poitners to update `PATH` without a reboot. |
|||
It will also build without `unsafe`. |
|||
|
|||
```bash |
|||
go build -tags unsafe -o winpath.exe |
|||
``` |
|||
|
|||
```bash |
|||
winpath show |
|||
|
|||
%USERPROFILE%\AppData\Local\Microsoft\WindowsApps |
|||
C:\Users\me\AppData\Local\Programs\Microsoft VS Code\bin |
|||
%USERPROFILE%\go\bin |
|||
C:\Users\me\AppData\Roaming\npm |
|||
C:\Users\me\AppData\Local\Keybase\ |
|||
``` |
|||
|
|||
```bash |
|||
winpath append C:\someplace\special |
|||
|
|||
Run the following for changes to take affect immediately: |
|||
PATH %PATH%;C:\someplace\special |
|||
``` |
|||
|
|||
```bash |
|||
winpath prepend C:\someplace\special |
|||
|
|||
Run the following for changes to take affect immediately: |
|||
PATH C:\someplace\special;%PATH% |
|||
``` |
|||
|
|||
```bash |
|||
winpath remove C:\someplace\special |
|||
``` |
|||
|
|||
# Special Considerations |
|||
|
|||
Giving away the secret sauce right here: |
|||
|
|||
* `HWND_BROADCAST` |
|||
* `WM_SETTINGCHANGE` |
|||
|
|||
This is essentially the snippet you need to have the HKCU and HKLM Environment registry keys propagated without rebooting: |
|||
|
|||
```go |
|||
HWND_BROADCAST := uintptr(0xffff) |
|||
WM_SETTINGCHANGE := uintptr(0x001A) |
|||
_, _, err := syscall. |
|||
NewLazyDLL("user32.dll"). |
|||
NewProc("SendMessageW"). |
|||
Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ENVIRONMENT")))) |
|||
|
|||
``` |
|||
|
|||
* `os.Getenv("COMSPEC")` |
|||
* `os.Getenv("SHELL")` |
|||
|
|||
If you check `SHELL` and it isn't empty, then you're probably in MINGW or some such. |
|||
If that's empty but `COMSPEC` isn't, you can be reasonably sure that you're in cmd.exe or Powershell. |
@ -0,0 +1,16 @@ |
|||
// +build windows
|
|||
|
|||
package winpath |
|||
|
|||
import "testing" |
|||
|
|||
func TestShow(t *testing.T) { |
|||
paths, err := Paths() |
|||
if nil != err { |
|||
t.Error(err) |
|||
} |
|||
|
|||
if len(paths) < 1 { |
|||
t.Error("should have paths") |
|||
} |
|||
} |
@ -0,0 +1,94 @@ |
|||
// Package winpath is useful for managing PATH as part of the Environment
|
|||
// in the Windows HKey Local User registry. It returns an error for most
|
|||
// operations on non-Windows systems.
|
|||
package winpath |
|||
|
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"path/filepath" |
|||
"strings" |
|||
) |
|||
|
|||
// ErrWrongPlatform indicates that this was not built for Windows
|
|||
var ErrWrongPlatform = fmt.Errorf("method not implemented on this platform") |
|||
|
|||
// sendmsg uses a syscall to broadcast the registry change so that
|
|||
// new shells will get the new PATH immediately, without a reboot
|
|||
var sendmsg func() |
|||
|
|||
// Paths returns all PATHs according to the Windows HKLU registry
|
|||
// (or nil on non-windows platforms)
|
|||
func Paths() ([]string, error) { |
|||
return paths() |
|||
} |
|||
|
|||
// Add will rewrite the Windows registry HKLU Environment,
|
|||
// prepending the given directory path to the user's PATH.
|
|||
// It will return whether the PATH was modified and an
|
|||
// error if it should have been modified, but wasn't.
|
|||
func Add(p string) (bool, error) { |
|||
return add(p) |
|||
} |
|||
|
|||
// Remove will rewrite the Windows registry HKLU Environment
|
|||
// without the given directory path.
|
|||
// It will return whether the PATH was modified and an
|
|||
// error if it should have been modified, but wasn't.
|
|||
func Remove(p string) (bool, error) { |
|||
return remove(p) |
|||
} |
|||
|
|||
// NormalizePathEntry will return the given directory path relative
|
|||
// from its absolute path to the %USERPROFILE% (home) directory.
|
|||
func NormalizePathEntry(pathentry string) (string, string) { |
|||
home, err := os.UserHomeDir() |
|||
if nil != err { |
|||
fmt.Fprintf(os.Stderr, "Couldn't get HOME directory. That's an unrecoverable hard fail.") |
|||
panic(err) |
|||
} |
|||
|
|||
sep := string(os.PathSeparator) |
|||
absentry, _ := filepath.Abs(pathentry) |
|||
home, _ = filepath.Abs(home) |
|||
|
|||
var homeentry 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):] |
|||
} |
|||
|
|||
if absentry == pathentry { |
|||
absentry = "" |
|||
} |
|||
if homeentry == pathentry { |
|||
homeentry = "" |
|||
} |
|||
|
|||
return absentry, homeentry |
|||
} |
|||
|
|||
// 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 { |
|||
abspath, homepath := NormalizePathEntry(p) |
|||
|
|||
index := -1 |
|||
for i := range paths { |
|||
if strings.ToLower(p) == strings.ToLower(paths[i]) { |
|||
index = i |
|||
break |
|||
} |
|||
if strings.ToLower(abspath) == strings.ToLower(paths[i]) { |
|||
index = i |
|||
break |
|||
} |
|||
if strings.ToLower(homepath) == strings.ToLower(paths[i]) { |
|||
index = i |
|||
break |
|||
} |
|||
} |
|||
|
|||
return index |
|||
} |
@ -0,0 +1,15 @@ |
|||
// +build !windows
|
|||
|
|||
package winpath |
|||
|
|||
func paths() ([]string, error) { |
|||
return nil, ErrWrongPlatform |
|||
} |
|||
|
|||
func add(string) (bool, error) { |
|||
return false, ErrWrongPlatform |
|||
} |
|||
|
|||
func remove(string) (bool, error) { |
|||
return false, ErrWrongPlatform |
|||
} |
@ -0,0 +1,23 @@ |
|||
package winpath |
|||
|
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"testing" |
|||
) |
|||
|
|||
func TestNormalize(t *testing.T) { |
|||
home, _ := os.UserHomeDir() |
|||
|
|||
absexp := "" |
|||
homeexp := "%USERPROFILE%" + string(os.PathSeparator) + "foo" |
|||
abspath, homepath := NormalizePathEntry(home + string(os.PathSeparator) + "foo") |
|||
|
|||
if absexp != abspath { |
|||
t.Error(fmt.Errorf("Expected %q, but got %q", absexp, abspath)) |
|||
} |
|||
|
|||
if homeexp != homepath { |
|||
t.Error(fmt.Errorf("Expected %q, but got %q", homeexp, homepath)) |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
// +build windows,unsafe
|
|||
|
|||
package winpath |
|||
|
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"syscall" |
|||
"unsafe" |
|||
) |
|||
|
|||
const ( |
|||
HWND_BROADCAST = uintptr(0xffff) |
|||
WM_SETTINGCHANGE = uintptr(0x001A) |
|||
) |
|||
|
|||
func init() { |
|||
|
|||
// WM_SETTING_CHANGE
|
|||
// https://gist.github.com/microo8/c1b9525efab9bb462adf9d123e855c52
|
|||
sendmsg = func() { |
|||
//x, y, err := syscall.
|
|||
_, _, err := syscall. |
|||
NewLazyDLL("user32.dll"). |
|||
NewProc("SendMessageW"). |
|||
Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ENVIRONMENT")))) |
|||
//fmt.Fprintf(os.Stderr, "%d, %d, %s\n", x, y, err)
|
|||
if nil != err { |
|||
fmt.Fprintf(os.Stderr, "%s\n", err) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,120 @@ |
|||
// +build windows
|
|||
|
|||
package winpath |
|||
|
|||
// Needs to
|
|||
// * use the registry editor directly to avoid possible PATH truncation
|
|||
// ( https://stackoverflow.com/questions/9546324/adding-directory-to-path-environment-variable-in-windows )
|
|||
// ( https://superuser.com/questions/387619/overcoming-the-1024-character-limit-with-setx )
|
|||
// * explicitly send WM_SETTINGCHANGE
|
|||
// ( https://github.com/golang/go/issues/18680#issuecomment-275582179 )
|
|||
|
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"strings" |
|||
|
|||
"golang.org/x/sys/windows/registry" |
|||
) |
|||
|
|||
func add(p string) (bool, error) { |
|||
cur, err := paths() |
|||
if nil != err { |
|||
return false, err |
|||
} |
|||
|
|||
index := IndexOf(cur, p) |
|||
// skip silently, successfully
|
|||
if index >= 0 { |
|||
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 { |
|||
return false, err |
|||
} |
|||
|
|||
return true, nil |
|||
} |
|||
|
|||
func remove(p string) (bool, error) { |
|||
cur, err := paths() |
|||
if nil != err { |
|||
return false, err |
|||
} |
|||
|
|||
index := findMatch(cur, p) |
|||
// skip silently, successfully
|
|||
if index < 0 { |
|||
return false, nil |
|||
} |
|||
|
|||
var newpaths []string |
|||
for i := range cur { |
|||
if i != index { |
|||
newpaths = append(newpaths, cur[i]) |
|||
} |
|||
} |
|||
|
|||
err = write(cur) |
|||
if nil != err { |
|||
return false, err |
|||
} |
|||
|
|||
return true, nil |
|||
} |
|||
|
|||
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) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
defer k.Close() |
|||
|
|||
err = k.SetStringValue(`Path`, strings.Join(cur, string(os.PathListSeparator))) |
|||
if nil != err { |
|||
return err |
|||
} |
|||
|
|||
err = k.Close() |
|||
if nil != err { |
|||
return err |
|||
} |
|||
|
|||
if nil != sendmsg { |
|||
sendmsg() |
|||
} else { |
|||
fmt.Fprintf(os.Stderr, "Warning: added PATH, but you must reboot for changes to take effect\n") |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func paths() ([]string, error) { |
|||
// This is the canonical reference, which is actually quite nice to have.
|
|||
// 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 |
|||
} |
|||
defer k.Close() |
|||
|
|||
// This is case insensitive on Windows.
|
|||
// PATH, Path, path will all work.
|
|||
s, _, err := k.GetStringValue("Path") |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
// ";" on Windows
|
|||
return strings.Split(s, string(os.PathListSeparator)), nil |
|||
} |
Loading…
Reference in new issue