From 3e51ac58dbf9e2e6107ce1a3dffa204dafb487de Mon Sep 17 00:00:00 2001 From: brent s Date: Tue, 16 Nov 2021 01:44:08 -0500 Subject: [PATCH] update some stuff for paths, add env mappings --- funcs.go | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ net/README.adoc | 2 +- paths/func.go | 153 +++++++++++++++++++++++------------ 3 files changed, 309 insertions(+), 54 deletions(-) create mode 100644 funcs.go diff --git a/funcs.go b/funcs.go new file mode 100644 index 0000000..619f005 --- /dev/null +++ b/funcs.go @@ -0,0 +1,208 @@ +package sysutils + +import ( + `os` + `runtime` + `strconv` + `strings` +) + +/* + EnvMapper contains the environment variables as grouped by their basic type. + If a variable's type cannot be determined, it's placed in Strings. + If a variable's type is a list, it will be an []interface{} as each item may be a different variable type. + It essentially is the same as EnvMap except with the types split out for convenience. +*/ +type EnvMapper struct { + Booleans map[string]bool `json:"bools"` + Numbers map[string]int `json:"nums"` + Strings map[string]string `json:"strings"` + Lists map[string][]interface{} `json:"lists"` +} + +// GetEnvMapper returns a pointer to a populated EnvMapper. +func GetEnvMapper() (e *EnvMapper, err error) { + + var em map[string]interface{} + var env EnvMapper + + env = EnvMapper{ + Booleans: nil, + Numbers: nil, + Strings: nil, + Lists: nil, + } + + for k, v := range em { + + switch t := v.(type) { + case bool: + if env.Booleans == nil { + env.Booleans = make(map[string]bool, 0) + } + env.Booleans[k] = t + continue + case int: + if env.Numbers == nil { + env.Numbers = make(map[string]int, 0) + } + env.Numbers[k] = t + continue + case []interface{}: + if env.Lists == nil { + env.Lists = make(map[string][]interface{}, 0) + } + env.Lists[k] = t + case string: // the "default" since everything could probably be typeswitched to a string otherwise. + if env.Strings == nil { + env.Strings = make(map[string]string, 0) + } + env.Strings[k] = t + } + } + + *e = env + + return +} + +/* + EnvMap returns a map of environment variables. + The variable is an interface due to it attempting to use a variable's value to its "true" native type. + If a type cannot be determined, it will be treated a string. +*/ +func EnvMap() (envs map[string]interface{}, err error) { + + var ems map[string]string + + if ems, err = EnvMapString(); err != nil { + return + } + + for k, v := range ems { + + // Is int? + if i, ok := getNum(v); ok { + envs[k] = i + continue + } + + // Is bool? + if b, ok := getBool(v); ok { + envs[k] = b + continue + } + + // Is array? + if a, ok := getArr(v); ok { + envs[k] = a + continue + } + + // It's a string (probably). + envs[k] = v + } + + return +} + +// EnvMapString is like EnvMap, but all values are treated as strings. +func EnvMapString() (envs map[string]string, err error) { + + var envArray []string + + envs = make(map[string]string, 0) + + envArray = os.Environ() + + for _, e := range envArray { + var k, v string + var kv []string + + kv = strings.SplitN(e, "=", 2) + k = kv[0] + if len(kv) == 2 { + v = kv[1] + } else { + v = "" + } + envs[k] = v + } + + return +} + +// UTILITY FUNCS + +// getBool attempts to convert a string value to a boolean. +func getBool(s string) (b bool, ok bool) { + + switch s2 := strings.ToLower(strings.TrimSpace(s)); s2 { + case "true", "yes", "y": + b = true + ok = true + case "false", "no", "n": + b = false + ok = true + } + + return +} + +// getNum attempts to convert a string value to an int. +func getNum(s string) (n int, ok bool) { + + var err error + + if n, err = strconv.Atoi(s); err == nil { + ok = true + } + + return +} + +// getArr attempts to convert a string value to an array of interface{}. +func getArr(s string) (a []interface{}, ok bool) { + + var arrS []string + + if arrS, ok = getArrStr(s); !ok { + return + } + + a = make([]interface{}, len(arrS)) + + for idx, i := range arrS { + if b, ok := getBool(i); ok { + a[idx] = b + } else if n, ok := getNum(i); ok { + a[idx] = n + } else if l, ok := getArr(i); ok { + a[idx] = l + } else { + a[idx] = i + } + } + + return +} + +// getArrStr attempts to convert a string value to an array of strings. +func getArrStr(s string) (a []string, ok bool) { + + var sep string = ":" + + if runtime.GOOS == "windows" { + sep = ";" + } + + a = strings.Split(s, sep) + l := len(s) + if l <= 1 { + return + } + + ok = true + + return +} diff --git a/net/README.adoc b/net/README.adoc index 02b7815..c2e3df4 100644 --- a/net/README.adoc +++ b/net/README.adoc @@ -63,7 +63,7 @@ func main() { "\tService: %v\n" + "\tDesc: %v\n" + "\tReserved?: %v\n", - p.Number, p.Proto.Name, p.ServiceName, p.Description, p.Reserved) + p.Number, p.Protocol.Name, p.ServiceName, p.Description, p.Reserved) } ---- diff --git a/paths/func.go b/paths/func.go index 5bcde7c..05e51b3 100644 --- a/paths/func.go +++ b/paths/func.go @@ -20,108 +20,155 @@ package paths import ( "errors" - "fmt" + `fmt` "os" "os/user" "path/filepath" + `runtime` // "strconv" "strings" + // "syscall" ) -var err error +/* + ExpandHome will take a tilde(~)-prefixed path and resolve it to the actual path in-place. + Note that it only works for current user; the syntax ~someotheruser/foo/bar is currently unsupported. +*/ +func ExpandHome(path *string) (err error) { + + var usr *user.User -func ExpandHome(path *string) error { // Props to this guy. // https://stackoverflow.com/a/43578461/733214 if len(*path) == 0 { - return errors.New("empty path") + err = errors.New("empty path") + return } else if (*path)[0] != '~' { - return nil + return } + // E(ffective)UID (e.g. chown'd user for SUID) /* - uid := strconv.Itoa(syscall.Geteuid()) - usr, err := user.LookupId(euid) + uid := strconv.Itoa(syscall.Geteuid()) + usr, err := user.LookupId(euid) */ - // R(real)UID (invoking user) - usr, err := user.Current() - if err != nil { + // (Real)UID (invoking user) + if usr, err = user.Current(); err != nil { return err } *path = filepath.Join(usr.HomeDir, (*path)[1:]) - return nil + + return } -func GetPathEnv() ([]string, error) { - paths := []string{} - for _, p := range strings.Split(os.Getenv("PATH"), ":") { +// GetPathEnv returns a slice of the PATH variable's items. +func GetPathEnv() (paths []string, err error) { + + var pathVar string = "PATH" + var sep string = ":" + + paths = make([]string, 0) + + if runtime.GOOS == "windows" { + pathVar = "Path" + sep = ";" + } + + for _, p := range strings.Split(os.Getenv(pathVar), sep) { if err = RealPath(&p); err != nil { - return nil, err + return } paths = append(paths, p) } - return paths, nil + return } -func MakeDirIfNotExist(path *string) error { - exists, stat, err := RealPathExistsStat(path) - if err != nil { +// MakeDirIfNotExist will create a directory at a given path if it doesn't exist. +func MakeDirIfNotExist(path string) (err error) { + + var stat os.FileInfo + var exists bool + var locPath string = path + + if exists, stat, err = RealPathExistsStat(&locPath); err != nil { if !exists { // This, at least as of golang 1.15, uses the user's umask. // It does not actually create a dir with 0777. // It's up to the caller to do an os.Chmod() on the path after, if desired. - os.MkdirAll(*path, 0777) - return nil + if err = os.MkdirAll(locPath, 0777); err != nil { + return + } + err = nil + return } else { - return err + return } } + // So it exists, but it probably isn't a dir. if !stat.Mode().IsDir() { - return errors.New(fmt.Sprintf("path %v exists but is not a directory", *path)) + err = errors.New(fmt.Sprintf("path %v exists but is not a directory", locPath)) + return } + // This should probably never happen. Probably. - return errors.New("undefined behaviour") + err = errors.New("undefined") + return } -func RealPath(path *string) error { - err := ExpandHome(path) - if err != nil { - return err +// RealPath will transform a given path into the very best guess for an absolute path in-place. +func RealPath(path *string) (err error) { + + if err = ExpandHome(path); err != nil { + return } - *path, err = filepath.Abs(*path) - if err != nil { - return err + + if *path, err = filepath.Abs(*path); err != nil { + return } - return nil + + return } -func RealPathExists(path *string) (bool, error) { - // I know it's hacky, but we use the bool as a sort of proto-state-machine thing. - // If err != nil and bool is true, the error occurred during path absolution. - // If err != nil and bool is false, the path does not exist. - err := RealPath(path) - if err != nil { - return true, err +/* + RealPathExists is like RealPath, but will also return a boolean as to whether the path + actually exists or not. + + It's hacky, but the "exists" bool along with err is a sort of proto-state-machine. + If err != nil and bool is true, the error occurred during path absolution. + If err != nil and bool is false, the path does not exist. +*/ +func RealPathExists(path *string) (exists bool, err error) { + + if err = RealPath(path); err != nil { + exists = true + return } - if _, err := os.Stat(*path); err != nil { - return false, err + + if _, err = os.Stat(*path); err != nil { + return } - return true, nil + + exists = true + + return } -func RealPathExistsStat(path *string) (bool, os.FileInfo, error) { - // Same deal as RealPathExists. - // If err != nil and bool is true, the error occurred during path absolution. - // If err != nil and bool is false, the path does not exist. - err := RealPath(path) - if err != nil { - return true, nil, err +// RealPathExistsStat is like RealPathExists except it will also return the os.FileInfo for the path (assuming it exists). +func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error) { + + // See the comments for RealPathExists for details on this. + if err = RealPath(path); err != nil { + exists = true + return } - stat, err := os.Stat(*path) - if err != nil { - return false, nil, err + + if stat, err = os.Stat(*path); err != nil { + return } - return true, stat, nil + + exists = true + + return }