Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c543a05e7
|
||
|
|
e5191383a7
|
||
|
|
ae49f42c0c
|
||
|
|
b87934e8a9
|
||
|
70d6c2cbb3
|
@@ -2,8 +2,8 @@ package bitmask
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func (m *MaskBit) AddFlag(flag MaskBit) {
|
|||||||
// ClearFlag removes MaskBit flag from m.
|
// ClearFlag removes MaskBit flag from m.
|
||||||
func (m *MaskBit) ClearFlag(flag MaskBit) {
|
func (m *MaskBit) ClearFlag(flag MaskBit) {
|
||||||
|
|
||||||
*m &= flag
|
*m &^= flag
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -109,6 +109,15 @@ func (m *MaskBit) Bytes(trim bool) (b []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy returns a pointer to a (new) copy of a MaskBit.
|
||||||
|
func (m *MaskBit) Copy() (newM *MaskBit) {
|
||||||
|
|
||||||
|
newM = new(MaskBit)
|
||||||
|
*newM = *m
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Value returns the current raw uint value of a MaskBit.
|
// Value returns the current raw uint value of a MaskBit.
|
||||||
func (m *MaskBit) Value() (v uint) {
|
func (m *MaskBit) Value() (v uint) {
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,14 @@
|
|||||||
https://golang.org/pkg/runtime/#Caller
|
https://golang.org/pkg/runtime/#Caller
|
||||||
-- log.LlongFile and log.Lshortfile flags don't currently work properly for StdLogger/FileLogger; they refer to the file in logging package rather than the caller.
|
-- log.LlongFile and log.Lshortfile flags don't currently work properly for StdLogger/FileLogger; they refer to the file in logging package rather than the caller.
|
||||||
|
|
||||||
|
- StdLogger2; where stdout and stderr are both logged to depending on severity level.
|
||||||
|
- make configurable via OR bitmask
|
||||||
|
|
||||||
- Suport remote loggers? (eventlog, syslog, systemd)
|
- Suport remote loggers? (eventlog, syslog, systemd)
|
||||||
|
|
||||||
- JSON logger? YAML logger? XML logger?
|
- JSON logger? YAML logger? XML logger?
|
||||||
|
|
||||||
- DOCS.
|
- DOCS.
|
||||||
-- Done, but flesh out.
|
-- Done, but flesh out.
|
||||||
|
|
||||||
|
- Implement io.Writer interfaces
|
||||||
|
|||||||
@@ -12,3 +12,16 @@ const (
|
|||||||
// appendFlags are the flags used for testing the file (and opening/writing).
|
// appendFlags are the flags used for testing the file (and opening/writing).
|
||||||
appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY
|
appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const PriorityNone logPrio = 0
|
||||||
|
const (
|
||||||
|
PriorityEmergency logPrio = 1 << iota
|
||||||
|
PriorityAlert
|
||||||
|
PriorityCritical
|
||||||
|
PriorityError
|
||||||
|
PriorityWarning
|
||||||
|
PriorityNotice
|
||||||
|
PriorityInformational
|
||||||
|
PriorityDebug
|
||||||
|
)
|
||||||
|
const PriorityAll logPrio = PriorityEmergency | PriorityAlert | PriorityCritical | PriorityError | PriorityWarning | PriorityNotice | PriorityInformational | PriorityDebug
|
||||||
|
|||||||
@@ -220,6 +220,14 @@ func (l *FileLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *FileLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// renderWrite prepares/formats a log message to be written to this FileLogger.
|
// renderWrite prepares/formats a log message to be written to this FileLogger.
|
||||||
func (l *FileLogger) renderWrite(msg, prio string) {
|
func (l *FileLogger) renderWrite(msg, prio string) {
|
||||||
|
|
||||||
|
|||||||
23
logging/funcs_logprio.go
Normal file
23
logging/funcs_logprio.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/goutils/bitmask`
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasFlag provides a wrapper for functionality to the underlying bitmask.MaskBit.
|
||||||
|
func (l *logPrio) HasFlag(prio logPrio) (hasFlag bool) {
|
||||||
|
|
||||||
|
var m *bitmask.MaskBit
|
||||||
|
var p *bitmask.MaskBit
|
||||||
|
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = bitmask.NewMaskBitExplicit(uint(*l))
|
||||||
|
p = bitmask.NewMaskBitExplicit(uint(prio))
|
||||||
|
|
||||||
|
hasFlag = m.HasFlag(*p)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
74
logging/funcs_logwriter.go
Normal file
74
logging/funcs_logwriter.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/goutils/multierr`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Write writes bytes b to the underlying Logger's priority level if the logWriter's priority level(s) match.
|
||||||
|
func (l *logWriter) Write(b []byte) (n int, err error) {
|
||||||
|
|
||||||
|
var s string
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = string(b)
|
||||||
|
|
||||||
|
if l.prio.HasFlag(PriorityEmergency) {
|
||||||
|
if err = l.backend.Emerg(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityAlert) {
|
||||||
|
if err = l.backend.Alert(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityCritical) {
|
||||||
|
if err = l.backend.Crit(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityError) {
|
||||||
|
if err = l.backend.Err(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityWarning) {
|
||||||
|
if err = l.backend.Warning(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityNotice) {
|
||||||
|
if err = l.backend.Notice(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityInformational) {
|
||||||
|
if err = l.backend.Info(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.prio.HasFlag(PriorityDebug) {
|
||||||
|
if err = l.backend.Debug(s); err != nil {
|
||||||
|
mErr.AddError(err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mErr.IsEmpty() {
|
||||||
|
err = mErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package logging
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
`log`
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"r00t2.io/goutils/multierr"
|
"r00t2.io/goutils/multierr"
|
||||||
@@ -370,3 +371,11 @@ func (m *MultiLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (m *MultiLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&logWriter{backend: m, prio: prio}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
`log`
|
||||||
|
)
|
||||||
|
|
||||||
// Setup does nothing at all; it's here for interface compat. 🙃
|
// Setup does nothing at all; it's here for interface compat. 🙃
|
||||||
func (l *NullLogger) Setup() (err error) {
|
func (l *NullLogger) Setup() (err error) {
|
||||||
return
|
return
|
||||||
@@ -72,3 +76,11 @@ func (l *NullLogger) Notice(s string, v ...interface{}) (err error) {
|
|||||||
func (l *NullLogger) Warning(s string, v ...interface{}) (err error) {
|
func (l *NullLogger) Warning(s string, v ...interface{}) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger (that doesn't actually write to anything).
|
||||||
|
func (l *NullLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&nullWriter{}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
12
logging/funcs_nullwriter.go
Normal file
12
logging/funcs_nullwriter.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
// nulLWriter writes... nothing. To avoid errors, however, in downstream code it pretends it does (n will *always* == len(b)).
|
||||||
|
func (nw *nullWriter) Write(b []byte) (n int, err error) {
|
||||||
|
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n = len(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -244,3 +244,11 @@ func (l *StdLogger) renderWrite(msg, prio string) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *StdLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -223,3 +223,11 @@ func (l *SystemDLogger) renderWrite(msg string, prio journal.Priority) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *SystemDLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -266,3 +266,11 @@ func (l *SyslogLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *SyslogLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package logging
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
`log`
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -342,3 +343,11 @@ func (l *WinLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *WinLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ package logging
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
`r00t2.io/goutils/bitmask`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type logPrio bitmask.MaskBit
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Logger is one of the various loggers offered by this module.
|
Logger is one of the various loggers offered by this module.
|
||||||
*/
|
*/
|
||||||
@@ -23,6 +27,7 @@ type Logger interface {
|
|||||||
GetPrefix() (p string, err error)
|
GetPrefix() (p string, err error)
|
||||||
Setup() (err error)
|
Setup() (err error)
|
||||||
Shutdown() (err error)
|
Shutdown() (err error)
|
||||||
|
ToLogger(prio logPrio) (stdLibLog *log.Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -105,3 +110,12 @@ type MultiLogger struct {
|
|||||||
*/
|
*/
|
||||||
Loggers map[string]Logger
|
Loggers map[string]Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logWriter is used as a log.Logger and is returned by <Logger>.ToLogger.
|
||||||
|
type logWriter struct {
|
||||||
|
backend Logger
|
||||||
|
prio logPrio
|
||||||
|
}
|
||||||
|
|
||||||
|
// nullWriter is used as a shortcut by NullLogger.ToLogger.
|
||||||
|
type nullWriter struct{}
|
||||||
|
|||||||
5
structutils/consts.go
Normal file
5
structutils/consts.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package structutils
|
||||||
|
|
||||||
|
const (
|
||||||
|
TagMapTrim tagMapOpt = iota
|
||||||
|
)
|
||||||
362
structutils/funcs.go
Normal file
362
structutils/funcs.go
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
5
structutils/types.go
Normal file
5
structutils/types.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package structutils
|
||||||
|
|
||||||
|
type (
|
||||||
|
tagMapOpt uint8
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user