diff --git a/envs/funcs.go b/envs/funcs.go index 3eacadb..2da63ba 100644 --- a/envs/funcs.go +++ b/envs/funcs.go @@ -1,27 +1,27 @@ package envs import ( - `bytes` - `errors` - `fmt` - `io/ioutil` - `os` - `reflect` - `strings` - `sync` + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "reflect" + "strings" + "sync" - `r00t2.io/goutils/multierr` - `r00t2.io/goutils/structutils` - `r00t2.io/sysutils/errs` - `r00t2.io/sysutils/internal` - `r00t2.io/sysutils/paths` + "r00t2.io/goutils/multierr" + "r00t2.io/goutils/structutils" + "r00t2.io/sysutils/errs" + "r00t2.io/sysutils/internal" + "r00t2.io/sysutils/paths" ) /* - DefEnv operates like Python's .get() method on dicts (maps); - if the environment variable specified by key does not exist/is not specified, - then the value specified by fallback will be returned instead - otherwise key's value is returned. +DefEnv operates like Python's .get() method on dicts (maps); +if the environment variable specified by key does not exist/is not specified, +then the value specified by fallback will be returned instead +otherwise key's value is returned. */ func DefEnv(key, fallback string) (value string) { @@ -45,6 +45,54 @@ func DefEnvBlank(key, fallback string) (value string) { return } +// GetEnvErr returns the value of key if it exists. If it does not exist, err will be an EnvErrNoVal. +func GetEnvErr(key string) (value string, err error) { + + var exists bool + + if value, exists = os.LookupEnv(key); !exists { + err = &EnvErrNoVal{ + VarName: key, + } + return + } + + return +} + +/* +GetEnvErrNoBlank behaves exactly like GetEnvErr with the +additional stipulation that the value must not be empty. + +An error for a value that is non-empty but whitespace only (e.g. VARNM="\t") +can be returned if ignoreWhitespace == true. + +(If it is, an EnvErrNoVal will also be returned.) +*/ +func GetEnvErrNoBlank(key string, ignoreWhitespace bool) (value string, err error) { + + var exists bool + var e *EnvErrNoVal = &EnvErrNoVal{ + VarName: key, + WasRequiredNonEmpty: true, + IgnoreWhiteSpace: ignoreWhitespace, + } + + if value, exists = os.LookupEnv(key); !exists { + err = e + return + } else { + e.WasFound = true + e.WasWhitespace = (strings.TrimSpace(value) == "") && (value != "") + if ignoreWhitespace && e.WasWhitespace { + err = e + return + } + } + + return +} + // GetEnvMap returns a map of all environment variables. All values are strings. func GetEnvMap() (envVars map[string]string) { @@ -56,18 +104,18 @@ func GetEnvMap() (envVars map[string]string) { } /* - GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them. - All values are interfaces. It is up to the caller to typeswitch them to proper types. +GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them. +All values are interfaces. It is up to the caller to typeswitch them to proper types. - Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be - a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator, - will be transformed to a slice or the like. - If an error occurs during parsing the path env var, it will be rendered as a string. +Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be +a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator, +will be transformed to a slice or the like. +If an error occurs during parsing the path env var, it will be rendered as a string. - All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.). +All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.). - If a type cannot be determined for a value, its string form will be used - (as it would be found in GetEnvMap). +If a type cannot be determined for a value, its string form will be used +(as it would be found in GetEnvMap). */ func GetEnvMapNative() (envMap map[string]interface{}) { @@ -79,24 +127,24 @@ func GetEnvMapNative() (envMap map[string]interface{}) { } /* - GetFirst gets the first instance if populated/set occurrence of varNames. +GetFirst gets the first instance if populated/set occurrence of varNames. - For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ, - and want to follow the logic flow of: +For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ, +and want to follow the logic flow of: - 1.) Check if FOO is set. If not, - 2.) Check if FOOBAR is set. If not, - 3.) Check if FOOBARBAZ is set. + 1.) Check if FOO is set. If not, + 2.) Check if FOOBAR is set. If not, + 3.) Check if FOOBARBAZ is set. - Then this would be specified as: +Then this would be specified as: - GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"}) + GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"}) - If val is "" and ok is true, this means that one of the specified variable names IS - set but is set to an empty value. If ok is false, none of the specified variables - are set. +If val is "" and ok is true, this means that one of the specified variable names IS +set but is set to an empty value. If ok is false, none of the specified variables +are set. - It is a thin wrapper around GetFirstWithRef. +It is a thin wrapper around GetFirstWithRef. */ func GetFirst(varNames []string) (val string, ok bool) { @@ -106,14 +154,14 @@ func GetFirst(varNames []string) (val string, ok bool) { } /* - GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx, - which specifies the index in varNames in which a set variable was found. e.g. if: +GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx, +which specifies the index in varNames in which a set variable was found. e.g. if: - GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"}) + GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"}) - is called and FOO is not set but FOOBAR is, idx will be 1. +is called and FOO is not set but FOOBAR is, idx will be 1. - If ok is false, idx will always be -1 and should be ignored. +If ok is false, idx will always be -1 and should be ignored. */ func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) { @@ -148,8 +196,8 @@ func GetPathEnv() (pathList []string, err error) { } /* - GetPidEnvMap will only work on *NIX-like systems with procfs. - It gets the environment variables of a given process' PID. +GetPidEnvMap will only work on *NIX-like systems with procfs. +It gets the environment variables of a given process' PID. */ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) { @@ -187,10 +235,10 @@ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) { } /* - GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them. - All values are interfaces. It is up to the caller to typeswitch them to proper types. +GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them. +All values are interfaces. It is up to the caller to typeswitch them to proper types. - See the documentation for GetEnvMapNative for details. +See the documentation for GetEnvMapNative for details. */ func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) { @@ -206,11 +254,11 @@ func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) { } /* - HasEnv is much like os.LookupEnv, but only returns a boolean for - if the environment variable key exists or not. +HasEnv is much like os.LookupEnv, but only returns a boolean for +if the environment variable key exists or not. - This is useful anywhere you may need to set a boolean in a func call - depending on the *presence* of an env var or not. +This is useful anywhere you may need to set a boolean in a func call +depending on the *presence* of an env var or not. */ func HasEnv(key string) (envIsSet bool) { @@ -220,28 +268,28 @@ func HasEnv(key string) (envIsSet bool) { } /* - Interpolate takes one of: +Interpolate takes one of: - - a string (pointer only) - - a struct (pointer only) - - a map (applied to both keys *and* values) - - a slice + - a string (pointer only) + - a struct (pointer only) + - a map (applied to both keys *and* values) + - a slice - and performs variable substitution on strings from environment variables. +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%). +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 etc., the default is to apply interpolation to both keys and values. - All other tag value(s) are ignored. +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 etc., the default is to apply interpolation to both keys and values. +All other tag value(s) are ignored. - For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected). +For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected). - 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. +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) (err error) { @@ -293,16 +341,16 @@ func Interpolate[T any](s T) (err error) { } /* - InterpolateString takes (a pointer to) a struct or string and performs variable substitution on it - from environment variables. +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%). +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. +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. +This is a standalone function that is much more performant than Interpolate +at the cost of rigidity. */ func InterpolateString(s *string) (err error) { diff --git a/envs/funcs_enverrnoval.go b/envs/funcs_enverrnoval.go new file mode 100644 index 0000000..ebfa805 --- /dev/null +++ b/envs/funcs_enverrnoval.go @@ -0,0 +1,27 @@ +package envs + +import ( + "strings" +) + +// Error conforms to a stdlib error interface. +func (e *EnvErrNoVal) Error() (errStr string) { + + var sb *strings.Builder = new(strings.Builder) + + sb.WriteString("the variable '") + sb.WriteString(e.VarName) + sb.WriteString("' was ") + if sb.WasFound { + sb.WriteString("found") + } else { + sb.WriteString("not found") + } + if e.WasRequiredNonEmpty && e.WasFound { + sb.WriteString(" but is empty and was required to be non-empty") + } + + errStr = sb.String() + + return +} diff --git a/envs/types.go b/envs/types.go new file mode 100644 index 0000000..44f45f7 --- /dev/null +++ b/envs/types.go @@ -0,0 +1,20 @@ +package envs + +type ( + /* + EnvErrNoVal is an error containing the variable that does not exist + (and information surrounding the errored state). + */ + EnvErrNoVal struct { + // VarName is the variable name/key name originally specified in the function call. + VarName string `json:"var" toml:"VariableName" yaml:"Variable Name/Key" xml:"key,attr"` + // WasFound is only used for GetEnvErrNoBlank(). It is true if the variable was found/populated. + WasFound bool `json:"found" toml:"Found" yaml:"Found" xml:"found,attr"` + // WasRequiredNonEmpty indicates that this error was returned in a context where a variable was required to be non-empty (e.g. via GetEnvErrNoBlank()) but was empty. + WasRequiredNonEmpty bool `json:"reqd_non_empty" toml:"RequiredNonEmpty" yaml:"Required Non-Empty" xml:"reqNonEmpty,attr"` + // IgnoreWhitespace is true if the value was found but its evaluation was done against a whitestripped version. + IgnoreWhitespace bool `json:"ignore_ws" toml:"IgnoreWhitespace" yaml:"Ignore Whitespace" xml:"ignoreWhitespace,attr"` + // WasWhitespace is true if the value was whitespace-only. + WasWhitespace bool `json:"was_ws" toml:"WasWhitespace" yaml:"Was Whitespace Only" xml:"wasWhitespace,attr"` + } +)