diff --git a/envs/consts.go b/envs/consts.go index 6ee386f..e4cedd4 100644 --- a/envs/consts.go +++ b/envs/consts.go @@ -9,3 +9,25 @@ var ( reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P\+|-)[0-9]+$`) reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P\+|-)?[0-9]+\.[0-9]+$`) ) + +var ( + StructTagInterpolate string = "envsub" + StructTagPopulate string = "envpop" +) + +var ( + defaultInterpolateOpts interpolateOpts = interpolateOpts{ + noMapKey: false, + noMapVal: false, + } + // InterpolateOptNoMapKey is the equivalent of the struct tag `no_map_key` for Interpolate. + InterpolateOptNoMapKey optInterpolate = func(o *interpolateOpts) (err error) { + o.noMapKey = true + return + } + // InterpolateOptNoMapValue is the equivalent of the struct tag `no_map_value` for Interpolate. + InterpolateOptNoMapValue optInterpolate = func(o *interpolateOpts) (err error) { + o.noMapVal = true + return + } +) diff --git a/envs/funcs.go b/envs/funcs.go index 8bdeb58..073b672 100644 --- a/envs/funcs.go +++ b/envs/funcs.go @@ -6,28 +6,16 @@ import ( `fmt` `io/ioutil` `os` + `reflect` `strings` + `sync` + `r00t2.io/goutils/multierr` + `r00t2.io/sysutils/errs` `r00t2.io/sysutils/internal` `r00t2.io/sysutils/paths` ) -// GetPathEnv returns a slice of the PATH variable's items. -func GetPathEnv() (pathList []string, err error) { - - var pathVar string = internal.GetPathEnvName() - - pathList = make([]string, 0) - - for _, p := range strings.Split(os.Getenv(pathVar), string(os.PathListSeparator)) { - if err = paths.RealPath(&p); err != nil { - return - } - pathList = append(pathList, p) - } - return -} - // GetEnvMap returns a map of all environment variables. All values are strings. func GetEnvMap() (envVars map[string]string) { @@ -114,6 +102,22 @@ func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) { return } +// GetPathEnv returns a slice of the PATH variable's items. +func GetPathEnv() (pathList []string, err error) { + + var pathVar string = internal.GetPathEnvName() + + pathList = make([]string, 0) + + for _, p := range strings.Split(os.Getenv(pathVar), string(os.PathListSeparator)) { + if err = paths.RealPath(&p); err != nil { + return + } + pathList = append(pathList, p) + } + return +} + /* GetPidEnvMap will only work on *NIX-like systems with procfs. It gets the environment variables of a given process' PID. @@ -185,3 +189,609 @@ func HasEnv(key string) (envIsSet bool) { return } + +/* + Interpolate takes one of: + + - a string (pointer only) + - a struct (pointer only) + - a map + - a slice + + and performs variable substitution on strings from environment variables. + + It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and, + if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%). + + For structs, the tag name used can be changed by setting the StructTagInterpolate + variable in this submodule; the default is `envsub`. + If the tag value is "-", the field will be skipped. + For map fields within structs, the default is to apply interpolation to both keys and values; + this can be changed with the `no_map_key` and `no_map_value` options (tag values). + Any other tag value(s) are ignored. + + For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected). + + Supported struct tag options: + + * `no_map_key` - Do not operate on map keys if they are strings or string pointers. + See also InterpolateOptNoMapKey. + * `no_map_value` - Do not operate on map values if they are strings or string pointers. + See also InterpolateOptNoMapValue. + + If s is nil, no interpolation will be performed. No error will be returned. + If s is not a valid/supported type, no interpolation will be performed. No error will be returned. +*/ +func Interpolate[T any](s T, opts ...optInterpolate) (err error) { + + var sVal reflect.Value = reflect.ValueOf(s) + var sType reflect.Type = sVal.Type() + var kind reflect.Kind = sType.Kind() + var ptrVal reflect.Value + var ptrType reflect.Type + var ptrKind reflect.Kind + + switch kind { + case reflect.Ptr: + if sVal.IsNil() || sVal.IsZero() || !sVal.IsValid() { + return + } + ptrVal = sVal.Elem() + ptrType = ptrVal.Type() + ptrKind = ptrType.Kind() + if ptrKind == reflect.String { + err = interpolateStringReflect(ptrVal, opts, nil) + } else { + // Otherwise, it should be a struct ptr. + if ptrKind != reflect.Struct { + return + } + err = interpolateStruct(ptrVal, opts, nil) + } + case reflect.Map: + if sVal.IsNil() || sVal.IsZero() || !sVal.IsValid() { + return + } + err = interpolateMap(sVal, opts, nil) + case reflect.Slice: + if sVal.IsNil() || sVal.IsZero() || !sVal.IsValid() { + return + } + err = interpolateSlice(sVal, opts, nil) + /* + case reflect.Struct: + if sVal.IsZero() || !sVal.IsValid() { + return + } + err = interpolateStruct(sVal, opts, nil) + + */ + } + + return +} + +/* + InterpolateString takes (a pointer to) a struct or string and performs variable substitution on it + from environment variables. + + It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and, + if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%). + + If s is nil, nothing will be done and err will be ErrNilPtr. + + This is a standalone function that is much more performant than Interpolate + at the cost of rigidity. +*/ +func InterpolateString(s *string) (err error) { + + var newStr string + + if s == nil { + err = errs.ErrNilPtr + return + } + + if newStr, err = interpolateString(*s); err != nil { + return + } + *s = newStr + + return +} + +/* + PopulateStruct takes (a pointer to) a struct and performs *population* on it. + Unlike the InterpolateStruct function, this *completely populates* (or *replaces*) + a field's value with the specified environment variable; no *substitution* is performed. + + You can change the tag name used by changing the StructTagPopulate variable in this module; + the default is `envpop`. + + Tag value format: + :"[,