diff --git a/logging/TODO b/logging/TODO index ddda8bc..9d64324 100644 --- a/logging/TODO +++ b/logging/TODO @@ -12,3 +12,5 @@ - DOCS. -- Done, but flesh out. + +- Implement io.Writer interfaces diff --git a/structutils/consts.go b/structutils/consts.go new file mode 100644 index 0000000..0968d20 --- /dev/null +++ b/structutils/consts.go @@ -0,0 +1,5 @@ +package structutils + +const ( + TagMapTrim tagMapOpt = iota +) diff --git a/structutils/funcs.go b/structutils/funcs.go new file mode 100644 index 0000000..7b8c128 --- /dev/null +++ b/structutils/funcs.go @@ -0,0 +1,362 @@ +/* + GoUtils - a library to assist with various Golang-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 . +*/ + +package structutils + +import ( + `reflect` + `strings` +) + +/* + TagToBoolMap takes struct field `field` and tag name `tagName`, + optionally with options `opts`, and returns a map of the tag values. + The tag value string is assumed to be in the form of: + option[,option,option...] + and returns a map[string]bool (map[option]true). + + If field does not have tag tagName, m will be nil. + + See the TagMap* constants for opts. +*/ +func TagToBoolMap(field reflect.StructField, tagName string, opts ...tagMapOpt) (m map[string]bool) { + + var s string + var optSplit []string + var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts) + + s = field.Tag.Get(tagName) + + 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 { + if tagOpts[TagMapTrim] { + o = strings.TrimSpace(o) + } + m[o] = true + } + + return +} + +/* + TagToBoolMapWithValue is like TagToBoolMap but additionally assumes the first value is an "identifier". + The tag value string is assumed to be in the form of: + value,option[,option,option...] + and returns a map[string]bool (map[option]true) with the value. +*/ +func TagToBoolMapWithValue(field reflect.StructField, tagName string, opts ...tagMapOpt) (value string, m map[string]bool) { + + var s string + var optSplit []string + var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts) + + s = field.Tag.Get(tagName) + + if strings.TrimSpace(s) == "" { + return + } + + optSplit = strings.Split(s, ",") + if optSplit == nil || len(optSplit) == 0 { + return + } + m = make(map[string]bool) + for idx, o := range optSplit { + if idx == 0 { + if tagOpts[TagMapTrim] { + o = strings.TrimSpace(o) + } + value = o + continue + } + if tagOpts[TagMapTrim] { + o = strings.TrimSpace(o) + } + m[o] = true + } + + return +} + +/* + TagToMixedMap combines TagToBoolMap and TagToStringMap. + It takes struct field `field` and tag name `tagName`, + and returns all single-value options in mapBool, and all key/value options in mapString. + + If field does not have tag tagName, m will be nil. + + See the TagMap* constants for opts. +*/ +func TagToMixedMap(field reflect.StructField, tagName string, opts ...tagMapOpt) (mapBool map[string]bool, mapString map[string]string) { + + var s string + var valStr string + var split []string + var kvSplit []string + var valSplit []string + var k string + var v string + var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts) + + s = field.Tag.Get(tagName) + + if strings.TrimSpace(s) == "" { + return + } + + split = strings.Split(s, ",") + if split == nil || len(split) == 0 { + return + } + mapBool = make(map[string]bool) + mapString = make(map[string]string) + for _, valStr = range split { + if strings.Contains(valStr, "=") { + kvSplit = strings.SplitN(valStr, "=", 2) + if kvSplit == nil || len(kvSplit) == 0 { + continue + } + k = valSplit[0] + switch len(valSplit) { + case 1: + v = "" + case 2: + v = kvSplit[1] + } + if tagOpts[TagMapTrim] { + k = strings.TrimSpace(k) + v = strings.TrimSpace(v) + } + mapString[k] = v + } else { + if tagOpts[TagMapTrim] { + valStr = strings.TrimSpace(valStr) + } + mapBool[valStr] = true + } + } + + return +} + +/* + TagToMixedMapWithValue combines TagToBoolMapWithValue and TagToStringMapWithValue. + It takes struct field `field` and tag name `tagName`, + and returns all single-value options in mapBool, and all key/value options in mapString + along with the first single-value option as value.. + + If field does not have tag tagName, m will be nil. + + See the TagMap* constants for opts. +*/ +func TagToMixedMapWithValue(field reflect.StructField, tagName string, opts ...tagMapOpt) (value string, mapBool map[string]bool, mapString map[string]string) { + + var s string + var idx int + var valStr string + var split []string + var kvSplit []string + var valSplit []string + var k string + var v string + var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts) + + s = field.Tag.Get(tagName) + + if strings.TrimSpace(s) == "" { + return + } + + split = strings.Split(s, ",") + if split == nil || len(split) == 0 { + return + } + mapBool = make(map[string]bool) + mapString = make(map[string]string) + for idx, valStr = range split { + if idx == 0 { + if tagOpts[TagMapTrim] { + valStr = strings.TrimSpace(valStr) + } + value = valStr + continue + } + if strings.Contains(valStr, "=") { + kvSplit = strings.SplitN(valStr, "=", 2) + if kvSplit == nil || len(kvSplit) == 0 { + continue + } + k = valSplit[0] + switch len(valSplit) { + case 1: + v = "" + case 2: + v = kvSplit[1] + } + if tagOpts[TagMapTrim] { + k = strings.TrimSpace(k) + v = strings.TrimSpace(v) + } + mapString[k] = v + } else { + if tagOpts[TagMapTrim] { + valStr = strings.TrimSpace(valStr) + } + mapBool[valStr] = true + } + } + + return +} + +/* + TagToStringMap takes struct field `field` and tag name `tagName`, + optionally with options `opts`, and returns a map of the tag values. + The tag value string is 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 field does not have tag tagName, 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"} + + See the TagMap* constants for opts. +*/ +func TagToStringMap(field reflect.StructField, tagName string, opts ...tagMapOpt) (m map[string]string) { + + var s string + var kvSplit []string + var valSplit []string + var k string + var v string + var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts) + + s = field.Tag.Get(tagName) + + 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) + } + if tagOpts[TagMapTrim] { + k = strings.TrimSpace(k) + v = strings.TrimSpace(v) + } + m[k] = v + } + + return +} + +/* + TagToStringMapWithValue is like TagToStringMap but additionally assumes the first value is an "identifier". + The tag value string is assumed to be in the form of: + value,key=value[,key=value,key=value...] + and returns a map[string]string (map[key]value) with the value. +*/ +func TagToStringMapWithValue(field reflect.StructField, tagName string, opts ...tagMapOpt) (value string, m map[string]string) { + + var s string + var kvSplit []string + var valSplit []string + var k string + var v string + var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts) + + s = field.Tag.Get(tagName) + + if strings.TrimSpace(s) == "" { + return + } + + kvSplit = strings.Split(s, ",") + if kvSplit == nil || len(kvSplit) == 0 { + return + } + for idx, kv := range kvSplit { + if idx == 0 { + if tagOpts[TagMapTrim] { + kv = strings.TrimSpace(kv) + } + value = kv + continue + } + 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) + } + if tagOpts[TagMapTrim] { + k = strings.TrimSpace(k) + v = strings.TrimSpace(v) + } + m[k] = v + } + + return +} + +func getTagMapOpts(opts []tagMapOpt) (optMap map[tagMapOpt]bool) { + + optMap = make(map[tagMapOpt]bool) + + if opts == nil { + return + } + + return +} diff --git a/structutils/types.go b/structutils/types.go new file mode 100644 index 0000000..03ffdd0 --- /dev/null +++ b/structutils/types.go @@ -0,0 +1,5 @@ +package structutils + +type ( + tagMapOpt uint8 +)