Initial import

This commit is contained in:
George Suntres
2026-03-29 10:32:25 -04:00
commit ebdf370c23
11 changed files with 515 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Allowlisting gitignore template for GO projects prevents us
# from adding various unwanted local files, such as generated
# files, developer configurations or IDE-specific files etc.
#
# Recommended: Go.AllowList.gitignore
# Ignore everything
*
# But these files...
!.gitignore
!*.go
!go.sum
!go.mod
!README.md
!LICENSE
!Makefile
!*.sh
!*.md
# ...even if they are in subdirectories
!*/

40
Makefile Normal file
View File

@@ -0,0 +1,40 @@
.PHONY: help
help: ## This help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
.DEFAULT_GOAL := help
GCFLAGS=
LDFLAGS_EXTRA=
clean:
rm coverage.out && true
lint:
go-critic check ./... || true
debug-test: ## Debug test with dlv
dlv test -- -test.v
connect:
dlv connect 127.0.0.1:36666
test:
go test \
-failfast \
-race ./... -v
test-watch:
gotestsum \
--watch \
-- \
-failfast \
./...
cover:
go test -coverprofile=coverage.out
go tool cover -func=coverage.out
@rm coverage.out
.PHONY: run

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module git.gsuntres.com/general/commons
go 1.25.0
require go.mongodb.org/mongo-driver/v2 v2.5.0

6
go.sum Normal file
View File

@@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=

2
main.go Normal file
View File

@@ -0,0 +1,2 @@
// Commons includes utility functions used across projects.
package commons

11
math.go Normal file
View File

@@ -0,0 +1,11 @@
package commons
// MathMin will compare two integers and return the one with the smaller value.
func MathMin[T ~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 | ~string](a, b T) T {
if a < b {
return a
}
return b
}

36
math_test.go Normal file
View File

@@ -0,0 +1,36 @@
package commons
import (
"fmt"
"testing"
)
func TestMathMin(t *testing.T) {
v := MathMin[int64](int64(15), int64(98))
if v != 15 {
t.Fatalf("Should have been 15 not %v", v)
}
}
func TestMathMin_Swap(t *testing.T) {
v := MathMin[int64](int64(100), int64(12))
if v != 12 {
t.Fatalf("Should have been 12 not %v", v)
}
}
func TestMathMin_String(t *testing.T) {
v := MathMin[string]("15", "152")
if v != "15" {
t.Fatalf("Should have been 15 not %v", v)
}
}
func ExampleMathMin() {
fmt.Printf("%d", MathMin[int64](int64(10), int64(20)))
// Output:
// 10
}

27
string_test.go Normal file
View File

@@ -0,0 +1,27 @@
package commons
import (
"testing"
)
func TestStringNormalize(t *testing.T) {
v := " some text here "
vv := StringNormalize(v)
if vv != "some text here" {
t.Fatal("Text should have been normalized")
}
}
func TestStringIsBlank(t *testing.T) {
if !StringIsBlank("") {
t.Fatal("Failed to check string is blank")
}
}
func TestStringIsNotBlank(t *testing.T) {
if !StringIsNotBlank("fasdfdas") {
t.Fatal("Failed to check string is not blank")
}
}

25
strings.go Normal file
View File

@@ -0,0 +1,25 @@
package commons
import (
"regexp"
"strings"
)
var whitespace = regexp.MustCompile(`\s+`)
// StringNormalize will replace arbitrary lengths of consecutive white space with a single one.
func StringNormalize(s string) string {
s = whitespace.ReplaceAllString(s, " ")
return strings.TrimSpace(s)
}
//StringIsBlank checks if a string is blank. Will trim the string before the check takes place.
func StringIsBlank(s string) bool {
return len(strings.TrimSpace(s)) == 0
}
// StringIsNotBlank check if a string is NOT blank.
func StringIsNotBlank(s string) bool {
return !StringIsBlank(s)
}

134
struct.go Normal file
View File

@@ -0,0 +1,134 @@
package commons
import (
"log"
"reflect"
"strings"
// "encoding/json"
"go.mongodb.org/mongo-driver/v2/bson"
)
// MapToStruct will convert a map[string]any to a struct.
func MapToStruct(m map[string]any, o any) error {
b, err := bson.Marshal(m)
if err != nil {
log.Printf("Failed marshal %v", err)
return err
}
err = bson.Unmarshal(b, o)
if err != nil {
log.Printf("Failed to unmarshal %v", err)
return err
}
return nil
}
// MapIsSubset given two map[string]any m1 and m2 will determine if m1 is a subset of m2.
// Only fields' name is evaluated not their values.
func MapIsSubset(subset, superset any) bool {
sub := subset.(map[string]any)
sup := superset.(map[string]any)
isSubset := true
for k, _ := range sub {
_, ok := sup[k]
if !ok {
isSubset = false
}
}
return isSubset
}
// MapIsSubsetOfStruct given a map[string]any and a struct will determine if the map is a subset of the struct.
// Only fields' name is evaluated not their values.
func MapIsSubsetOfStruct(m map[string]any, s any) bool {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
v = v.Elem() // Dereference if it's a pointer
}
// Ensure we are working with a struct
if v.Kind() != reflect.Struct {
return false
}
isSubset := true
t := v.Type()
for key := range m {
// FieldByName only finds exported fields
if _, found := t.FieldByName(key); !found {
isSubset = false
break
}
}
return isSubset
}
// StructHasJsonName determines if a struct has the given json tag name.
func StructHasJsonName(s any, targetName string) bool {
t := reflect.TypeOf(s)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("json")
// The name is the first part before any commas (e.g., "user_id,omitempty")
name := strings.Split(tag, ",")[0]
if name == targetName {
return true
}
}
return false
}
// StructHasJsonName determines if a struct has the given bson tag name.
func StructHasBsonName(s any, targetName string) bool {
t := reflect.TypeOf(s)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("bson")
// The name is the first part before any commas (e.g., "user_id,omitempty")
name := strings.Split(tag, ",")[0]
if name == targetName {
return true
}
}
return false
}
// StructCopyMatching copies the fields of one struct to another only if they have the same name and type.
func StructCopyMatching(source, target any) {
sVal := reflect.ValueOf(source).Elem()
tVal := reflect.ValueOf(target).Elem()
for i := 0; i < sVal.NumField(); i++ {
sField := sVal.Type().Field(i)
tField, ok := tVal.Type().FieldByName(sField.Name)
if ok && sField.Type == tField.Type {
tVal.FieldByName(sField.Name).Set(sVal.Field(i))
}
}
}

203
struct_test.go Normal file
View File

@@ -0,0 +1,203 @@
package commons
import (
"testing"
)
func TestMapToStruct(t *testing.T) {
o := map[string]any {
"name": "name1",
"age": 22,
"active": true,
}
type O struct {
Name string
Age int
Active bool
}
var oo O
if err := MapToStruct(o, &oo); err != nil {
t.Fatalf("1 Failed to convert %v", err)
}
if oo.Name != "name1" || oo.Age != 22 || !oo.Active {
t.Fatalf("2 Failed to convert %v", oo)
}
}
func TestMapIsSubset(t *testing.T) {
super := map[string]any {
"name": "name1",
"age": 22,
"active": "2",
}
sub := map[string]any {
"name": "a",
}
if !MapIsSubset(sub, super) {
t.Fatal("Should have indicated that map is a subset")
}
}
func TestMapIsSubset_Nosubset(t *testing.T) {
super := map[string]any {
"name": "name1",
"age": 22,
"active": "2",
}
sub := map[string]any {
"foo": "a",
}
if MapIsSubset(sub, super) {
t.Fatal("Should have indicated that map is a NOT subset")
}
}
func TestMapIsSubsetOfStruct_NoSubset(t *testing.T) {
type O struct {
Name string
Age int `json:"age"`
Notes string
}
oo := map[string]any{
"name": "A Name",
"age": 1,
"notes": "dsafda",
"other": "dd",
}
if MapIsSubsetOfStruct(oo, &O{}) {
t.Fatal("Should have indicated that map is a NOT subset")
}
}
func TestMapIsSubsetOfStruct(t *testing.T) {
type O struct {
Name string
Age int `json:"age"`
Notes string
}
oo := map[string]any{
"Name": "A Name",
}
if !MapIsSubsetOfStruct(oo, &O{}) {
t.Fatal("Should have indicated that map is a subset")
}
}
func TestMapIsSubsetOfStruct_SomeFields(t *testing.T) {
type O struct {
Name string
Age int `json:"age"`
Notes string
}
oo := map[string]any{
"Name": "A Name",
"Foo": "bar",
}
if MapIsSubsetOfStruct(oo, &O{}) {
t.Fatal("Should have indicated that map is a NOT subset")
}
}
func TestStructHasJsonName_True(t *testing.T) {
type O struct {
Name string
Age int `json:"age"`
Notes string
}
o := &O{
}
if !StructHasJsonName(o, "age") {
t.Fatal("False negative for json tag")
}
}
func TestStructHasJsonName_False(t *testing.T) {
type O struct {
Name string
Age int `json:"age"`
Notes string
}
o := &O{
}
if StructHasJsonName(o, "foo") {
t.Fatal("False positive for json tag")
}
}
func TestStructHasBsonName_True(t *testing.T) {
type O struct {
Name string
Age int `bson:"age"`
Notes string
}
o := &O{
}
if !StructHasBsonName(o, "age") {
t.Fatal("False negative for bson tag")
}
}
func TestStructHasBsonName_False(t *testing.T) {
type O struct {
Name string
Age int `bson:"age"`
Notes string
}
o := &O{
}
if StructHasBsonName(o, "foo") {
t.Fatal("False positive for bson tag")
}
}
func TestCopyMatching(t *testing.T) {
type O struct {
Name string
Age int
Notes string
}
type OO struct {
Name string
Age int
Active bool
}
from := &O{
Name: "Nick",
Age: 15,
Notes: "Some notes about nick",
}
var to OO
StructCopyMatching(from, &to)
if to.Name != "Nick" && to.Age != 15 && to.Active != true {
t.Fatalf("Failed to copy matching fields to %v", to)
}
}