Compare commits

...

12 Commits

Author SHA1 Message Date
412277b3e0 v0.0.63 2023-01-28 22:29:45 +01:00
e46f8019ec v0.0.62 2023-01-28 22:29:21 +01:00
ae952b2166 v0.0.61 2023-01-28 22:28:20 +01:00
b24dba9a45 v0.0.60 2023-01-28 14:44:12 +01:00
cfbc20367d v0.0.59 2023-01-15 02:27:08 +01:00
e25912758e v0.0.58 2023-01-15 02:05:05 +01:00
e1ae77a9db v0.0.57 2023-01-15 01:56:40 +01:00
9d07b3955f v0.0.56 2023-01-13 16:05:39 +01:00
02be696c25 v0.0.55 2023-01-06 02:02:22 +01:00
ba07625b7c v0.0.54 2022-12-29 22:52:52 +01:00
aeded3fb37 v0.0.53 2022-12-24 03:11:09 +01:00
1a1cd6d0aa v0.0.52 2022-12-24 02:50:46 +01:00
8 changed files with 556 additions and 39 deletions

View File

@@ -14,9 +14,21 @@ import (
// ApplyEnvOverrides overrides field values from environment variables // ApplyEnvOverrides overrides field values from environment variables
// //
// fields must be tagged with `env:"env_key"` // fields must be tagged with `env:"env_key"`
func ApplyEnvOverrides[T any](c *T) error { //
// only works on exported fields
//
// fields without an env tag are ignored
// fields with an `env:"-"` tag are ignore
//
// sub-structs are recursively parsed (if they have an env tag) and the env-variable keys are delimited by the delim parameter
// sub-structs with `env:""` are also parsed, but the delimited is skipped (they are handled as if they were one level higher)
func ApplyEnvOverrides[T any](c *T, delim string) error {
rval := reflect.ValueOf(c).Elem() rval := reflect.ValueOf(c).Elem()
return processEnvOverrides(rval, delim, "")
}
func processEnvOverrides(rval reflect.Value, delim string, prefix string) error {
rtyp := rval.Type() rtyp := rval.Type()
for i := 0; i < rtyp.NumField(); i++ { for i := 0; i < rtyp.NumField(); i++ {
@@ -24,12 +36,36 @@ func ApplyEnvOverrides[T any](c *T) error {
rsfield := rtyp.Field(i) rsfield := rtyp.Field(i)
rvfield := rval.Field(i) rvfield := rval.Field(i)
envkey := rsfield.Tag.Get("env") if !rsfield.IsExported() {
if envkey == "" {
continue continue
} }
envval, efound := os.LookupEnv(envkey) if rvfield.Kind() == reflect.Struct {
envkey, found := rsfield.Tag.Lookup("env")
if !found || envkey == "-" {
continue
}
subPrefix := prefix
if envkey != "" {
subPrefix = subPrefix + envkey + delim
}
err := processEnvOverrides(rvfield, delim, subPrefix)
if err != nil {
return err
}
}
envkey := rsfield.Tag.Get("env")
if envkey == "" || envkey == "-" {
continue
}
fullEnvKey := prefix + envkey
envval, efound := os.LookupEnv(fullEnvKey)
if !efound { if !efound {
continue continue
} }
@@ -38,86 +74,86 @@ func ApplyEnvOverrides[T any](c *T) error {
rvfield.Set(reflect.ValueOf(envval)) rvfield.Set(reflect.ValueOf(envval))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
} else if rvfield.Type() == reflect.TypeOf(int(0)) { } else if rvfield.Type() == reflect.TypeOf(int(0)) {
envint, err := strconv.ParseInt(envval, 10, bits.UintSize) envint, err := strconv.ParseInt(envval, 10, bits.UintSize)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int (value := '%s')", envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int (value := '%s')", fullEnvKey, envval))
} }
rvfield.Set(reflect.ValueOf(int(envint))) rvfield.Set(reflect.ValueOf(int(envint)))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
} else if rvfield.Type() == reflect.TypeOf(int64(0)) { } else if rvfield.Type() == reflect.TypeOf(int64(0)) {
envint, err := strconv.ParseInt(envval, 10, 64) envint, err := strconv.ParseInt(envval, 10, 64)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int64 (value := '%s')", envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int64 (value := '%s')", fullEnvKey, envval))
} }
rvfield.Set(reflect.ValueOf(int64(envint))) rvfield.Set(reflect.ValueOf(int64(envint)))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
} else if rvfield.Type() == reflect.TypeOf(int32(0)) { } else if rvfield.Type() == reflect.TypeOf(int32(0)) {
envint, err := strconv.ParseInt(envval, 10, 32) envint, err := strconv.ParseInt(envval, 10, 32)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", fullEnvKey, envval))
} }
rvfield.Set(reflect.ValueOf(int32(envint))) rvfield.Set(reflect.ValueOf(int32(envint)))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
} else if rvfield.Type() == reflect.TypeOf(int8(0)) { } else if rvfield.Type() == reflect.TypeOf(int8(0)) {
envint, err := strconv.ParseInt(envval, 10, 8) envint, err := strconv.ParseInt(envval, 10, 8)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", fullEnvKey, envval))
} }
rvfield.Set(reflect.ValueOf(int8(envint))) rvfield.Set(reflect.ValueOf(int8(envint)))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
} else if rvfield.Type() == reflect.TypeOf(time.Duration(0)) { } else if rvfield.Type() == reflect.TypeOf(time.Duration(0)) {
dur, err := timeext.ParseDurationShortString(envval) dur, err := timeext.ParseDurationShortString(envval)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to duration (value := '%s')", envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to duration (value := '%s')", fullEnvKey, envval))
} }
rvfield.Set(reflect.ValueOf(dur)) rvfield.Set(reflect.ValueOf(dur))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, dur.String()) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, dur.String())
} else if rvfield.Type() == reflect.TypeOf(time.UnixMilli(0)) { } else if rvfield.Type() == reflect.TypeOf(time.UnixMilli(0)) {
tim, err := time.Parse(time.RFC3339Nano, envval) tim, err := time.Parse(time.RFC3339Nano, envval)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to time.time (value := '%s')", envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to time.time (value := '%s')", fullEnvKey, envval))
} }
rvfield.Set(reflect.ValueOf(tim)) rvfield.Set(reflect.ValueOf(tim))
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, tim.String()) fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, tim.String())
} else if rvfield.Type().ConvertibleTo(reflect.TypeOf(int(0))) { } else if rvfield.Type().ConvertibleTo(reflect.TypeOf(int(0))) {
envint, err := strconv.ParseInt(envval, 10, 8) envint, err := strconv.ParseInt(envval, 10, 8)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to <%s, ,int> (value := '%s')", rvfield.Type().Name(), envkey, envval)) return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to <%s, ,int> (value := '%s')", rvfield.Type().Name(), fullEnvKey, envval))
} }
envcvl := reflect.ValueOf(envint).Convert(rvfield.Type()) envcvl := reflect.ValueOf(envint).Convert(rvfield.Type())
rvfield.Set(envcvl) rvfield.Set(envcvl)
fmt.Printf("[CONF] Overwrite config '%s' with '%v'\n", envkey, envcvl.Interface()) fmt.Printf("[CONF] Overwrite config '%s' with '%v'\n", fullEnvKey, envcvl.Interface())
} else if rvfield.Type().ConvertibleTo(reflect.TypeOf("")) { } else if rvfield.Type().ConvertibleTo(reflect.TypeOf("")) {
@@ -125,7 +161,7 @@ func ApplyEnvOverrides[T any](c *T) error {
rvfield.Set(envcvl) rvfield.Set(envcvl)
fmt.Printf("[CONF] Overwrite config '%s' with '%v'\n", envkey, envcvl.Interface()) fmt.Printf("[CONF] Overwrite config '%s' with '%v'\n", fullEnvKey, envcvl.Interface())
} else { } else {
return errors.New(fmt.Sprintf("Unknown kind/type in config: [ %s | %s ]", rvfield.Kind().String(), rvfield.Type().String())) return errors.New(fmt.Sprintf("Unknown kind/type in config: [ %s | %s ]", rvfield.Kind().String(), rvfield.Type().String()))

View File

@@ -1,6 +1,7 @@
package confext package confext
import ( import (
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
"testing" "testing"
"time" "time"
) )
@@ -40,7 +41,7 @@ func TestApplyEnvOverridesNoop(t *testing.T) {
output := input output := input
err := ApplyEnvOverrides(&output) err := ApplyEnvOverrides(&output, ".")
if err != nil { if err != nil {
t.Errorf("%v", err) t.Errorf("%v", err)
t.FailNow() t.FailNow()
@@ -92,7 +93,7 @@ func TestApplyEnvOverridesSimple(t *testing.T) {
t.Setenv("TEST_V8", "1min4s") t.Setenv("TEST_V8", "1min4s")
t.Setenv("TEST_V9", "2009-11-10T23:00:00Z") t.Setenv("TEST_V9", "2009-11-10T23:00:00Z")
err := ApplyEnvOverrides(&data) err := ApplyEnvOverrides(&data, ".")
if err != nil { if err != nil {
t.Errorf("%v", err) t.Errorf("%v", err)
t.FailNow() t.FailNow()
@@ -109,6 +110,109 @@ func TestApplyEnvOverridesSimple(t *testing.T) {
assertEqual(t, data.V9, time.Unix(1257894000, 0).UTC()) assertEqual(t, data.V9, time.Unix(1257894000, 0).UTC())
} }
func TestApplyEnvOverridesRecursive(t *testing.T) {
type subdata struct {
V1 int `env:"SUB_V1"`
VX string ``
V2 string `env:"SUB_V2"`
V8 time.Duration `env:"SUB_V3"`
V9 time.Time `env:"SUB_V4"`
}
type testdata struct {
V1 int `env:"TEST_V1"`
VX string ``
Sub1 subdata ``
Sub2 subdata `env:"TEST_V2"`
Sub3 subdata `env:"TEST_V3"`
Sub4 subdata `env:""`
V5 string `env:"-"`
}
data := testdata{
V1: 1,
VX: "2",
V5: "no",
Sub1: subdata{
V1: 3,
VX: "4",
V2: "5",
V8: 6 * time.Second,
V9: time.Date(2000, 1, 7, 1, 1, 1, 0, time.UTC),
},
Sub2: subdata{
V1: 8,
VX: "9",
V2: "10",
V8: 11 * time.Second,
V9: time.Date(2000, 1, 12, 1, 1, 1, 0, timeext.TimezoneBerlin),
},
Sub3: subdata{
V1: 13,
VX: "14",
V2: "15",
V8: 16 * time.Second,
V9: time.Date(2000, 1, 17, 1, 1, 1, 0, timeext.TimezoneBerlin),
},
Sub4: subdata{
V1: 18,
VX: "19",
V2: "20",
V8: 21 * time.Second,
V9: time.Date(2000, 1, 22, 1, 1, 1, 0, timeext.TimezoneBerlin),
},
}
t.Setenv("TEST_V1", "999")
t.Setenv("-", "yes")
t.Setenv("TEST_V2_SUB_V1", "846")
t.Setenv("TEST_V2_SUB_V2", "222_hello_world")
t.Setenv("TEST_V2_SUB_V3", "1min4s")
t.Setenv("TEST_V2_SUB_V4", "2009-11-10T23:00:00Z")
t.Setenv("TEST_V3_SUB_V1", "33846")
t.Setenv("TEST_V3_SUB_V2", "33_hello_world")
t.Setenv("TEST_V3_SUB_V3", "33min4s")
t.Setenv("TEST_V3_SUB_V4", "2033-11-10T23:00:00Z")
t.Setenv("SUB_V1", "11")
t.Setenv("SUB_V2", "22")
t.Setenv("SUB_V3", "33min")
t.Setenv("SUB_V4", "2044-01-01T00:00:00Z")
err := ApplyEnvOverrides(&data, "_")
if err != nil {
t.Errorf("%v", err)
t.FailNow()
}
assertEqual(t, data.V1, 999)
assertEqual(t, data.VX, "2")
assertEqual(t, data.V5, "no")
assertEqual(t, data.Sub1.V1, 3)
assertEqual(t, data.Sub1.VX, "4")
assertEqual(t, data.Sub1.V2, "5")
assertEqual(t, data.Sub1.V8, time.Second*6)
assertEqual(t, data.Sub1.V9, time.Unix(947206861, 0).UTC())
assertEqual(t, data.Sub2.V1, 846)
assertEqual(t, data.Sub2.VX, "9")
assertEqual(t, data.Sub2.V2, "222_hello_world")
assertEqual(t, data.Sub2.V8, time.Second*64)
assertEqual(t, data.Sub2.V9, time.Unix(1257894000, 0).UTC())
assertEqual(t, data.Sub3.V1, 33846)
assertEqual(t, data.Sub3.VX, "14")
assertEqual(t, data.Sub3.V2, "33_hello_world")
assertEqual(t, data.Sub3.V8, time.Second*1984)
assertEqual(t, data.Sub3.V9, time.Unix(2015276400, 0).UTC())
assertEqual(t, data.Sub4.V1, 11)
assertEqual(t, data.Sub4.VX, "19")
assertEqual(t, data.Sub4.V2, "22")
assertEqual(t, data.Sub4.V8, time.Second*1980)
assertEqual(t, data.Sub4.V9, time.Unix(2335219200, 0).UTC())
}
func assertEqual[T comparable](t *testing.T, actual T, expected T) { func assertEqual[T comparable](t *testing.T, actual T, expected T) {
if actual != expected { if actual != expected {
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected)

View File

@@ -179,6 +179,101 @@ func (ph PassHash) Upgrade(plainpass string) (PassHash, error) {
} }
} }
func (ph PassHash) ClearTOTP() (PassHash, error) {
version, _, _, _, _, valid := ph.Data()
if !valid {
return "", errors.New("invalid PassHash")
}
if version == 0 {
return ph, nil
}
if version == 1 {
return ph, nil
}
if version == 2 {
return ph, nil
}
if version == 3 {
split := strings.Split(string(ph), "|")
split[3] = "0"
return PassHash(strings.Join(split, "|")), nil
}
if version == 4 {
split := strings.Split(string(ph), "|")
split[2] = "0"
return PassHash(strings.Join(split, "|")), nil
}
return "", errors.New("unknown version")
}
func (ph PassHash) WithTOTP(totpSecret []byte) (PassHash, error) {
version, _, _, _, _, valid := ph.Data()
if !valid {
return "", errors.New("invalid PassHash")
}
if version == 0 {
return "", errors.New("version does not support totp, needs upgrade")
}
if version == 1 {
return "", errors.New("version does not support totp, needs upgrade")
}
if version == 2 {
return "", errors.New("version does not support totp, needs upgrade")
}
if version == 3 {
split := strings.Split(string(ph), "|")
split[3] = hex.EncodeToString(totpSecret)
return PassHash(strings.Join(split, "|")), nil
}
if version == 4 {
split := strings.Split(string(ph), "|")
split[2] = hex.EncodeToString(totpSecret)
return PassHash(strings.Join(split, "|")), nil
}
return "", errors.New("unknown version")
}
func (ph PassHash) Change(newPlainPass string) (PassHash, error) {
version, _, _, hastotp, totpsecret, valid := ph.Data()
if !valid {
return "", errors.New("invalid PassHash")
}
if version == 0 {
return HashPasswordV0(newPlainPass)
}
if version == 1 {
return HashPasswordV1(newPlainPass)
}
if version == 2 {
return HashPasswordV2(newPlainPass)
}
if version == 3 {
return HashPasswordV3(newPlainPass, langext.Conditional(hastotp, totpsecret, nil))
}
if version == 4 {
return HashPasswordV4(newPlainPass, langext.Conditional(hastotp, totpsecret, nil))
}
return "", errors.New("unknown version")
}
func (ph PassHash) String() string { func (ph PassHash) String() string {
return string(ph) return string(ph)
} }

View File

@@ -19,38 +19,38 @@ import (
// There are also a bunch of unit tests to ensure that the cache is always in a consistent state // There are also a bunch of unit tests to ensure that the cache is always in a consistent state
// //
type LRUMap[TData any] struct { type LRUMap[TKey comparable, TData any] struct {
maxsize int maxsize int
lock sync.Mutex lock sync.Mutex
cache map[string]*cacheNode[TData] cache map[TKey]*cacheNode[TKey, TData]
lfuHead *cacheNode[TData] lfuHead *cacheNode[TKey, TData]
lfuTail *cacheNode[TData] lfuTail *cacheNode[TKey, TData]
} }
type cacheNode[TData any] struct { type cacheNode[TKey comparable, TData any] struct {
key string key TKey
data TData data TData
parent *cacheNode[TData] parent *cacheNode[TKey, TData]
child *cacheNode[TData] child *cacheNode[TKey, TData]
} }
func NewLRUMap[TData any](size int) *LRUMap[TData] { func NewLRUMap[TKey comparable, TData any](size int) *LRUMap[TKey, TData] {
if size <= 2 && size != 0 { if size <= 2 && size != 0 {
panic("Size must be > 2 (or 0)") panic("Size must be > 2 (or 0)")
} }
return &LRUMap[TData]{ return &LRUMap[TKey, TData]{
maxsize: size, maxsize: size,
lock: sync.Mutex{}, lock: sync.Mutex{},
cache: make(map[string]*cacheNode[TData], size+1), cache: make(map[TKey]*cacheNode[TKey, TData], size+1),
lfuHead: nil, lfuHead: nil,
lfuTail: nil, lfuTail: nil,
} }
} }
func (c *LRUMap[TData]) Put(key string, value TData) { func (c *LRUMap[TKey, TData]) Put(key TKey, value TData) {
if c.maxsize == 0 { if c.maxsize == 0 {
return // cache disabled return // cache disabled
} }
@@ -68,7 +68,7 @@ func (c *LRUMap[TData]) Put(key string, value TData) {
} }
// key does not exist: insert into map and add to top of LFU // key does not exist: insert into map and add to top of LFU
node = &cacheNode[TData]{ node = &cacheNode[TKey, TData]{
key: key, key: key,
data: value, data: value,
parent: nil, parent: nil,
@@ -93,7 +93,7 @@ func (c *LRUMap[TData]) Put(key string, value TData) {
} }
} }
func (c *LRUMap[TData]) TryGet(key string) (TData, bool) { func (c *LRUMap[TKey, TData]) TryGet(key TKey) (TData, bool) {
if c.maxsize == 0 { if c.maxsize == 0 {
return *new(TData), false // cache disabled return *new(TData), false // cache disabled
} }
@@ -109,7 +109,7 @@ func (c *LRUMap[TData]) TryGet(key string) (TData, bool) {
return val.data, ok return val.data, ok
} }
func (c *LRUMap[TData]) moveNodeToTop(node *cacheNode[TData]) { func (c *LRUMap[TKey, TData]) moveNodeToTop(node *cacheNode[TKey, TData]) {
// (only called in critical section !) // (only called in critical section !)
if c.lfuHead == node { // fast case if c.lfuHead == node { // fast case
@@ -142,7 +142,7 @@ func (c *LRUMap[TData]) moveNodeToTop(node *cacheNode[TData]) {
} }
} }
func (c *LRUMap[TData]) Size() int { func (c *LRUMap[TKey, TData]) Size() int {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
return len(c.cache) return len(c.cache)

116
dataext/stack.go Normal file
View File

@@ -0,0 +1,116 @@
package dataext
import (
"errors"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"sync"
)
var ErrEmptyStack = errors.New("stack is empty")
type Stack[T any] struct {
lock *sync.Mutex
data []T
}
func NewStack[T any](threadsafe bool, initialCapacity int) *Stack[T] {
var lck *sync.Mutex = nil
if threadsafe {
lck = &sync.Mutex{}
}
return &Stack[T]{
lock: lck,
data: make([]T, 0, initialCapacity),
}
}
func (s *Stack[T]) Push(v T) {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
s.data = append(s.data, v)
}
func (s *Stack[T]) Pop() (T, error) {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return *new(T), ErrEmptyStack
}
result := s.data[l-1]
s.data = s.data[:l-1]
return result, nil
}
func (s *Stack[T]) OptPop() *T {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return nil
}
result := s.data[l-1]
s.data = s.data[:l-1]
return langext.Ptr(result)
}
func (s *Stack[T]) Peek() (T, error) {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return *new(T), ErrEmptyStack
}
return s.data[l-1], nil
}
func (s *Stack[T]) OptPeek() *T {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return nil
}
return langext.Ptr(s.data[l-1])
}
func (s *Stack[T]) Length() int {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
return len(s.data)
}
func (s *Stack[T]) Empty() bool {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
return len(s.data) == 0
}

View File

@@ -15,3 +15,35 @@ func Conditional[T any](v bool, resTrue T, resFalse T) T {
return resFalse return resFalse
} }
} }
func ConditionalFn00[T any](v bool, resTrue T, resFalse T) T {
if v {
return resTrue
} else {
return resFalse
}
}
func ConditionalFn10[T any](v bool, resTrue func() T, resFalse T) T {
if v {
return resTrue()
} else {
return resFalse
}
}
func ConditionalFn01[T any](v bool, resTrue T, resFalse func() T) T {
if v {
return resTrue
} else {
return resFalse()
}
}
func ConditionalFn11[T any](v bool, resTrue func() T, resFalse func() T) T {
if v {
return resTrue()
} else {
return resFalse()
}
}

View File

@@ -34,3 +34,7 @@ func IsNil(i interface{}) bool {
} }
return false return false
} }
func PtrEquals[T comparable](v1 *T, v2 *T) bool {
return (v1 == nil && v2 == nil) || (v1 != nil && v2 != nil && *v1 == *v2)
}

130
rext/wrapper.go Normal file
View File

@@ -0,0 +1,130 @@
package rext
import "regexp"
type Regex interface {
IsMatch(haystack string) bool
MatchFirst(haystack string) (RegexMatch, bool)
MatchAll(haystack string) []RegexMatch
ReplaceAll(haystack string, repl string, literal bool) string
ReplaceAllFunc(haystack string, repl func(string) string) string
RemoveAll(haystack string) string
GroupCount() int
}
type regexWrapper struct {
rex *regexp.Regexp
subnames []string
}
type RegexMatch struct {
haystack string
submatchesIndex []int
subnames []string
}
type RegexMatchGroup struct {
haystack string
start int
end int
}
func W(rex *regexp.Regexp) Regex {
return &regexWrapper{rex: rex, subnames: rex.SubexpNames()}
}
// ---------------------------------------------------------------------------------------------------------------------
func (w *regexWrapper) IsMatch(haystack string) bool {
return w.rex.MatchString(haystack)
}
func (w *regexWrapper) MatchFirst(haystack string) (RegexMatch, bool) {
res := w.rex.FindStringSubmatchIndex(haystack)
if res == nil {
return RegexMatch{}, false
}
return RegexMatch{haystack: haystack, submatchesIndex: res, subnames: w.subnames}, true
}
func (w *regexWrapper) MatchAll(haystack string) []RegexMatch {
resarr := w.rex.FindAllStringSubmatchIndex(haystack, -1)
matches := make([]RegexMatch, 0, len(resarr))
for _, res := range resarr {
matches = append(matches, RegexMatch{haystack: haystack, submatchesIndex: res, subnames: w.subnames})
}
return matches
}
func (w *regexWrapper) ReplaceAll(haystack string, repl string, literal bool) string {
if literal {
// do not expand placeholder aka $1, $2, ...
return w.rex.ReplaceAllLiteralString(haystack, repl)
} else {
return w.rex.ReplaceAllString(haystack, repl)
}
}
func (w *regexWrapper) ReplaceAllFunc(haystack string, repl func(string) string) string {
return w.rex.ReplaceAllStringFunc(haystack, repl)
}
func (w *regexWrapper) RemoveAll(haystack string) string {
return w.rex.ReplaceAllLiteralString(haystack, "")
}
// GroupCount returns the amount of groups in this match, does not count group-0 (whole match)
func (w *regexWrapper) GroupCount() int {
return len(w.subnames) - 1
}
// ---------------------------------------------------------------------------------------------------------------------
func (m RegexMatch) FullMatch() RegexMatchGroup {
return RegexMatchGroup{haystack: m.haystack, start: m.submatchesIndex[0], end: m.submatchesIndex[1]}
}
// GroupCount returns the amount of groups in this match, does not count group-0 (whole match)
func (m RegexMatch) GroupCount() int {
return len(m.subnames) - 1
}
// GroupByIndex returns the value of a matched group (group 0 == whole match)
func (m RegexMatch) GroupByIndex(idx int) RegexMatchGroup {
return RegexMatchGroup{haystack: m.haystack, start: m.submatchesIndex[idx*2], end: m.submatchesIndex[idx*2+1]}
}
// GroupByName returns the value of a matched group (group 0 == whole match)
func (m RegexMatch) GroupByName(name string) RegexMatchGroup {
for idx, subname := range m.subnames {
if subname == name {
return RegexMatchGroup{haystack: m.haystack, start: m.submatchesIndex[idx*2], end: m.submatchesIndex[idx*2+1]}
}
}
panic("failed to find regex-group by name")
}
// ---------------------------------------------------------------------------------------------------------------------
func (g RegexMatchGroup) Value() string {
return g.haystack[g.start:g.end]
}
func (g RegexMatchGroup) Start() int {
return g.start
}
func (g RegexMatchGroup) End() int {
return g.end
}
func (g RegexMatchGroup) Range() (int, int) {
return g.start, g.end
}
func (g RegexMatchGroup) Length() int {
return g.end - g.start
}