v1.9.6
FIXED: * More clear docs for bitmask * Resolved potential issue for using PriorityAll in logging.logPrio.HasFlag.
This commit is contained in:
parent
688abd0874
commit
2222cea7fb
@ -36,7 +36,9 @@ func NewMaskBitExplicit(value uint) (m *MaskBit) {
|
||||
|
||||
/*
|
||||
HasFlag is true if m has MaskBit flag set/enabled.
|
||||
|
||||
THIS WILL RETURN FALSE FOR OR'd FLAGS.
|
||||
|
||||
For example:
|
||||
|
||||
flagA MaskBit = 0x01
|
||||
@ -50,6 +52,9 @@ func NewMaskBitExplicit(value uint) (m *MaskBit) {
|
||||
Use [MaskBit.IsOneOf] instead if you do not desire this behavior,
|
||||
and instead want to test composite flag *membership*.
|
||||
(MaskBit.IsOneOf will also return true for non-composite equality.)
|
||||
|
||||
To be more clear, if MaskBit flag is a composite MaskBit (e.g. flagComposite above),
|
||||
HasFlag will only return true of ALL bits in flag are also set in MaskBit m.
|
||||
*/
|
||||
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
|
||||
|
||||
@ -70,12 +75,15 @@ func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
|
||||
If composite is *not* an OR'd MaskBit (i.e.
|
||||
it falls directly on a boundary -- 0, 1, 2, 4, 8, 16, etc.),
|
||||
then IsOneOf will behave exactly like HasFlag.
|
||||
|
||||
If m is a composite MaskBit (it usually is) and composite is ALSO a composite MaskBit,
|
||||
IsOneOf will return true if ANY of the flags set in m is set in composite.
|
||||
*/
|
||||
func (m *MaskBit) IsOneOf(composite MaskBit) (r bool) {
|
||||
|
||||
var b MaskBit = *m
|
||||
|
||||
if b&flag != 0 {
|
||||
if b&composite != 0 {
|
||||
r = true
|
||||
}
|
||||
return
|
||||
|
119
bitmask/doc.go
119
bitmask/doc.go
@ -1,9 +1,35 @@
|
||||
/*
|
||||
Package bitmask handles a flag-like opt/bitmask system.
|
||||
|
||||
See https://yourbasic.org/golang/bitmask-flag-set-clear/ for more information.
|
||||
See https://yourbasic.org/golang/bitmask-flag-set-clear/ for basic information on what bitmasks are and why they're useful.
|
||||
|
||||
To use this, set constants like thus:
|
||||
Specifically, in the case of Go, they allow you to essentially manage many, many, many "booleans" as part of a single value.
|
||||
|
||||
A single bool value in Go takes up 8 bits/1 byte, unavoidably.
|
||||
|
||||
However, a [bitmask.MaskBit] is backed by a uint which (depending on your platform) is either 32 bits/4 bytes or 64 bits/8 bytes.
|
||||
|
||||
"But wait, that takes up more memory though!"
|
||||
|
||||
Yep, but bitmasking lets you store a "boolean" AT EACH BIT - it operates on
|
||||
whether a bit in a byte/set of bytes at a given position is 0 or 1.
|
||||
|
||||
Which means on 32-bit platforms, a [MaskBit] can have up to 4294967295 "booleans" in a single value (0 to (2^32)-1).
|
||||
|
||||
On 64-bit platforms, a [MaskBit] can have up to 18446744073709551615 "booleans" in a single value (0 to (2^64)-1).
|
||||
|
||||
If you tried to do that with Go bool values, that'd take up 4294967295 bytes (4 GiB)
|
||||
or 18446744073709551615 bytes (16 EiB - yes, that's [exbibytes]) of RAM for 32-bit/64-bit platforms respectively.
|
||||
|
||||
"But that has to be so slow to unpack that!"
|
||||
|
||||
Nope. It's not using compression or anything, the CPU is just comparing bit "A" vs. bit "B" 32/64 times. That's super easy work for a CPU.
|
||||
|
||||
There's a reason Doom used bitmasking for the "dmflags" value in its server configs.
|
||||
|
||||
# Usage
|
||||
|
||||
To use this library, set constants like thus:
|
||||
|
||||
package main
|
||||
|
||||
@ -42,12 +68,95 @@ But this would return false:
|
||||
|
||||
MyMask.HasFlag(OPT2)
|
||||
|
||||
# Technical Caveats
|
||||
|
||||
TARGETING
|
||||
|
||||
When implementing, you should always set MyMask (from Usage section above) as the actual value.
|
||||
For example, if you are checking a permissions set for a user that has the value, say, 6
|
||||
|
||||
var userPerms uint = 6 // 0x0000000000000006
|
||||
|
||||
and your library has the following permission bits defined:
|
||||
|
||||
const PermsNone bitmask.MaskBit = 0
|
||||
const (
|
||||
PermsList bitmask.MaskBit = 1 << iota // 1
|
||||
PermsRead // 2
|
||||
PermsWrite // 4
|
||||
PermsExec // 8
|
||||
PermsAdmin // 16
|
||||
)
|
||||
|
||||
And you want to see if the user has the PermsRead flag set, you would do:
|
||||
|
||||
userPermMask = bitmask.NewMaskBitExplicit(userPerms)
|
||||
if userPermMask.HasFlag(PermsRead) {
|
||||
// ...
|
||||
}
|
||||
|
||||
NOT:
|
||||
|
||||
userPermMask = bitmask.NewMaskBitExplicit(PermsRead)
|
||||
// Nor:
|
||||
// userPermMask = PermsRead
|
||||
if userPermMask.HasFlag(userPerms) {
|
||||
// ...
|
||||
}
|
||||
|
||||
This will be terribly, horribly wrong, cause incredibly unexpected results,
|
||||
and quite possibly cause massive security issues. Don't do it.
|
||||
|
||||
COMPOSITES
|
||||
|
||||
If you want to define a set of flags that are a combination of other flags,
|
||||
your inclination would be to bitwise-OR them together:
|
||||
|
||||
const (
|
||||
flagA bitmask.MaskBit = 1 << iota // 1
|
||||
flagB // 2
|
||||
)
|
||||
|
||||
const (
|
||||
flagAB bitmask.MaskBit = flagA | flagB // 3
|
||||
)
|
||||
|
||||
Which is fine and dandy. But if you then have:
|
||||
|
||||
var myMask *bitmask.MaskBit = bitmask.NewMaskBit()
|
||||
|
||||
myMask.AddFlag(flagA)
|
||||
|
||||
You may expect this call to [MaskBit.HasFlag]:
|
||||
|
||||
myMask.HasFlag(flagAB)
|
||||
|
||||
to be true, since flagA is "in" flagAB.
|
||||
It will return false - HasFlag does strict comparisons.
|
||||
It will only return true if you then ALSO do:
|
||||
|
||||
// This would require setting flagA first.
|
||||
// The order of setting flagA/flagB doesn't matter,
|
||||
// but you must have both set for HasFlag(flagAB) to return true.
|
||||
myMask.AddFlag(flagB)
|
||||
|
||||
or if you do:
|
||||
|
||||
// This can be done with or without additionally setting flagA.
|
||||
myMask.AddFlag(flagAB)
|
||||
|
||||
Instead, if you want to see if a mask has membership within a composite flag,
|
||||
you can use [MaskBit.IsOneOf].
|
||||
|
||||
# Other Options
|
||||
|
||||
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
|
||||
* [github.com/alvaroloes/enumer]
|
||||
* [github.com/abice/go-enum]
|
||||
* [github.com/jeffreyrichter/enum/enum]
|
||||
|
||||
[exbibytes]: https://simple.wikipedia.org/wiki/Exbibyte
|
||||
*/
|
||||
package bitmask
|
||||
|
@ -4,6 +4,8 @@
|
||||
-- no native Go support (yet)?
|
||||
--- https://developer.apple.com/forums/thread/773369
|
||||
|
||||
- The log destinations for e.g. consts_nix.go et. al. probably should be unexported types.
|
||||
|
||||
- add a `log/slog` logging.Logger?
|
||||
|
||||
- Implement code line/func/etc. (only for debug?):
|
||||
|
@ -23,8 +23,8 @@ const (
|
||||
// LogUndefined indicates an undefined Logger type.
|
||||
const LogUndefined bitmask.MaskBit = iota
|
||||
const (
|
||||
// LogJournald flags a SystemDLogger Logger type.
|
||||
LogJournald = 1 << iota
|
||||
// LogJournald flags a SystemDLogger Logger type. This will, for hopefully obvious reasons, only work on Linux systemd systems.
|
||||
LogJournald bitmask.MaskBit = 1 << iota
|
||||
// LogSyslog flags a SyslogLogger Logger type.
|
||||
LogSyslog
|
||||
// LogFile flags a FileLogger Logger type.
|
||||
|
@ -3,16 +3,14 @@ package logging
|
||||
import (
|
||||
`os`
|
||||
`path/filepath`
|
||||
|
||||
`r00t2.io/goutils/bitmask`
|
||||
)
|
||||
|
||||
// Flags for logger configuration. These are used internally.
|
||||
// LogUndefined indicates an undefined Logger type.
|
||||
LogUndefined bitmask.MaskBit = 0
|
||||
const (
|
||||
// LogUndefined indicates an undefined Logger type.
|
||||
LogUndefined bitmask.MaskBit = 1 << iota
|
||||
// LogWinLogger indicates a WinLogger Logger type (Event Log).
|
||||
LogWinLogger
|
||||
LogWinLogger bitmask.MaskBit= 1 << iota
|
||||
// LogFile flags a FileLogger Logger type.
|
||||
LogFile
|
||||
// LogStdout flags a StdLogger Logger type.
|
||||
|
@ -17,7 +17,9 @@ func (l *logPrio) HasFlag(prio logPrio) (hasFlag bool) {
|
||||
m = bitmask.NewMaskBitExplicit(uint(*l))
|
||||
p = bitmask.NewMaskBitExplicit(uint(prio))
|
||||
|
||||
hasFlag = m.HasFlag(*p)
|
||||
// Use IsOneOf instead in case PriorityAll is passed for prio.
|
||||
// hasFlag = m.HasFlag(*p)
|
||||
hasFlag = m.IsOneOf(*p)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ func (l *logWriter) Write(b []byte) (n int, err error) {
|
||||
|
||||
s = string(b)
|
||||
|
||||
// Since this explicitly checks each priority level, there's no need for IsOneOf in case of PriorityAll.
|
||||
|
||||
if l.prio.HasFlag(PriorityEmergency) {
|
||||
if err = l.backend.Emerg(s); err != nil {
|
||||
mErr.AddError(err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user