Compare commits

...

9 Commits

Author SHA1 Message Date
brent saner ae49f42c0c
v1.6.0
Added bitmask/MaskBit.Copy()
2024-04-14 01:54:59 -04:00
brent saner b87934e8a9
v1.5.0
Added structutils
2024-04-13 13:04:17 -04:00
brent s 70d6c2cbb3
updating logging TODO 2023-07-12 23:16:31 -04:00
brent s. a445a51c0d
Adding GetDebug method to loggers. 2022-09-07 06:03:28 -04:00
brent s. a2a849600b
Add docs for NullLogger. 2022-09-06 01:01:39 -04:00
brent s. 94145fb4c7
Add NullLogger to MultiLogger. 2022-03-13 13:34:24 -04:00
brent s. 81a2d308f0
Add NullLogger.
For when you need a Logger but don't want one. ;)
2022-03-13 13:29:31 -04:00
brent s. c4b3c6441a
adding Bytes() to MaskBit 2022-02-01 18:27:45 -05:00
brent s. 1c5abd4083
modifying bitmask to allow specifying an explicit value, and changing to uint instead of uint8 2022-02-01 15:36:56 -05:00
17 changed files with 772 additions and 144 deletions

127
bitmask/bitmask.go Normal file
View File

@ -0,0 +1,127 @@
package bitmask

import (
"bytes"
"encoding/binary"
"errors"
"math/bits"
)

// MaskBit is a flag container.
type MaskBit uint

/*
NewMaskBit is a convenience function.
It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly.

It is highly recommended to set this default as a "None" flag (separate from your iotas!)
as shown in the example.
*/
func NewMaskBit() (m *MaskBit) {

m = new(MaskBit)

return
}

// NewMaskBitExplicit is like NewMaskBit, but allows you to specify a non-zero (0x0) value.
func NewMaskBitExplicit(value uint) (m *MaskBit) {

var v MaskBit = MaskBit(value)

m = &v

return
}

// HasFlag is true if m has MaskBit flag set/enabled.
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {

var b MaskBit = *m

if b&flag != 0 {
r = true
}
return
}

// AddFlag adds MaskBit flag to m.
func (m *MaskBit) AddFlag(flag MaskBit) {

*m |= flag

return
}

// ClearFlag removes MaskBit flag from m.
func (m *MaskBit) ClearFlag(flag MaskBit) {

*m &= flag

return
}

// ToggleFlag switches MaskBit flag in m to its inverse; if true, it is now false and vice versa.
func (m *MaskBit) ToggleFlag(flag MaskBit) {

*m ^= flag

return
}

/*
Bytes returns the current value of a MasBit as a byte slice (big-endian).

If trim is false, b will (probably) be 4 bytes long if you're on a 32-bit size system,
and b will (probably) be 8 bytes long if you're on a 64-bit size system. You can determine
the size of the resulting slice via (math/)bits.UintSize / 8.

If trim is true, it will trim leading null bytes (if any). This will lead to an unpredictable
byte slice length in b, but is most likely preferred for byte operations.

*/
func (m *MaskBit) Bytes(trim bool) (b []byte) {

var b2 []byte
var size int = bits.UintSize / 8
var err error

b2 = make([]byte, size)

switch s := bits.UintSize; s {
case 32:
binary.BigEndian.PutUint32(b2[:], uint32(*m))
case 64:
binary.BigEndian.PutUint64(b2[:], uint64(*m))
default:
err = errors.New("unsupported Uint/system bit size")
panic(err)
}

if trim {
b = bytes.TrimLeft(b2, "\x00")
return
} else {
b = b2
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.
func (m *MaskBit) Value() (v uint) {

v = uint(*m)

return
}

View File

@ -1,46 +0,0 @@
package bitmask

// MaskBit is a flag container.
type MaskBit uint8

/*
NewMaskBit is a convenience function.
It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly.
It is highly recommended to set this default as a "None" flag (separate from your iotas!)
as shown in the example.
*/
func NewMaskBit() (m *MaskBit) {

m = new(MaskBit)

return
}

// HasFlag is true if m has MaskBit flag set/enabled.
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {

var b MaskBit = *m

if b&flag != 0 {
r = true
}
return
}

// AddFlag adds MaskBit flag to m.
func (m *MaskBit) AddFlag(flag MaskBit) {
*m |= flag
return
}

// ClearFlag removes MaskBit flag from m.
func (m *MaskBit) ClearFlag(flag MaskBit) {
*m &= flag
return
}

// ToggleFlag switches MaskBit flag in m to its inverse; if true, it is now false and vice versa.
func (m *MaskBit) ToggleFlag(flag MaskBit) {
*m ^= flag
return
}

View File

@ -11,18 +11,18 @@ To use this, set constants like thus:
"r00t2.io/goutils/bitmask"
)

const OPTNONE types.MaskBit = 0
const OPTNONE bitmask.MaskBit = 0
const (
OPT1 types.MaskBit = 1 << iota
OPT1 bitmask.MaskBit = 1 << iota
OPT2
OPT3
// ...
)

var MyMask *MaskBit
var MyMask *bitmask.MaskBit

func main() {
MyMask = types.NewMaskBit
MyMask = bitmask.NewMaskBit()

MyMask.AddFlag(OPT1)
MyMask.AddFlag(OPT3)
@ -41,5 +41,13 @@ As would this:
But this would return false:

MyMask.HasFlag(OPT2)

If you need something with more flexibility (as always, at the cost of complexity),
you may be interested in one of the following libraries:

. github.com/alvaroloes/enumer
. github.com/abice/go-enum
. github.com/jeffreyrichter/enum/enum

*/
package bitmask

View File

@ -3,9 +3,14 @@
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.

- StdLogger2; where stdout and stderr are both logged to depending on severity level.
- make configurable via OR bitmask

- Suport remote loggers? (eventlog, syslog, systemd)

- JSON logger? YAML logger? XML logger?

- DOCS.
-- Done, but flesh out.

- Implement io.Writer interfaces

View File

@ -3,18 +3,19 @@ Package logging implements and presents various loggers under a unified interfac

These particular loggers (logging.Logger) available are:

StdLogger
FileLogger
SystemDLogger (Linux only)
SyslogLogger (Linux only)
WinLogger (Windows only)
NullLogger
StdLogger
FileLogger
SystemDLogger (Linux only)
SyslogLogger (Linux only)
WinLogger (Windows only)

There is a sixth type of logging.Logger, MultiLogger, that allows for multiple loggers to be written to with a single call.
There is a seventh type of logging.Logger, MultiLogger, that allows for multiple loggers to be written to with a single call.
As you may have guessed, NullLogger doesn't actually log anything but is fully "functional" as a logging.Logger.

Note that for some Loggers, the prefix may be modified - "literal" loggers (StdLogger and FileLogger) will append a space to the end of the prefix.
If this is undesired (unlikely), you will need to modify (Logger).Prefix and run (Logger).Logger.SetPrefix(yourPrefixHere) for the respective logger.


Every logging.Logger type has the following methods that correspond to certain "levels".

Alert(s string, v ...interface{}) (err error)
@ -36,6 +37,7 @@ Note that in the case of a MultiLogger, err (if not nil) will be a (r00t2.io/gou
logging.Logger types also have the following methods:

DoDebug(d bool) (err error)
GetDebug() (d bool)
SetPrefix(p string) (err error)
GetPrefix() (p string, err error)
Setup() (err error)

View File

@ -1,12 +1,12 @@
package logging

import (
`errors`
"errors"
"fmt"
`io/fs`
"io/fs"
"log"
"os"
`strings`
"strings"
)

// Setup sets up/configures a FileLogger and prepares it for use.
@ -43,8 +43,8 @@ func (l *FileLogger) Shutdown() (err error) {
}

/*
GetPrefix returns the prefix used by this FileLogger.
err will always be nil; it's there for interface-compat.
GetPrefix returns the prefix used by this FileLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *FileLogger) GetPrefix() (prefix string, err error) {

@ -54,9 +54,9 @@ func (l *FileLogger) GetPrefix() (prefix string, err error) {
}

/*
DoDebug sets the debug state of this FileLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
DoDebug sets the debug state of this FileLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
*/
func (l *FileLogger) DoDebug(d bool) (err error) {

@ -65,9 +65,17 @@ func (l *FileLogger) DoDebug(d bool) (err error) {
return
}

// GetDebug returns the debug status of this FileLogger.
func (l *FileLogger) GetDebug() (d bool) {

d = l.EnableDebug

return
}

/*
SetPrefix sets the prefix for this FileLogger.
err will always be nil; it's there for interface-compat.
SetPrefix sets the prefix for this FileLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *FileLogger) SetPrefix(prefix string) (err error) {


View File

@ -1,11 +1,11 @@
package logging

import (
`errors`
`fmt`
`sync`
"errors"
"fmt"
"sync"

`r00t2.io/goutils/multierr`
"r00t2.io/goutils/multierr"
)

// Setup sets up/configures a MultiLogger (and all its MultiLogger.Loggers) and prepares it for use.
@ -67,8 +67,8 @@ func (m *MultiLogger) Shutdown() (err error) {
}

/*
GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers).
err will always be nil; it's there for interface-compat.
GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers).
err will always be nil; it's there for interface-compat.
*/
func (m *MultiLogger) GetPrefix() (prefix string, err error) {

@ -79,10 +79,10 @@ func (m *MultiLogger) GetPrefix() (prefix string, err error) {
}

/*
DoDebug sets the debug state of this MultiLogger (and all its MultiLogger.Loggers).
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
DoDebug sets the debug state of this MultiLogger (and all its MultiLogger.Loggers).
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.

If you had a logger-specific EnableDebug set, you will need to re-set it to your desired state after running this method.
If you had a logger-specific EnableDebug set, you will need to re-set it to your desired state after running this method.
*/
func (m *MultiLogger) DoDebug(d bool) (err error) {

@ -114,10 +114,18 @@ func (m *MultiLogger) DoDebug(d bool) (err error) {
return
}

/*
SetPrefix sets the prefix for this MultiLogger (and all its MultiLogger.Loggers).
// GetDebug returns the debug status of this MultiLogger.
func (m *MultiLogger) GetDebug() (d bool) {

If you had a logger-specific Prefix set, you will need to re-set it to your desired prefix after running this method.
d = m.EnableDebug

return
}

/*
SetPrefix sets the prefix for this MultiLogger (and all its MultiLogger.Loggers).

If you had a logger-specific Prefix set, you will need to re-set it to your desired prefix after running this method.
*/
func (m *MultiLogger) SetPrefix(prefix string) (err error) {


View File

@ -1,10 +1,10 @@
package logging

import (
`path`
"path"

`github.com/google/uuid`
`r00t2.io/sysutils/paths`
"github.com/google/uuid"
"r00t2.io/sysutils/paths"
)

/*
@ -145,6 +145,40 @@ func (m *MultiLogger) AddFileLogger(identifier string, logFlags int, logfilePath
return
}

/*
AddNullLogger adds a NullLogger to a MultiLogger.

identifier is a string to use to identify the added NullLogger in MultiLogger.Loggers.
If empty, one will be automatically generated.
*/
func (m *MultiLogger) AddNullLogger(identifier string) (err error) {

var exists bool
var prefix string

if identifier == "" {
identifier = uuid.New().String()
}

if _, exists = m.Loggers[identifier]; exists {
err = ErrExistingLogger
return
}

m.Loggers[identifier] = &NullLogger{}
if err = m.Loggers[identifier].Setup(); err != nil {
return
}

if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
return
}

m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)

return
}

// RemoveLogger will let you remove a Logger from MultiLogger.Loggers.
func (m *MultiLogger) RemoveLogger(identifier string) (err error) {


View File

@ -0,0 +1,74 @@
package logging

// Setup does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Setup() (err error) {
return
}

// DoDebug does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) DoDebug(d bool) (err error) {
return
}

// GetDebug returns the debug status of this NullLogger. It will always return true. 🙃
func (n *NullLogger) GetDebug() (d bool) {

d = true

return
}

// SetPrefix does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) SetPrefix(p string) (err error) {
return
}

// GetPrefix does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) GetPrefix() (p string, err error) {
return
}

// Shutdown does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Shutdown() (err error) {
return
}

// Alert does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Alert(s string, v ...interface{}) (err error) {
return
}

// Crit does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Crit(s string, v ...interface{}) (err error) {
return
}

// Debug does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Debug(s string, v ...interface{}) (err error) {
return
}

// Emerg does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Emerg(s string, v ...interface{}) (err error) {
return
}

// Err does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Err(s string, v ...interface{}) (err error) {
return
}

// Info does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Info(s string, v ...interface{}) (err error) {
return
}

// Notice does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Notice(s string, v ...interface{}) (err error) {
return
}

// Warning does nothing at all; it's here for interface compat. 🙃
func (l *NullLogger) Warning(s string, v ...interface{}) (err error) {
return
}

View File

@ -2,15 +2,15 @@ package logging

import (
"fmt"
`io`
`log`
`os`
`strings`
"io"
"log"
"os"
"strings"
)

/*
Setup sets up/configures a StdLogger and prepares it for use.
err will always be nil; it's there for interface-compat.
Setup sets up/configures a StdLogger and prepares it for use.
err will always be nil; it's there for interface-compat.
*/
func (l *StdLogger) Setup() (err error) {

@ -47,8 +47,8 @@ func (l *StdLogger) Setup() (err error) {
}

/*
Shutdown cleanly shuts down a StdLogger.
err will always be nil; it's there for interface-compat.
Shutdown cleanly shuts down a StdLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *StdLogger) Shutdown() (err error) {

@ -58,8 +58,8 @@ func (l *StdLogger) Shutdown() (err error) {
}

/*
GetPrefix returns the prefix used by this StdLogger.
err will always be nil; it's there for interface-compat.
GetPrefix returns the prefix used by this StdLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *StdLogger) GetPrefix() (prefix string, err error) {

@ -69,9 +69,9 @@ func (l *StdLogger) GetPrefix() (prefix string, err error) {
}

/*
DoDebug sets the debug state of this StdLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
DoDebug sets the debug state of this StdLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
*/
func (l *StdLogger) DoDebug(d bool) (err error) {

@ -80,9 +80,17 @@ func (l *StdLogger) DoDebug(d bool) (err error) {
return
}

// GetDebug returns the debug status of this StdLogger.
func (l *StdLogger) GetDebug() (d bool) {

d = l.EnableDebug

return
}

/*
SetPrefix sets the prefix for this StdLogger.
err will always be nil; it's there for interface-compat.
SetPrefix sets the prefix for this StdLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *StdLogger) SetPrefix(prefix string) (err error) {


View File

@ -8,8 +8,8 @@ import (
)

/*
Setup sets up/configures a SystemDLogger and prepares it for use.
err will always be nil; it's there for interface-compat.
Setup sets up/configures a SystemDLogger and prepares it for use.
err will always be nil; it's there for interface-compat.
*/
func (l *SystemDLogger) Setup() (err error) {

@ -19,8 +19,8 @@ func (l *SystemDLogger) Setup() (err error) {
}

/*
Shutdown cleanly shuts down a SystemDLogger.
err will always be nil; it's there for interface-compat.
Shutdown cleanly shuts down a SystemDLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *SystemDLogger) Shutdown() (err error) {

@ -30,8 +30,8 @@ func (l *SystemDLogger) Shutdown() (err error) {
}

/*
GetPrefix returns the prefix used by this SystemDLogger.
err will always be nil; it's there for interface-compat.
GetPrefix returns the prefix used by this SystemDLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *SystemDLogger) GetPrefix() (prefix string, err error) {

@ -41,9 +41,9 @@ func (l *SystemDLogger) GetPrefix() (prefix string, err error) {
}

/*
DoDebug sets the debug state of this SystemDLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
DoDebug sets the debug state of this SystemDLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
*/
func (l *SystemDLogger) DoDebug(d bool) (err error) {

@ -52,9 +52,17 @@ func (l *SystemDLogger) DoDebug(d bool) (err error) {
return
}

// GetDebug returns the debug status of this SystemDLogger.
func (l *SystemDLogger) GetDebug() (d bool) {

d = l.EnableDebug

return
}

/*
SetPrefix sets the prefix for this SystemDLogger.
err will always be nil; it's there for interface-compat.
SetPrefix sets the prefix for this SystemDLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *SystemDLogger) SetPrefix(prefix string) (err error) {


View File

@ -5,7 +5,7 @@ import (
"log"
"log/syslog"

`r00t2.io/goutils/multierr`
"r00t2.io/goutils/multierr"
)

// Setup sets up/configures a SyslogLogger and prepares it for use.
@ -73,8 +73,8 @@ func (l *SyslogLogger) Shutdown() (err error) {
}

/*
GetPrefix returns the prefix used by this SyslogLogger.
err will always be nil; it's there for interface-compat.
GetPrefix returns the prefix used by this SyslogLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *SyslogLogger) GetPrefix() (prefix string, err error) {

@ -84,9 +84,9 @@ func (l *SyslogLogger) GetPrefix() (prefix string, err error) {
}

/*
DoDebug sets the debug state of this SyslogLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
DoDebug sets the debug state of this SyslogLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
*/
func (l *SyslogLogger) DoDebug(d bool) (err error) {

@ -95,6 +95,14 @@ func (l *SyslogLogger) DoDebug(d bool) (err error) {
return
}

// GetDebug returns the debug status of this SyslogLogger.
func (l *SyslogLogger) GetDebug() (d bool) {

d = l.EnableDebug

return
}

// SetPrefix sets the prefix for this SyslogLogger.
func (l *SyslogLogger) SetPrefix(prefix string) (err error) {


View File

@ -1,26 +1,26 @@
package logging

import (
`errors`
`fmt`
`os`
`os/exec`
`syscall`
"errors"
"fmt"
"os"
"os/exec"
"syscall"

`golang.org/x/sys/windows/registry`
`golang.org/x/sys/windows/svc/eventlog`
`r00t2.io/sysutils/paths`
"golang.org/x/sys/windows/registry"
"golang.org/x/sys/windows/svc/eventlog"
"r00t2.io/sysutils/paths"
)

/*
Setup sets up/configures a WinLogger and prepares it for use.
This will fail with an Access Denied (the first time, at least) unless running with elevated permissions unless WinLogger.Prefix is
a registered Event Log source.
Setup sets up/configures a WinLogger and prepares it for use.
This will fail with an Access Denied (the first time, at least) unless running with elevated permissions unless WinLogger.Prefix is
a registered Event Log source.

If a failure occurs while trying to open the log with the given WinLogger.Prefix ("source"), a new Event Log source will be registered.
If WinLogger.Executable is not empty at the time of calling WinLogger.Setup (or WinLogger.ForceService is true),
eventlog.Install will be used (with the WinLogger.ExpandKey field).
Otherwise eventlog.InstallAsEventCreate will be used.
If a failure occurs while trying to open the log with the given WinLogger.Prefix ("source"), a new Event Log source will be registered.
If WinLogger.Executable is not empty at the time of calling WinLogger.Setup (or WinLogger.ForceService is true),
eventlog.Install will be used (with the WinLogger.ExpandKey field).
Otherwise eventlog.InstallAsEventCreate will be used.
*/
func (l *WinLogger) Setup() (err error) {

@ -108,8 +108,8 @@ func (l *WinLogger) Remove() (err error) {
}

/*
Shutdown cleanly shuts down a WinLogger but keep the source registered. Use WinLogger.Remove
(or set WinLogger.RemoveOnClose to true before calling WinLogger.Shutdown) to remove the registered source.
Shutdown cleanly shuts down a WinLogger but keep the source registered. Use WinLogger.Remove
(or set WinLogger.RemoveOnClose to true before calling WinLogger.Shutdown) to remove the registered source.
*/
func (l *WinLogger) Shutdown() (err error) {

@ -128,8 +128,8 @@ func (l *WinLogger) Shutdown() (err error) {
}

/*
GetPrefix returns the prefix used by this WinLogger.
err will always be nil; it's there for interface-compat.
GetPrefix returns the prefix used by this WinLogger.
err will always be nil; it's there for interface-compat.
*/
func (l *WinLogger) GetPrefix() (prefix string, err error) {

@ -139,9 +139,9 @@ func (l *WinLogger) GetPrefix() (prefix string, err error) {
}

/*
DoDebug sets the debug state of this WinLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
DoDebug sets the debug state of this WinLogger.
Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log.
err will always be nil; it's there for interface-compat.
*/
func (l *WinLogger) DoDebug(d bool) (err error) {

@ -150,6 +150,14 @@ func (l *WinLogger) DoDebug(d bool) (err error) {
return
}

// GetDebug returns the debug status of this WinLogger.
func (l *WinLogger) GetDebug() (d bool) {

d = l.EnableDebug

return
}

// SetPrefix sets the prefix for this WinLogger.
func (l *WinLogger) SetPrefix(prefix string) (err error) {


View File

@ -2,11 +2,11 @@ package logging

import (
"log"
`os`
"os"
)

/*
Logger is one of the various loggers offered by this module.
Logger is one of the various loggers offered by this module.
*/
type Logger interface {
Alert(s string, v ...interface{}) (err error)
@ -18,6 +18,7 @@ type Logger interface {
Notice(s string, v ...interface{}) (err error)
Warning(s string, v ...interface{}) (err error)
DoDebug(d bool) (err error)
GetDebug() (d bool)
SetPrefix(p string) (err error)
GetPrefix() (p string, err error)
Setup() (err error)
@ -25,8 +26,8 @@ type Logger interface {
}

/*
StdLogger uses the log package in stdlib to perform all logging. The default is to write to STDOUT.
If you wish to modify the underling log.Logger object, you can access it directly via StdLogger.Logger.
StdLogger uses the log package in stdlib to perform all logging. The default is to write to STDOUT.
If you wish to modify the underling log.Logger object, you can access it directly via StdLogger.Logger.
*/
type StdLogger struct {
// All log.Logger fields/methods are exposed.
@ -71,11 +72,11 @@ type StdLogger struct {
}

/*
FileLogger uses a StdLogger with a file handle writer to write to the file given at Path.
FileLogger uses a StdLogger with a file handle writer to write to the file given at Path.

NOTE: If you wish to change the FileLogger.StdLogger.LogFlags, do *not* run FileLogger.StdLogger.Setup after doing so as this
will instead create a logger detached from the file handler. Instead, be sure to call FileLogger.Setup.
(Alternatively, run FileLogger.Shutdown and replace your logger with a new FileLogger.)
NOTE: If you wish to change the FileLogger.StdLogger.LogFlags, do *not* run FileLogger.StdLogger.Setup after doing so as this
will instead create a logger detached from the file handler. Instead, be sure to call FileLogger.Setup.
(Alternatively, run FileLogger.Shutdown and replace your logger with a new FileLogger.)
*/
type FileLogger struct {
// StdLogger is used for the log formation and handling. See StdLogger for more details.
@ -86,6 +87,9 @@ type FileLogger struct {
writer *os.File
}

// NullLogger is used mainly for test implementations, mockup code, etc. It does absolutely nothing with all messages sent to it.
type NullLogger struct{}

// MultiLogger is used to contain one or more Loggers and present them all as a single Logger.
type MultiLogger struct {
/*

5
structutils/consts.go Normal file
View File

@ -0,0 +1,5 @@
package structutils

const (
TagMapTrim tagMapOpt = iota
)

362
structutils/funcs.go Normal file
View 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
View File

@ -0,0 +1,5 @@
package structutils

type (
tagMapOpt uint8
)