happy parser
This commit is contained in:
parent
a1a5aa81e3
commit
d850aff673
|
@ -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