checking in before refactoring interpolation
This commit is contained in:
parent
187ad868db
commit
eed9c34ebf
@ -9,3 +9,25 @@ var (
|
|||||||
reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P<sign>\+|-)[0-9]+$`)
|
reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P<sign>\+|-)[0-9]+$`)
|
||||||
reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P<sign>\+|-)?[0-9]+\.[0-9]+$`)
|
reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P<sign>\+|-)?[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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
642
envs/funcs.go
642
envs/funcs.go
@ -6,28 +6,16 @@ import (
|
|||||||
`fmt`
|
`fmt`
|
||||||
`io/ioutil`
|
`io/ioutil`
|
||||||
`os`
|
`os`
|
||||||
|
`reflect`
|
||||||
`strings`
|
`strings`
|
||||||
|
`sync`
|
||||||
|
|
||||||
|
`r00t2.io/goutils/multierr`
|
||||||
|
`r00t2.io/sysutils/errs`
|
||||||
`r00t2.io/sysutils/internal`
|
`r00t2.io/sysutils/internal`
|
||||||
`r00t2.io/sysutils/paths`
|
`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.
|
// GetEnvMap returns a map of all environment variables. All values are strings.
|
||||||
func GetEnvMap() (envVars map[string]string) {
|
func GetEnvMap() (envVars map[string]string) {
|
||||||
|
|
||||||
@ -114,6 +102,22 @@ func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) {
|
|||||||
return
|
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.
|
GetPidEnvMap will only work on *NIX-like systems with procfs.
|
||||||
It gets the environment variables of a given process' PID.
|
It gets the environment variables of a given process' PID.
|
||||||
@ -185,3 +189,609 @@ func HasEnv(key string) (envIsSet bool) {
|
|||||||
|
|
||||||
return
|
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:
|
||||||
|
<tag>:"<VAR NAME>[,<option>,<option>...]"
|
||||||
|
e.g.
|
||||||
|
envpop:"SOMEVAR"
|
||||||
|
envpop:"OTHERVAR,force"
|
||||||
|
envpop:"OTHERVAR,allow_empty"
|
||||||
|
envpop:"OTHERVAR,force,allow_empty"
|
||||||
|
|
||||||
|
If the tag value is "-", or <VAR NAME> is not provided, the field will be explicitly skipped.
|
||||||
|
(This is the default behavior for struct fields not tagged with `envpop`.)
|
||||||
|
|
||||||
|
Recognized options:
|
||||||
|
|
||||||
|
* force - Existing field values that are non-empty strings or non-nil pointers are normally skipped; this option always replaces them.
|
||||||
|
* allow_empty - Normally no replacement will be performed if the specified variable is undefined/not found.
|
||||||
|
This option allows an empty string to be used instead.
|
||||||
|
Not very useful for string fields, but potentially useful for string pointer fields.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
struct{
|
||||||
|
// If this is an empty string, it will be replaced with the value of $CWD.
|
||||||
|
CurrentDir string `envpop:"CWD"`
|
||||||
|
// This would only populate with $USER if the pointer is nil.
|
||||||
|
UserName *string `envpop:"USER"`
|
||||||
|
// This will *always* replace the field's value with the value of $DISPLAY,
|
||||||
|
// even if not an empty string.
|
||||||
|
// Note the `force` option.
|
||||||
|
Display string `envpop:"DISPLAY,force"`
|
||||||
|
// Likewise, even if not nil, this field's value would be replaced with the value of $SHELL.
|
||||||
|
Shell *string `envpop:"SHELL,force"`
|
||||||
|
// This field will be untouched if non-nil, otherwise it will be a pointer to an empty string
|
||||||
|
// if FOOBAR is undefined.
|
||||||
|
NonExistentVar *string `envpop:"FOOBAR,allow_empty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
If s is nil, nothing will be done and err will be errs.ErrNilPtr.
|
||||||
|
If s is not a pointer to a struct, nothing will be done and err will be errs.ErrBadType.
|
||||||
|
*/
|
||||||
|
func PopulateStruct[T any](s T) (err error) {
|
||||||
|
|
||||||
|
var structVal reflect.Value
|
||||||
|
var structType reflect.Type
|
||||||
|
var field reflect.StructField
|
||||||
|
var fieldVal reflect.Value
|
||||||
|
var tagVal string
|
||||||
|
var valSplit []string
|
||||||
|
var varNm string
|
||||||
|
var varVal string
|
||||||
|
var optsMap map[string]bool
|
||||||
|
var force bool
|
||||||
|
var allowEmpty bool
|
||||||
|
var defined bool
|
||||||
|
|
||||||
|
if reflect.TypeOf(s).Kind() != reflect.Ptr {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
structVal = reflect.ValueOf(s)
|
||||||
|
if structVal.IsNil() || structVal.IsZero() || !structVal.IsValid() {
|
||||||
|
err = errs.ErrNilPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
structVal = reflect.ValueOf(s).Elem()
|
||||||
|
structType = structVal.Type()
|
||||||
|
|
||||||
|
if structType.Kind() != reflect.Struct {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < structVal.NumField(); i++ {
|
||||||
|
field = structType.Field(i)
|
||||||
|
fieldVal = structVal.Field(i)
|
||||||
|
|
||||||
|
// Skip explicitly skipped or non-tagged fields.
|
||||||
|
tagVal = field.Tag.Get(StructTagPopulate)
|
||||||
|
if tagVal == "" || strings.TrimSpace(tagVal) == "-" || strings.HasPrefix(tagVal, "-,") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldVal = structVal.Field(i)
|
||||||
|
if fieldVal.Kind() != reflect.Ptr && fieldVal.Kind() != reflect.String {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
optsMap = make(map[string]bool)
|
||||||
|
valSplit = strings.Split(tagVal, ",")
|
||||||
|
if valSplit == nil || len(valSplit) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
varNm = valSplit[0]
|
||||||
|
if strings.TrimSpace(varNm) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(valSplit) >= 2 {
|
||||||
|
for _, o := range valSplit[1:] {
|
||||||
|
optsMap[o] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
force = optsMap["force"]
|
||||||
|
allowEmpty = optsMap["allow_empty"]
|
||||||
|
|
||||||
|
// if !force && (!fieldVal.IsNil() && !fieldVal.IsZero()) {
|
||||||
|
if !force && !fieldVal.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldVal.Kind() == reflect.Ptr {
|
||||||
|
if field.Type.Elem().Kind() != reflect.String {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fieldVal.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
varVal, defined = os.LookupEnv(varNm)
|
||||||
|
if !defined && !allowEmpty {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fieldVal.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
fieldVal.Set(reflect.ValueOf(&varVal))
|
||||||
|
case reflect.String:
|
||||||
|
fieldVal.SetString(varVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateMap is used by Interpolate and interpolateReflect for maps. v should be a reflect.Value of a map.
|
||||||
|
func interpolateMap(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var kVal reflect.Value
|
||||||
|
var newMap reflect.Value
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var numJobs int
|
||||||
|
var errChan chan error
|
||||||
|
var doneChan chan bool = make(chan bool, 1)
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
var t reflect.Type = v.Type()
|
||||||
|
var kind reflect.Kind = t.Kind()
|
||||||
|
var valOpts *interpolateOpts = new(interpolateOpts)
|
||||||
|
|
||||||
|
if kind != reflect.Map {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsNil() || v.IsZero() || !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*valOpts = defaultInterpolateOpts
|
||||||
|
if opts != nil && len(opts) > 0 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagOpts != nil && len(tagOpts) > 0 {
|
||||||
|
for _, opt := range tagOpts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if valOpts.noMapKey && valOpts.noMapVal {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numJobs = v.Len()
|
||||||
|
errChan = make(chan error, numJobs)
|
||||||
|
wg.Add(numJobs)
|
||||||
|
|
||||||
|
newMap = reflect.MakeMap(reflect.TypeOf(v.Interface()))
|
||||||
|
|
||||||
|
for _, e := range v.MapKeys() {
|
||||||
|
kVal = e
|
||||||
|
|
||||||
|
go func(mapK reflect.Value) {
|
||||||
|
var mapErr error
|
||||||
|
var newKey reflect.Value
|
||||||
|
var newVal reflect.Value
|
||||||
|
var vVal reflect.Value = v.MapIndex(mapK)
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if !valOpts.noMapKey {
|
||||||
|
newKey = reflect.New(reflect.TypeOf(mapK.Interface()))
|
||||||
|
newKey.Set(vVal)
|
||||||
|
if mapK.Kind() == reflect.String {
|
||||||
|
if mapErr = interpolateStringReflect(newKey, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mapErr = interpolateValue(newKey, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newKey = mapK
|
||||||
|
}
|
||||||
|
if !valOpts.noMapVal {
|
||||||
|
newVal = reflect.New(vVal.Type())
|
||||||
|
newVal.Set(vVal)
|
||||||
|
if vVal.Kind() == reflect.String {
|
||||||
|
if mapErr = interpolateStringReflect(newVal, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mapErr = interpolateValue(newVal, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newVal = vVal
|
||||||
|
}
|
||||||
|
newMap.SetMapIndex(reflect.ValueOf(newKey), reflect.ValueOf(newVal))
|
||||||
|
}(kVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
doneChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-doneChan
|
||||||
|
|
||||||
|
for i := 0; i < numJobs; i++ {
|
||||||
|
if err = <-errChan; err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set(newMap)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateSlice is used by Interpolate and interpolateReflect for slices. v should be a reflect.Value of a slice.
|
||||||
|
func interpolateSlice(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var errChan chan error
|
||||||
|
var numJobs int
|
||||||
|
var doneChan chan bool = make(chan bool, 1)
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
var t reflect.Type = v.Type()
|
||||||
|
var kind reflect.Kind = t.Kind()
|
||||||
|
var valOpts *interpolateOpts = new(interpolateOpts)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind != reflect.Slice {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsNil() || v.IsZero() || !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*valOpts = defaultInterpolateOpts
|
||||||
|
if opts != nil && len(opts) > 0 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numJobs = v.Len()
|
||||||
|
errChan = make(chan error, numJobs)
|
||||||
|
wg.Add(numJobs)
|
||||||
|
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
go func(idx int) {
|
||||||
|
var sErr error
|
||||||
|
var newVal reflect.Value
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
newVal = reflect.New(v.Index(idx).Type())
|
||||||
|
newVal.Set(v.Index(idx))
|
||||||
|
if v.Index(idx).Kind() == reflect.String {
|
||||||
|
if sErr = interpolateStringReflect(newVal, opts, tagOpts); sErr != nil {
|
||||||
|
errChan <- sErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if sErr = interpolateValue(newVal, opts, tagOpts); sErr != nil {
|
||||||
|
errChan <- sErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Index(idx).Set(reflect.ValueOf(newVal))
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
doneChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-doneChan
|
||||||
|
|
||||||
|
for i := 0; i < numJobs; i++ {
|
||||||
|
if err = <-errChan; err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateStringReflect is used for structs/nested strings using reflection.
|
||||||
|
func interpolateStringReflect(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var strVal string
|
||||||
|
|
||||||
|
if strVal, err = interpolateString(v.String()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set(reflect.ValueOf(strVal).Convert(v.Type()))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateStruct is used by Interpolate and interpolateReflect for structs. v should be a reflect.Value of a struct.
|
||||||
|
func interpolateStruct(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var field reflect.StructField
|
||||||
|
var fieldVal reflect.Value
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var errChan chan error
|
||||||
|
var numJobs int
|
||||||
|
var doneChan chan bool = make(chan bool, 1)
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
var t reflect.Type = v.Type()
|
||||||
|
var kind reflect.Kind = t.Kind()
|
||||||
|
|
||||||
|
if kind != reflect.Struct {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numJobs = v.NumField()
|
||||||
|
wg.Add(numJobs)
|
||||||
|
errChan = make(chan error, numJobs)
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field = t.Field(i)
|
||||||
|
fieldVal = v.Field(i)
|
||||||
|
|
||||||
|
go func(f reflect.StructField, fv reflect.Value) {
|
||||||
|
var fErr error
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if fErr = interpolateStructField(f, fv, opts, nil); fErr != nil {
|
||||||
|
errChan <- fErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(field, fieldVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
doneChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-doneChan
|
||||||
|
|
||||||
|
for i := 0; i < numJobs; i++ {
|
||||||
|
if err = <-errChan; err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateStructField interpolates a struct field.
|
||||||
|
func interpolateStructField(field reflect.StructField, v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var tagVal string
|
||||||
|
// var ftKind reflect.Kind = field.Type.Kind()
|
||||||
|
var parsedTagOpts map[string]bool
|
||||||
|
var valOpts *interpolateOpts = new(interpolateOpts)
|
||||||
|
|
||||||
|
if !v.CanSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*valOpts = defaultInterpolateOpts
|
||||||
|
|
||||||
|
// Skip if explicitly instructed to do so.
|
||||||
|
tagVal = field.Tag.Get(StructTagInterpolate)
|
||||||
|
parsedTagOpts = internal.StringToMapBool(tagVal)
|
||||||
|
if parsedTagOpts["-"] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil && len(opts) > 0 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
err = interpolateStructField(field, v.Elem(), opts, tagOpts)
|
||||||
|
} else {
|
||||||
|
err = interpolateValue(v, opts, tagOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateValue is a dispatcher for a reflect value.
|
||||||
|
func interpolateValue(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var kind reflect.Kind = v.Kind()
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() || v.IsZero() || !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
if err = interpolateValue(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if err = interpolateStringReflect(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Slice:
|
||||||
|
if err = interpolateSlice(v, opts, tagOpts); err != nil {
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if err = interpolateMap(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if err = interpolateStruct(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
825
envs/funcs.go.old
Normal file
825
envs/funcs.go.old
Normal file
@ -0,0 +1,825 @@
|
|||||||
|
package envs
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
`errors`
|
||||||
|
`fmt`
|
||||||
|
`io/ioutil`
|
||||||
|
`os`
|
||||||
|
`reflect`
|
||||||
|
`strings`
|
||||||
|
`sync`
|
||||||
|
|
||||||
|
`r00t2.io/goutils/multierr`
|
||||||
|
`r00t2.io/sysutils/errs`
|
||||||
|
`r00t2.io/sysutils/internal`
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetEnvMap returns a map of all environment variables. All values are strings.
|
||||||
|
func GetEnvMap() (envVars map[string]string) {
|
||||||
|
|
||||||
|
var envList []string = os.Environ()
|
||||||
|
|
||||||
|
envVars = envListToMap(envList)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
|
||||||
|
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).
|
||||||
|
*/
|
||||||
|
func GetEnvMapNative() (envMap map[string]interface{}) {
|
||||||
|
|
||||||
|
var stringMap map[string]string = GetEnvMap()
|
||||||
|
|
||||||
|
envMap = nativizeEnvMap(stringMap)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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:
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
It is a thin wrapper around GetFirstWithRef.
|
||||||
|
*/
|
||||||
|
func GetFirst(varNames []string) (val string, ok bool) {
|
||||||
|
|
||||||
|
val, ok, _ = GetFirstWithRef(varNames)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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"})
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) {
|
||||||
|
|
||||||
|
idx = -1
|
||||||
|
|
||||||
|
for i, vn := range varNames {
|
||||||
|
if HasEnv(vn) {
|
||||||
|
ok = true
|
||||||
|
idx = i
|
||||||
|
val = os.Getenv(vn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
|
||||||
|
|
||||||
|
var envBytes []byte
|
||||||
|
var envList []string
|
||||||
|
var envArr [][]byte
|
||||||
|
var procPath string
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
envMap = make(map[string]string, 0)
|
||||||
|
|
||||||
|
procPath = fmt.Sprintf("/proc/%v/environ", pid)
|
||||||
|
|
||||||
|
if exists, err = paths.RealPathExists(&procPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
err = errors.New(fmt.Sprintf("information for pid %v does not exist", pid))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if envBytes, err = ioutil.ReadFile(procPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
envArr = bytes.Split(envBytes, []byte{0x0})
|
||||||
|
envList = make([]string, len(envArr))
|
||||||
|
for idx, b := range envArr {
|
||||||
|
envList[idx] = string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
envMap = envListToMap(envList)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) {
|
||||||
|
|
||||||
|
var stringMap map[string]string
|
||||||
|
|
||||||
|
if stringMap, err = GetPidEnvMap(pid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
envMap = nativizeEnvMap(stringMap)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
func HasEnv(key string) (envIsSet bool) {
|
||||||
|
|
||||||
|
_, envIsSet = os.LookupEnv(key)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Interpolate takes one of:
|
||||||
|
|
||||||
|
- a string (pointer only)
|
||||||
|
- a struct (pointer only)
|
||||||
|
- a map (comprised of the same requirements)
|
||||||
|
- a slice (comprised of the same requirements)
|
||||||
|
|
||||||
|
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:
|
||||||
|
<tag>:"<VAR NAME>[,<option>,<option>...]"
|
||||||
|
e.g.
|
||||||
|
envpop:"SOMEVAR"
|
||||||
|
envpop:"OTHERVAR,force"
|
||||||
|
envpop:"OTHERVAR,allow_empty"
|
||||||
|
envpop:"OTHERVAR,force,allow_empty"
|
||||||
|
|
||||||
|
If the tag value is "-", or <VAR NAME> is not provided, the field will be explicitly skipped.
|
||||||
|
(This is the default behavior for struct fields not tagged with `envpop`.)
|
||||||
|
|
||||||
|
Recognized options:
|
||||||
|
|
||||||
|
* force - Existing field values that are non-empty strings or non-nil pointers are normally skipped; this option always replaces them.
|
||||||
|
* allow_empty - Normally no replacement will be performed if the specified variable is undefined/not found.
|
||||||
|
This option allows an empty string to be used instead.
|
||||||
|
Not very useful for string fields, but potentially useful for string pointer fields.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
struct{
|
||||||
|
// If this is an empty string, it will be replaced with the value of $CWD.
|
||||||
|
CurrentDir string `envpop:"CWD"`
|
||||||
|
// This would only populate with $USER if the pointer is nil.
|
||||||
|
UserName *string `envpop:"USER"`
|
||||||
|
// This will *always* replace the field's value with the value of $DISPLAY,
|
||||||
|
// even if not an empty string.
|
||||||
|
// Note the `force` option.
|
||||||
|
Display string `envpop:"DISPLAY,force"`
|
||||||
|
// Likewise, even if not nil, this field's value would be replaced with the value of $SHELL.
|
||||||
|
Shell *string `envpop:"SHELL,force"`
|
||||||
|
// This field will be untouched if non-nil, otherwise it will be a pointer to an empty string
|
||||||
|
// if FOOBAR is undefined.
|
||||||
|
NonExistentVar *string `envpop:"FOOBAR,allow_empty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
If s is nil, nothing will be done and err will be errs.ErrNilPtr.
|
||||||
|
If s is not a pointer to a struct, nothing will be done and err will be errs.ErrBadType.
|
||||||
|
*/
|
||||||
|
func PopulateStruct[T any](s T) (err error) {
|
||||||
|
|
||||||
|
var structVal reflect.Value
|
||||||
|
var structType reflect.Type
|
||||||
|
var field reflect.StructField
|
||||||
|
var fieldVal reflect.Value
|
||||||
|
var tagVal string
|
||||||
|
var valSplit []string
|
||||||
|
var varNm string
|
||||||
|
var varVal string
|
||||||
|
var optsMap map[string]bool
|
||||||
|
var force bool
|
||||||
|
var allowEmpty bool
|
||||||
|
var defined bool
|
||||||
|
|
||||||
|
if reflect.TypeOf(s).Kind() != reflect.Ptr {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
structVal = reflect.ValueOf(s)
|
||||||
|
if structVal.IsNil() || structVal.IsZero() || !structVal.IsValid() {
|
||||||
|
err = errs.ErrNilPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
structVal = reflect.ValueOf(s).Elem()
|
||||||
|
structType = structVal.Type()
|
||||||
|
|
||||||
|
if structType.Kind() != reflect.Struct {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < structVal.NumField(); i++ {
|
||||||
|
field = structType.Field(i)
|
||||||
|
fieldVal = structVal.Field(i)
|
||||||
|
|
||||||
|
// Skip explicitly skipped or non-tagged fields.
|
||||||
|
tagVal = field.Tag.Get(StructTagPopulate)
|
||||||
|
if tagVal == "" || strings.TrimSpace(tagVal) == "-" || strings.HasPrefix(tagVal, "-,") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldVal = structVal.Field(i)
|
||||||
|
if fieldVal.Kind() != reflect.Ptr && fieldVal.Kind() != reflect.String {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
optsMap = make(map[string]bool)
|
||||||
|
valSplit = strings.Split(tagVal, ",")
|
||||||
|
if valSplit == nil || len(valSplit) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
varNm = valSplit[0]
|
||||||
|
if strings.TrimSpace(varNm) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(valSplit) >= 2 {
|
||||||
|
for _, o := range valSplit[1:] {
|
||||||
|
optsMap[o] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
force = optsMap["force"]
|
||||||
|
allowEmpty = optsMap["allow_empty"]
|
||||||
|
|
||||||
|
// if !force && (!fieldVal.IsNil() && !fieldVal.IsZero()) {
|
||||||
|
if !force && !fieldVal.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldVal.Kind() == reflect.Ptr {
|
||||||
|
if field.Type.Elem().Kind() != reflect.String {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fieldVal.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
varVal, defined = os.LookupEnv(varNm)
|
||||||
|
if !defined && !allowEmpty {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fieldVal.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
fieldVal.Set(reflect.ValueOf(&varVal))
|
||||||
|
case reflect.String:
|
||||||
|
fieldVal.SetString(varVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateMap is used by Interpolate and interpolateReflect for maps. v should be a reflect.Value of a map.
|
||||||
|
func interpolateMap(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var kVal reflect.Value
|
||||||
|
var vVal reflect.Value
|
||||||
|
var mIter *reflect.MapIter
|
||||||
|
var newMap reflect.Value
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var numJobs int
|
||||||
|
var errChan chan error
|
||||||
|
var doneChan chan bool = make(chan bool, 1)
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
var t reflect.Type = v.Type()
|
||||||
|
var kind reflect.Kind = t.Kind()
|
||||||
|
var valOpts *interpolateOpts = new(interpolateOpts)
|
||||||
|
|
||||||
|
if kind != reflect.Map {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsNil() || v.IsZero() || !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*valOpts = defaultInterpolateOpts
|
||||||
|
if opts != nil && len(opts) > 0 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagOpts != nil && len(tagOpts) > 0 {
|
||||||
|
for _, opt := range tagOpts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if valOpts.noMapKey && valOpts.noMapVal {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numJobs = v.Len()
|
||||||
|
errChan = make(chan error, numJobs)
|
||||||
|
wg.Add(numJobs)
|
||||||
|
|
||||||
|
newMap = reflect.MakeMap(reflect.TypeOf(v.Interface()))
|
||||||
|
|
||||||
|
mIter = v.MapRange()
|
||||||
|
for mIter.Next() {
|
||||||
|
kVal = mIter.Key()
|
||||||
|
vVal = mIter.Value()
|
||||||
|
go func(mapK, mapV reflect.Value) {
|
||||||
|
var mapErr error
|
||||||
|
var newKey reflect.Value
|
||||||
|
var newVal reflect.Value
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if !valOpts.noMapKey {
|
||||||
|
/*
|
||||||
|
if mapK.Kind() == reflect.String {
|
||||||
|
if mapErr = interpolateStringReflect(mapK, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mapErr = interpolateValue(mapK, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
newKey = reflect.New(mapK.Type())
|
||||||
|
newKey.Set(mapK)
|
||||||
|
if mapK.Kind() == reflect.String {
|
||||||
|
if mapErr = interpolateStringReflect(newKey, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mapErr = interpolateValue(newKey, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newKey = mapK
|
||||||
|
}
|
||||||
|
if !valOpts.noMapVal {
|
||||||
|
/*
|
||||||
|
if mapV.Kind() == reflect.String {
|
||||||
|
if mapErr = interpolateStringReflect(mapV, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mapErr = interpolateValue(mapV, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
newVal = reflect.New(mapV.Type())
|
||||||
|
newVal.Set(mapV)
|
||||||
|
if mapV.Kind() == reflect.String {
|
||||||
|
if mapErr = interpolateStringReflect(newVal, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mapErr = interpolateValue(newVal, opts, nil); mapErr != nil {
|
||||||
|
errChan <- mapErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newVal = mapV
|
||||||
|
}
|
||||||
|
newMap.SetMapIndex(reflect.ValueOf(newKey), reflect.ValueOf(newVal))
|
||||||
|
}(kVal, vVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
doneChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-doneChan
|
||||||
|
|
||||||
|
for i := 0; i < numJobs; i++ {
|
||||||
|
if err = <-errChan; err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set(newMap)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateSlice is used by Interpolate and interpolateReflect for slices. v should be a reflect.Value of a slice.
|
||||||
|
func interpolateSlice(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var errChan chan error
|
||||||
|
var numJobs int
|
||||||
|
var doneChan chan bool = make(chan bool, 1)
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
var t reflect.Type = v.Type()
|
||||||
|
var kind reflect.Kind = t.Kind()
|
||||||
|
var valOpts *interpolateOpts = new(interpolateOpts)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind != reflect.Slice {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsNil() || v.IsZero() || !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*valOpts = defaultInterpolateOpts
|
||||||
|
if opts != nil && len(opts) > 0 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numJobs = v.Len()
|
||||||
|
errChan = make(chan error, numJobs)
|
||||||
|
wg.Add(numJobs)
|
||||||
|
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
go func(idx int) {
|
||||||
|
var sErr error
|
||||||
|
var newVal reflect.Value
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
newVal = reflect.New(v.Index(idx).Type())
|
||||||
|
newVal.Set(v.Index(idx))
|
||||||
|
if v.Index(idx).Kind() == reflect.String {
|
||||||
|
if sErr = interpolateStringReflect(newVal, opts, tagOpts); sErr != nil {
|
||||||
|
errChan <- sErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if sErr = interpolateValue(newVal, opts, tagOpts); sErr != nil {
|
||||||
|
errChan <- sErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Index(idx).Set(reflect.ValueOf(newVal))
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
doneChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-doneChan
|
||||||
|
|
||||||
|
for i := 0; i < numJobs; i++ {
|
||||||
|
if err = <-errChan; err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateStringReflect is used for structs/nested strings using reflection.
|
||||||
|
func interpolateStringReflect(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var strVal string
|
||||||
|
|
||||||
|
if strVal, err = interpolateString(v.String()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set(reflect.ValueOf(strVal).Convert(v.Type()))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateStruct is used by Interpolate and interpolateReflect for structs. v should be a reflect.Value of a struct.
|
||||||
|
func interpolateStruct(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var field reflect.StructField
|
||||||
|
var fieldVal reflect.Value
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var errChan chan error
|
||||||
|
var numJobs int
|
||||||
|
var doneChan chan bool = make(chan bool, 1)
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
var t reflect.Type = v.Type()
|
||||||
|
var kind reflect.Kind = t.Kind()
|
||||||
|
|
||||||
|
if kind != reflect.Struct {
|
||||||
|
err = errs.ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numJobs = v.NumField()
|
||||||
|
wg.Add(numJobs)
|
||||||
|
errChan = make(chan error, numJobs)
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field = t.Field(i)
|
||||||
|
fieldVal = v.Field(i)
|
||||||
|
|
||||||
|
go func(f reflect.StructField, fv reflect.Value) {
|
||||||
|
var fErr error
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if fErr = interpolateStructField(f, fv, opts, nil); fErr != nil {
|
||||||
|
errChan <- fErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(field, fieldVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
doneChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-doneChan
|
||||||
|
|
||||||
|
for i := 0; i < numJobs; i++ {
|
||||||
|
if err = <-errChan; err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateStructField interpolates a struct field.
|
||||||
|
func interpolateStructField(field reflect.StructField, v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var tagVal string
|
||||||
|
// var ftKind reflect.Kind = field.Type.Kind()
|
||||||
|
var parsedTagOpts map[string]bool
|
||||||
|
var valOpts *interpolateOpts = new(interpolateOpts)
|
||||||
|
|
||||||
|
if !v.CanSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*valOpts = defaultInterpolateOpts
|
||||||
|
|
||||||
|
// Skip if explicitly instructed to do so.
|
||||||
|
tagVal = field.Tag.Get(StructTagInterpolate)
|
||||||
|
parsedTagOpts = internal.StringToMapBool(tagVal)
|
||||||
|
if parsedTagOpts["-"] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil && len(opts) > 0 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(valOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
err = interpolateStructField(field, v.Elem(), opts, tagOpts)
|
||||||
|
} else {
|
||||||
|
err = interpolateValue(v, opts, tagOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateValue is a dispatcher for a reflect value.
|
||||||
|
func interpolateValue(v reflect.Value, opts []optInterpolate, tagOpts []optInterpolate) (err error) {
|
||||||
|
|
||||||
|
var kind reflect.Kind = v.Kind()
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() || v.IsZero() || !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
if err = interpolateValue(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if err = interpolateStringReflect(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Slice:
|
||||||
|
if err = interpolateSlice(v, opts, tagOpts); err != nil {
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if err = interpolateMap(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if err = interpolateStruct(v, opts, tagOpts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
182
envs/funcs_test.go
Normal file
182
envs/funcs_test.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package envs
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
`os`
|
||||||
|
`testing`
|
||||||
|
`time`
|
||||||
|
|
||||||
|
`github.com/davecgh/go-spew/spew`
|
||||||
|
`r00t2.io/sysutils/errs`
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
testCustom string
|
||||||
|
testStruct struct {
|
||||||
|
Hello string `envpop:"USER"`
|
||||||
|
HelloPtr *string `envpop:"USER"`
|
||||||
|
HelloForce string `envpop:"USER,force"`
|
||||||
|
HelloPtrForce *string `envpop:"USER,force"`
|
||||||
|
HelloNo string `envsub:"-" envpop:"-"`
|
||||||
|
HelloNoPtr *string `envsub:"-" envpop:"-"`
|
||||||
|
BadType int
|
||||||
|
NilField *string `envpop:"NONEXISTENTBADVAR,allow_empty"`
|
||||||
|
PtrInt *int
|
||||||
|
Custom testCustom
|
||||||
|
MapStr map[string]string
|
||||||
|
SliceStr []string
|
||||||
|
SliceSlice [][]string
|
||||||
|
SliceMap []map[string]string
|
||||||
|
SliceStruct []testStruct
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterpolateString(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var s string = "My username is ${USER}; hello!"
|
||||||
|
var sp *string = &s
|
||||||
|
|
||||||
|
if err = InterpolateString(&s); err != nil {
|
||||||
|
t.Fatalf("Failed interpolation: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("String test passed:\n%v", s)
|
||||||
|
|
||||||
|
if err = InterpolateString(sp); err != nil {
|
||||||
|
t.Fatalf("Failed interpolation: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("String pointer test passed:\n%v", *sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolateStruct(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, i := range []interface{}{
|
||||||
|
"i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
new(string),
|
||||||
|
/*
|
||||||
|
testStruct{
|
||||||
|
Hello: "i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtr: new(string),
|
||||||
|
HelloForce: "i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtrForce: new(string),
|
||||||
|
HelloNo: "i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloNoPtr: new(string),
|
||||||
|
BadType: 4,
|
||||||
|
NilField: nil,
|
||||||
|
PtrInt: new(int),
|
||||||
|
Custom: testCustom("i am ${USER}, it is ${CURDATETIME}"),
|
||||||
|
SliceStr: []string{"i am ${USER}, it is ${CURDATETIME}"},
|
||||||
|
SliceSlice: [][]string{[]string{"i am ${USER}, it is ${CURDATETIME}"}},
|
||||||
|
SliceMap: []map[string]string{map[string]string{"i am ${USER} key": "i am ${USER} value, it is ${CURDATETIME}"}},
|
||||||
|
SliceStruct: []testStruct{
|
||||||
|
{
|
||||||
|
Hello: "i am nested ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtr: nil,
|
||||||
|
HelloForce: "i am nested ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtrForce: nil,
|
||||||
|
HelloNo: "i am nested ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloNoPtr: nil,
|
||||||
|
BadType: 0,
|
||||||
|
NilField: nil,
|
||||||
|
PtrInt: nil,
|
||||||
|
Custom: testCustom("i am nested ${USER}, it is ${CURDATETIME}"),
|
||||||
|
SliceStr: []string{"i am nested ${USER}, it is ${CURDATETIME}"},
|
||||||
|
SliceSlice: [][]string{[]string{"i am nested ${USER}, it is ${CURDATETIME}"}},
|
||||||
|
SliceMap: []map[string]string{map[string]string{"i am nested ${USER} key": "i am ${USER} value, it is ${CURDATETIME}"}},
|
||||||
|
SliceStruct: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
&testStruct{
|
||||||
|
Hello: "i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtr: new(string),
|
||||||
|
HelloForce: "i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtrForce: new(string),
|
||||||
|
HelloNo: "i am ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloNoPtr: new(string),
|
||||||
|
BadType: 4,
|
||||||
|
NilField: nil,
|
||||||
|
PtrInt: new(int),
|
||||||
|
Custom: testCustom("i am ${USER}, it is ${CURDATETIME}"),
|
||||||
|
MapStr: map[string]string{"i am ${USER} key": "i am ${USER} value, it is ${CURDATETIME}"},
|
||||||
|
SliceStr: []string{"i am ${USER}, it is ${CURDATETIME}"},
|
||||||
|
SliceSlice: [][]string{[]string{"i am ${USER}, it is ${CURDATETIME}"}},
|
||||||
|
SliceMap: []map[string]string{map[string]string{"i am ${USER} key": "i am ${USER} value, it is ${CURDATETIME}"}},
|
||||||
|
SliceStruct: []testStruct{
|
||||||
|
{
|
||||||
|
Hello: "i am nested ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtr: nil,
|
||||||
|
HelloForce: "i am nested ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloPtrForce: nil,
|
||||||
|
HelloNo: "i am nested ${USER}, it is ${CURDATETIME}",
|
||||||
|
HelloNoPtr: nil,
|
||||||
|
BadType: 0,
|
||||||
|
NilField: nil,
|
||||||
|
PtrInt: nil,
|
||||||
|
Custom: testCustom("i am nested ${USER}, it is ${CURDATETIME}"),
|
||||||
|
SliceStr: []string{"i am nested ${USER}, it is ${CURDATETIME}"},
|
||||||
|
SliceSlice: [][]string{[]string{"i am nested ${USER}, it is ${CURDATETIME}"}},
|
||||||
|
SliceMap: []map[string]string{map[string]string{"i am nested ${USER} key": "i am ${USER} value, it is ${CURDATETIME}"}},
|
||||||
|
SliceStruct: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if err = os.Setenv("CURDATETIME", time.Now().String()); err != nil {
|
||||||
|
t.Fatalf("Received error setting CURDATETIME: %v", err)
|
||||||
|
}
|
||||||
|
switch x := i.(type) {
|
||||||
|
case *string:
|
||||||
|
*x = "i am ${USER}, it is ${CURDATETIME}"
|
||||||
|
case testStruct:
|
||||||
|
*x.HelloPtr = x.Hello
|
||||||
|
*x.HelloPtrForce = x.HelloForce
|
||||||
|
*x.HelloNoPtr = x.HelloNo
|
||||||
|
*x.PtrInt = x.BadType
|
||||||
|
case *testStruct:
|
||||||
|
*x.HelloPtr = x.Hello
|
||||||
|
*x.HelloPtrForce = x.HelloForce
|
||||||
|
*x.HelloNoPtr = x.HelloNo
|
||||||
|
*x.PtrInt = x.BadType
|
||||||
|
}
|
||||||
|
t.Logf("Before (%T):\n%v", i, spew.Sdump(i))
|
||||||
|
if err = Interpolate(i); err != nil {
|
||||||
|
t.Fatalf("Failed interpolation: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("After (%T):\n%v\n", i, spew.Sdump(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPopulateStruct(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var greet string = "My username is ${USER}; hello!"
|
||||||
|
var num int = 4
|
||||||
|
var sp *testStruct = &testStruct{
|
||||||
|
Hello: greet,
|
||||||
|
HelloPtr: &greet,
|
||||||
|
HelloForce: greet,
|
||||||
|
HelloPtrForce: &greet,
|
||||||
|
HelloNo: greet,
|
||||||
|
HelloNoPtr: &greet,
|
||||||
|
BadType: 4,
|
||||||
|
PtrInt: &num,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = PopulateStruct(sp); err != nil {
|
||||||
|
if errors.Is(err, errs.ErrNilPtr) {
|
||||||
|
err = nil
|
||||||
|
t.Logf("Detected nil.")
|
||||||
|
} else if errors.Is(err, errs.ErrBadType) {
|
||||||
|
err = nil
|
||||||
|
t.Log("Detected bad type.")
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Failed interpolation: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("Evaluated:\n%v", spew.Sdump(sp))
|
||||||
|
|
||||||
|
}
|
10
envs/types.go
Normal file
10
envs/types.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package envs
|
||||||
|
|
||||||
|
type (
|
||||||
|
interpolateOpts struct {
|
||||||
|
noMapKey bool
|
||||||
|
noMapVal bool
|
||||||
|
isTagged bool
|
||||||
|
}
|
||||||
|
optInterpolate func(o *interpolateOpts) (err error)
|
||||||
|
)
|
18
envs/utils_nonwindows.go
Normal file
18
envs/utils_nonwindows.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package envs
|
||||||
|
|
||||||
|
import (
|
||||||
|
`os`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
interpolateString takes string s and performs environment variable interpolation/substitution on it.
|
||||||
|
err will always be nil; it's here for compat with the Windows equivalent.
|
||||||
|
*/
|
||||||
|
func interpolateString(s string) (subbed string, err error) {
|
||||||
|
|
||||||
|
subbed = os.ExpandEnv(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
15
envs/utils_windows.go
Normal file
15
envs/utils_windows.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package envs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// interpolateString takes string s and performs environment variable interpolation/substitution on it.
|
||||||
|
func interpolateString(s string) (subbed string, err error) {
|
||||||
|
|
||||||
|
subbed, err = registry.ExpandString(os.ExpandEnv(s))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
10
errs/errs.go
Normal file
10
errs/errs.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadType error = errors.New("a bad type was passed")
|
||||||
|
ErrNilPtr error = errors.New("a nil pointer was passed")
|
||||||
|
)
|
34
exec_extra/consts.go
Normal file
34
exec_extra/consts.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package exec_extra
|
||||||
|
|
||||||
|
var (
|
||||||
|
StructTagCmdArgs string = "cmdarg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
/*
|
||||||
|
CmdArgsOptPreferShort, if specified, prefers the "short" argument over "long" if both are specified.
|
||||||
|
The default is to prefer long.
|
||||||
|
|
||||||
|
Can be specified per-field via the `prefer_short` option (no value/value ignored).
|
||||||
|
*/
|
||||||
|
CmdArgsOptPreferShort cmdArgOpt = func(opts *cmdArgsOpts) (err error) {
|
||||||
|
opts.preferShort = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CmdArgsOptShortEquals, if specified, renders short flags *with* an equals sign
|
||||||
|
(if using POSIX args).
|
||||||
|
|
||||||
|
Has no effect if using Windows traditional syntax or if there is no value for the field.
|
||||||
|
*/
|
||||||
|
CmdArgsOptShortEquals cmdArgOpt = func(opts *cmdArgsOpts) (err error) {
|
||||||
|
opts.preferShort = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
CmdArgsOptLongNoEquals cmdArgOpt = func(opts *cmdArgsOpts) (err error) {
|
||||||
|
opts.preferShort = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
)
|
45
exec_extra/consts_windows.go
Normal file
45
exec_extra/consts_windows.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package exec_extra
|
||||||
|
|
||||||
|
var (
|
||||||
|
/*
|
||||||
|
CmdArgsOptForcePosix forces the resulting command string to use "POSIX-style" flag notation.
|
||||||
|
|
||||||
|
Traditionally, Windows used flags like `/f` instead of POSIX `-f`, `/c:value` instead of `-c value`
|
||||||
|
or `-c=value`, etc.
|
||||||
|
|
||||||
|
If this option is passed, either to GetCmdFromStruct() or for a specific field via the
|
||||||
|
tag defined by StructTagCmdArgs (option `force_posix`, no value/value ignored), then the
|
||||||
|
POSIX-style flag syntax will be used instead.
|
||||||
|
|
||||||
|
Note that on Windows runtime, the default is to use the traditional slash-based syntax.
|
||||||
|
If you are generating command strings for Powershell or third-party software, you probably
|
||||||
|
want to use this option.
|
||||||
|
|
||||||
|
See also the inverse of this option, CmdArgsOptForceNoPosix.
|
||||||
|
*/
|
||||||
|
CmdArgsOptForcePosix cmdArgOpt = func(opts *cmdArgsOpts) (err error) {
|
||||||
|
opts.forcePosix = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CmdArgsOptForceNoPosix forces the resulting command string to use "traditional Windows" flag notation.
|
||||||
|
|
||||||
|
Traditionally, Windows used flags like `/f` instead of POSIX `-f`, `/c:value` instead of `-c value`
|
||||||
|
or `-c=value`, etc.
|
||||||
|
|
||||||
|
If this option is passed, either to GetCmdFromStruct() or for a specific field via the
|
||||||
|
tag defined by StructTagCmdArgs (option `force_no_posix`, no value/value ignored), then the
|
||||||
|
Windows-style flag syntax will be used instead.
|
||||||
|
|
||||||
|
Note that on Windows runtime, the default is to use the traditional slash-based syntax.
|
||||||
|
If you are generating command strings for Powershell or third-party software, you probably
|
||||||
|
want to use CmdArgsOptForcePosix instead.
|
||||||
|
|
||||||
|
See also the inverse of this option, CmdArgsOptForcePosix.
|
||||||
|
*/
|
||||||
|
CmdArgsOptForceNoPosix cmdArgOpt = func(opts *cmdArgsOpts) (err error) {
|
||||||
|
opts.forcePosix = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
)
|
@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
SysUtils - a library to assist with various system-related functions
|
|
||||||
Copyright (C) 2020 Brent Saner
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package exec_extra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExecCmdReturn(cmd *exec.Cmd) (exitStatus int, err error) {
|
|
||||||
// https://stackoverflow.com/a/55055100/733214
|
|
||||||
err = cmd.Run()
|
|
||||||
exitErr, _ := err.(*exec.ExitError)
|
|
||||||
exitStatus = exitErr.ExitCode()
|
|
||||||
return
|
|
||||||
}
|
|
86
exec_extra/funcs.go
Normal file
86
exec_extra/funcs.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
SysUtils - a library to assist with various system-related functions
|
||||||
|
Copyright (C) 2020 Brent Saner
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package exec_extra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExecCmdReturn(cmd *exec.Cmd) (exitStatus int, err error) {
|
||||||
|
// https://stackoverflow.com/a/55055100/733214
|
||||||
|
err = cmd.Run()
|
||||||
|
exitErr, _ := err.(*exec.ExitError)
|
||||||
|
exitStatus = exitErr.ExitCode()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetCmdFromStruct takes (a pointer to) a struct and returns a slice of
|
||||||
|
strings compatible with os/exec.Cmd.
|
||||||
|
|
||||||
|
The tag name used can be changed by setting the StructTagCmdArgs variable in this module;
|
||||||
|
the default is `cmdarg`.
|
||||||
|
|
||||||
|
If the tag value is "-", the field will be skipped. Any other tag value(s) are ignored.
|
||||||
|
|
||||||
|
Tag value format:
|
||||||
|
<tag>:"<option>=<value>[,<option>[=<value>],<option>[=<value>]...]"
|
||||||
|
e.g.
|
||||||
|
cmdarg:"short=l,long=list"
|
||||||
|
cmdarg:"short=l"
|
||||||
|
cmdarg:"long=list"
|
||||||
|
|
||||||
|
If the tag value is "-", or <VAR NAME> is not provided, the field will be explicitly skipped.
|
||||||
|
(This is the default behavior for struct fields not tagged with `cmdarg`.)
|
||||||
|
|
||||||
|
If a cmdarg tag is specified but has no `short` or `long` option value, the field will be skipped entirely.
|
||||||
|
If a field's value is nil, it will be skipped.
|
||||||
|
Otherwise if a field's value is the zero-value, it will be skipped.
|
||||||
|
|
||||||
|
Recognized options:
|
||||||
|
|
||||||
|
* short - A short flag for the argument
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
struct{
|
||||||
|
// If this is an empty string, it will be replaced with the value of $CWD.
|
||||||
|
CurrentDir string `envpop:"CWD"`
|
||||||
|
// This would only populate with $USER if the pointer is nil.
|
||||||
|
UserName *string `envpop:"USER"`
|
||||||
|
// This will *always* replace the field's value with the value of $DISPLAY,
|
||||||
|
// even if not an empty string.
|
||||||
|
// Note the `force` option.
|
||||||
|
Display string `envpop:"DISPLAY,force"`
|
||||||
|
// Likewise, even if not nil, this field's value would be replaced with the value of $SHELL.
|
||||||
|
Shell *string `envpop:"SHELL,force"`
|
||||||
|
// This field will be untouched if non-nil, otherwise it will be a pointer to an empty string
|
||||||
|
// if FOOBAR is undefined.
|
||||||
|
NonExistentVar *string `envpop:"FOOBAR,allow_empty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
If s is nil, nothing will be done and err will be errs.ErrNilPtr.
|
||||||
|
If s is not a pointer to a struct, nothing will be done and err will be errs.ErrBadType.
|
||||||
|
*/
|
||||||
|
func GetCmdFromStruct[T any](s T, opts ...cmdArgOpt) (cmdSlice []string, err error) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
9
exec_extra/types.go
Normal file
9
exec_extra/types.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package exec_extra
|
||||||
|
|
||||||
|
type cmdArgsOpts struct {
|
||||||
|
preferShort bool
|
||||||
|
forcePosix bool
|
||||||
|
cmd *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmdArgOpt func(*cmdArgsOpts) (err error)
|
33
exec_extra/utils.go
Normal file
33
exec_extra/utils.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package exec_extra
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
CmdArgsWithBin returns a cmdArgsOpt that specifies program/executable/binary path `bin`,
|
||||||
|
ensuring that the resulting cmdSlice from GetCmdFromStruct() will return a ready-to-use slice.
|
||||||
|
(Otherwise the executable would need to be prepended to the resulting slice.)
|
||||||
|
|
||||||
|
Path normalization/canonziation can be enabled/disabled via normalizePath.
|
||||||
|
*/
|
||||||
|
func CmdArgsWithBin(bin string, normalizePath bool) (opt cmdArgOpt, err error) {
|
||||||
|
|
||||||
|
if normalizePath {
|
||||||
|
if err = paths.RealPath(&bin); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opt = func(opts *cmdArgsOpts) (err error) {
|
||||||
|
/*
|
||||||
|
if opts.cmd == nil {
|
||||||
|
opts.cmd = new(string)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
*opts.cmd = bin
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
7
go.mod
7
go.mod
@ -2,7 +2,12 @@ module r00t2.io/sysutils
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require github.com/g0rbe/go-chattr v1.0.1
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/g0rbe/go-chattr v1.0.1
|
||||||
|
golang.org/x/sys v0.19.0
|
||||||
|
r00t2.io/goutils v1.4.0
|
||||||
|
)
|
||||||
|
|
||||||
// Pending https://github.com/g0rbe/go-chattr/pull/3
|
// Pending https://github.com/g0rbe/go-chattr/pull/3
|
||||||
replace github.com/g0rbe/go-chattr => github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13
|
replace github.com/g0rbe/go-chattr => github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13
|
||||||
|
10
go.sum
10
go.sum
@ -1,2 +1,12 @@
|
|||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13 h1:tgEbuE4bNVjaCWWIB1u9lDzGqH/ZdBTg33+4vNW2rjg=
|
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13 h1:tgEbuE4bNVjaCWWIB1u9lDzGqH/ZdBTg33+4vNW2rjg=
|
||||||
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13/go.mod h1:yQc6VPJfpDDC1g+W2t47+yYmzBNioax/GLiyJ25/IOs=
|
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13/go.mod h1:yQc6VPJfpDDC1g+W2t47+yYmzBNioax/GLiyJ25/IOs=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
r00t2.io/goutils v1.4.0 h1:/x/etLpMFv3+j1aPtT7KK2G0uOk+gQkGvXIYBCdjn3E=
|
||||||
|
r00t2.io/goutils v1.4.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||||
|
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
||||||
|
@ -2,6 +2,7 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
`runtime`
|
`runtime`
|
||||||
|
`strings`
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPathEnvName gets the OS-specific path environment variable name.
|
// GetPathEnvName gets the OS-specific path environment variable name.
|
||||||
@ -16,3 +17,85 @@ func GetPathEnvName() (envVarName string) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
StringToMap takes string s, assumed to be in the form of
|
||||||
|
key=value[,key=value,key=value...]
|
||||||
|
and returns a map[string]string (map[key]value).
|
||||||
|
It is proccessed in order; later duplicate keys overwrite previous ones.
|
||||||
|
|
||||||
|
If s is an empty string or comprised only of whitespace, m will be nil.
|
||||||
|
If only a key is provided with no value, the value in the map will be an empty string.
|
||||||
|
(e.g. "foo,bar=baz" => map[string]string{"foo": "", "bar: "baz"}
|
||||||
|
|
||||||
|
Surrounding whitespace is trimmed.
|
||||||
|
*/
|
||||||
|
func StringToMap(s string) (m map[string]string) {
|
||||||
|
|
||||||
|
var kvSplit []string
|
||||||
|
var valSplit []string
|
||||||
|
var k string
|
||||||
|
var v string
|
||||||
|
|
||||||
|
if strings.TrimSpace(s) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kvSplit = strings.Split(s, ",")
|
||||||
|
if kvSplit == nil || len(kvSplit) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, kv := range kvSplit {
|
||||||
|
valSplit = strings.SplitN(kv, "=", 2)
|
||||||
|
if valSplit == nil || len(valSplit) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k = valSplit[0]
|
||||||
|
switch len(valSplit) {
|
||||||
|
case 1:
|
||||||
|
v = ""
|
||||||
|
case 2:
|
||||||
|
v = valSplit[1]
|
||||||
|
// It's not possible to have more than 2.
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[string]string)
|
||||||
|
}
|
||||||
|
k = strings.TrimSpace(k)
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
StringToMapBool is like StringToMap but designed for a map of booleans.
|
||||||
|
It takes string s, assumed to be in the form of
|
||||||
|
option[,option,option...]
|
||||||
|
and returns a map[string]bool (map[option]true).
|
||||||
|
|
||||||
|
If s is an empty string or comprised only of whitespace, m will be nil.
|
||||||
|
|
||||||
|
Surrounding whitespace is trimmed.
|
||||||
|
*/
|
||||||
|
func StringToMapBool(s string) (m map[string]bool) {
|
||||||
|
|
||||||
|
var optSplit []string
|
||||||
|
|
||||||
|
if strings.TrimSpace(s) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
optSplit = strings.Split(s, ",")
|
||||||
|
if optSplit == nil || len(optSplit) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m = make(map[string]bool)
|
||||||
|
for _, o := range optSplit {
|
||||||
|
o = strings.TrimSpace(o)
|
||||||
|
m[o] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user