pax_global_header00006660000000000000000000000064144345136340014521gustar00rootroot0000000000000052 comment=9a8c3bf4adb82700a5806ef21987323903fe604e golang-github-muhlemmer-gu-0.3.1/000077500000000000000000000000001443451363400166535ustar00rootroot00000000000000golang-github-muhlemmer-gu-0.3.1/.github/000077500000000000000000000000001443451363400202135ustar00rootroot00000000000000golang-github-muhlemmer-gu-0.3.1/.github/workflows/000077500000000000000000000000001443451363400222505ustar00rootroot00000000000000golang-github-muhlemmer-gu-0.3.1/.github/workflows/go.yml000066400000000000000000000007521443451363400234040ustar00rootroot00000000000000name: Go on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.18 - name: Build run: go build -v ./... - name: Test run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 golang-github-muhlemmer-gu-0.3.1/LICENSE000066400000000000000000000022731443451363400176640ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to golang-github-muhlemmer-gu-0.3.1/README.md000066400000000000000000000061031443451363400201320ustar00rootroot00000000000000# Generic Utilities [![Go Reference](https://pkg.go.dev/badge/github.com/muhlemmer/gu.svg)](https://pkg.go.dev/github.com/muhlemmer/gu) [![Go](https://github.com/muhlemmer/gu/actions/workflows/go.yml/badge.svg)](https://github.com/muhlemmer/gu/actions/workflows/go.yml) [![codecov](https://codecov.io/gh/muhlemmer/gu/branch/main/graph/badge.svg?token=I7UCR4XRV1)](https://codecov.io/gh/muhlemmer/gu) GU is a collection of Generic Utility functions, using Type Parameters featured in Go 1.18 and later. I often found myself writing boilerplate code for slices, maps, poitners etc. Since 1.18 I started using generics in some of my repositories and found that some functions often are the same between projects. The repository is a collection of those (utiltity) functions. Although the functions are pretty basic and *almost* don't justify putting them in a package, I share this code under the [unlicense](https://unlicense.org/), with the purpose: - Make my own life easier when reusing boiler plate code; - So that others can easily use these utilities; - People who want to learn more about generics in Go can read the code; ## Features There is no logic in which order I'm adding features. Ussualy when I see repetative code that can be generalized, it is dropped in here. Which means that there might be other utilities that seem to be missing. [Contributions](contributing) are welcome. Below features link to pkg.go.dev documentation where examples can be found. ### Pointers - [`Ptr`](https://pkg.go.dev/github.com/muhlemmer/gu#Ptr) allows for getting a direct pointer. For example from fuction returns: `t := gu.Ptr(time.Unix())` where `t := &time.Unix()` is illigal Go code. - [`Value`](https://pkg.go.dev/github.com/muhlemmer/gu#Value) safely returns a value through a pointer. When the pointer is `nil`, the zero value is returned without panic. ### Slices - [Transform](https://pkg.go.dev/github.com/muhlemmer/gu#InterfaceSlice) a slice of any type into a slice of interface (`[]T` to `[]interface{}`). - [Assert](https://pkg.go.dev/github.com/muhlemmer/gu#AssertInterfaces) a slice of interfaces to a slice of any type (`[]interface{}` to `[]T`). - [Transform](https://pkg.go.dev/github.com/muhlemmer/gu#Transform) slices of similar types that implement the [Transformer](https://pkg.go.dev/github.com/muhlemmer/gu#Transformer) interface. ### Maps - [Copy a map](https://pkg.go.dev/github.com/muhlemmer/gu#MapCopy). - [Copy a map](https://pkg.go.dev/github.com/muhlemmer/gu#MapCopyKeys) by certain keys only. - Check if two maps are [equal](https://pkg.go.dev/github.com/muhlemmer/gu#MapEqual). ## Contributing Open for Pull Requests. - In case of a bugfix, please clearly describe the issue and how to reproduce. Preferably a unit test that exposes the behaviour. - A new feature should be properly documented (godoc), added to REAMDE.md and fully unit tested. If the function seems to be abstract an example needs to be provided in the testfile (`ExampleXxx()` format) - All code needs to be `go fmt`ed Please note the [unlicense](LICENSE): you forfait all copyright when contributing to this repository. golang-github-muhlemmer-gu-0.3.1/go.mod000066400000000000000000000000501443451363400177540ustar00rootroot00000000000000module github.com/muhlemmer/gu go 1.18 golang-github-muhlemmer-gu-0.3.1/gu.go000066400000000000000000000004021443451363400176110ustar00rootroot00000000000000// Package gu provides Generic Utilities for the Go programming language. // These utilities are low in complexity, but I use them frequently in multiple projects. // This package depends on type parameter support, available in Go 1.18 and later. package gu golang-github-muhlemmer-gu-0.3.1/map.go000066400000000000000000000030731443451363400177620ustar00rootroot00000000000000package gu // MapEqual check if two maps have exactly the same content. // If both maps are nil, they are considered equal. // When a nil map is compared to an empty map, // they are not considered equal. func MapEqual[K, V comparable](a, b map[K]V) bool { if a == nil && b == nil { return true } if (a == nil && b != nil) || (b == nil && a != nil) { return false } if len(a) != len(b) { return false } for k, av := range a { if bv, ok := b[k]; !ok || av != bv { return false } } return true } // MapCopy copies all the entries of src into a new map. // Nil is returned when src is nil. // Note that if V is a pointer or reference type, // it is only shallow copied. func MapCopy[K comparable, V any](src map[K]V) map[K]V { if src == nil { return nil } dst := make(map[K]V, len(src)) for k, v := range src { dst[k] = v } return dst } // MapCopyKeys copies the entries or src, // identified by keys into a new map. // Nil is returned when src is nil. // // If no keys are provided and src is not nil, // an empty non-nil map is returned. // // Note that if V is a pointer or reference type, // it is only shallow copied. func MapCopyKeys[K comparable, V any](src map[K]V, keys ...K) map[K]V { if src == nil { return nil } dst := make(map[K]V, len(keys)) for _, k := range keys { if v, ok := src[k]; ok { dst[k] = v } } return dst } // MapMerge copies all entries from src in dst. // Any pre-existing keys in dst are overwritten. func MapMerge[K comparable, V any](src map[K]V, dst map[K]V) { for k, v := range src { dst[k] = v } } golang-github-muhlemmer-gu-0.3.1/map_test.go000066400000000000000000000050601443451363400210170ustar00rootroot00000000000000package gu import ( "testing" ) func TestMapEqual(t *testing.T) { tests := []struct { name string a map[int]int b map[int]int want bool }{ { "both nil", nil, nil, true, }, { "a nil", nil, map[int]int{ 1: 2, 3: 4, }, false, }, { "b nil", map[int]int{ 1: 2, 3: 4, }, nil, false, }, { "different length", map[int]int{ 1: 2, 3: 4, }, map[int]int{ 1: 2, 3: 4, 5: 6, }, false, }, { "different content", map[int]int{ 1: 2, 3: 4, }, map[int]int{ 3: 4, 5: 6, }, false, }, { "equal", map[int]int{ 5: 6, 1: 2, 3: 4, }, map[int]int{ 1: 2, 3: 4, 5: 6, }, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := MapEqual(tt.a, tt.b); got != tt.want { t.Errorf("MapEqual = %v, want %v", got, tt.want) } }) } } func TestMapCopy(t *testing.T) { tests := []struct { name string src map[int]int }{ { "nil", nil, }, { "empty", map[int]int{}, }, { "values", map[int]int{ 1: 2, 3: 4, 5: 6, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if dst := MapCopy(tt.src); !MapEqual(dst, tt.src) { t.Errorf("MapCopy =\n%v\nwant\n%v", dst, tt.src) } }) } } func TestMapCopyKeys(t *testing.T) { tests := []struct { name string src map[int]int keys []int want map[int]int }{ { "nil", nil, nil, nil, }, { "all empty", map[int]int{}, []int{}, map[int]int{}, }, { "keys nil", map[int]int{ 1: 2, 3: 4, 5: 6, }, nil, map[int]int{}, }, { "keys empty", map[int]int{ 1: 2, 3: 4, 5: 6, }, []int{}, map[int]int{}, }, { "subset", map[int]int{ 1: 2, 3: 4, 5: 6, }, []int{1, 5}, map[int]int{ 1: 2, 5: 6, }, }, { "superset", map[int]int{ 1: 2, 3: 4, 5: 6, }, []int{1, 5, 7, 9}, map[int]int{ 1: 2, 5: 6, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := MapCopyKeys(tt.src, tt.keys...); !MapEqual(got, tt.want) { t.Errorf("MapCopyKeys =\n%v\nwant\n%v", got, tt.want) } }) } } func TestMapMerge(t *testing.T) { src := map[int]int{ 1: 2, 3: 4, 5: 6, } dst := map[int]int{ 1: 99, 7: 8, } want := map[int]int{ 1: 2, 3: 4, 5: 6, 7: 8, } MapMerge(src, dst) if !MapEqual(dst, want) { t.Errorf("MapCopyKeys =\n%v\nwant\n%v", dst, want) } } golang-github-muhlemmer-gu-0.3.1/pointer.go000066400000000000000000000015661443451363400206720ustar00rootroot00000000000000package gu // Ptr returns a pointer to the passed value. // This helps where the ampersand can't be used directly, // such as on constants or function returns. func Ptr[T any](value T) (pointer *T) { return &value } // Value return the value behind pointer. // If the pointer is nil, the zero / empty value of T is returned. // This helps to safely access variables where it does not matter of the program // if they where nil or not, but you want to prevent a panic. // // Common use case is fields in gerated structs from frameworks such as // protobuf 2 or openapi 3. func Value[T any](pointer *T) (value T) { if pointer != nil { value = *pointer } return value } // PtrCopy copies a value behind pointer to a new pointer address. // Returns nil when the input is nil. func PtrCopy[T any](pointer *T) *T { if pointer == nil { return nil } return Ptr(Value(pointer)) } golang-github-muhlemmer-gu-0.3.1/pointer_test.go000066400000000000000000000041361443451363400217250ustar00rootroot00000000000000package gu import ( "fmt" "testing" ) func TestPtr(t *testing.T) { sp := Ptr("Hello world!") if sp == nil { t.Fatal("Ptr returned nil") } } func ExamplePtr() { // Pointer of a string // stringPointer := &"Hello world!": invalid operation: cannot take address of "Hello world!" (untyped string constant) stringPointer := Ptr("Hello world!") fmt.Printf("stringPointer is of type %T and points to value %v\n", stringPointer, *stringPointer) // Constant const i int64 = 22 // int64Pointer := &i: invalid operation: cannot take address of i (constant 22 of type int64) int64Pointer := Ptr(i) fmt.Printf("int64Pointer is of type %T and points to value %v\n", int64Pointer, *int64Pointer) // Function return // funcReturn := &fmt.Sprint(99): invalid operation: cannot take address of fmt.Sprint(99) (value of type string) funcReturn := Ptr(fmt.Sprint(99)) fmt.Printf("funcReturn is of type %T and points to value %v\n", funcReturn, *funcReturn) // Output: stringPointer is of type *string and points to value Hello world! // int64Pointer is of type *int64 and points to value 22 // funcReturn is of type *string and points to value 99 } func TestValue(t *testing.T) { tests := []struct { pointer *string wantValue string }{ { nil, "", }, { Ptr("foo"), "foo", }, } for _, tt := range tests { t.Run(fmt.Sprint(tt.pointer), func(t *testing.T) { if gotValue := Value(tt.pointer); gotValue != tt.wantValue { t.Errorf("Value() = %v, want %v", gotValue, tt.wantValue) } }) } } func ExampleValue() { type document struct { ID *int `json:"id,omitempty"` Description *string `json:"description,omitempty"` } d := document{ Description: Ptr("foobar"), } // this would panic, d.ID is nil // fmt.Println(*d.ID, *d.Description) // d.ID is nil, so a 0 is printed fmt.Println(Value(d.ID), Value(d.Description)) // Output: 0 foobar } func TestPtrCopy(t *testing.T) { got := PtrCopy[int](nil) if got != nil { t.Errorf("PtrCopy(): expected nil, got %v", got) } v := Ptr(7) got = PtrCopy(v) if got == v { t.Errorf("PtrCopy(): %v == %v", v, got) } } golang-github-muhlemmer-gu-0.3.1/slice.go000066400000000000000000000045571443451363400203140ustar00rootroot00000000000000package gu import "fmt" // InterfaceSlice transforms a slice of any type to a slice of interface{}. // This can be usefull when you have a slice of concrete types that has to be passed // to a function that takes a (variadic) slice of interface{}. func InterfaceSlice[T any](slice []T) []interface{} { out := make([]interface{}, len(slice)) for i := 0; i < len(slice); i++ { out[i] = slice[i] } return out } // AssertInterfaces asserts all members of the passed slice of interfaces // to the requested type and returs a slice of that type. // A nil slice allong with an error is returned // if one of the entries cannot be asserted to the destination type. func AssertInterfaces[T any](is []interface{}) ([]T, error) { out := make([]T, len(is)) for i := 0; i < len(is); i++ { var ok bool if out[i], ok = is[i].(T); !ok { return nil, fmt.Errorf("cannot assert %T of value %v to %T at index %d", is[i], is[i], out[i], i) } } return out, nil } // AssertInterfacesP is like AssertInterfaces, only that it does not // check for succesfull assertion and lets the runtime panic if assertion fails. // Usefull for inlining in cases where you are 100% sure of the concrete type of the passed slice. func AssertInterfacesP[T any](is []interface{}) []T { out := make([]T, len(is)) for i := 0; i < len(is); i++ { out[i] = is[i].(T) } return out } // Transform a slice of type 'A' to a slice of type 'B', // by calling transFunc for each entry. // // Usefull when working with slices of different, but similar, // struct types and you don't want to write the 'for' loops // over and over again. func Transform[A any, B any](as []A, transFunc func(A) B) []B { if as == nil { return nil } out := make([]B, len(as)) for i, a := range as { out[i] = transFunc(a) } return out } // TransformErr is similar to Transform, // but it uses a transFunc that can return an error. // // TranformErr will fail on the first error returned // and returns a wrapped error with index information, // along with a partial slice from previous succesfull operations. func TransformErr[A any, B any](as []A, transFunc func(A) (B, error)) ([]B, error) { if as == nil { return nil, nil } out := make([]B, 0, len(as)) for i, a := range as { b, err := transFunc(a) if err != nil { return out, fmt.Errorf("transform index %d: %w", i, err) } out = append(out, b) } return out, nil } golang-github-muhlemmer-gu-0.3.1/slice_test.go000066400000000000000000000132501443451363400213410ustar00rootroot00000000000000package gu import ( "fmt" "log" "reflect" "strconv" "strings" "testing" ) func TestInterfaceSlice(t *testing.T) { slice := []int{1, 2, 3, 4, 5} want := []interface{}{1, 2, 3, 4, 5} got := InterfaceSlice(slice) if !reflect.DeepEqual(got, want) { t.Errorf("InterfaceSlice =\n%v\nwant\n%v", got, want) } } func ExampleInterfaceSlice() { stringSlice := []string{"Hello", ", ", "World", "!"} // fmt.Print(stringSlice...): cannot use stringSlice (variable of type []string) as type []any in argument to fmt.Print fmt.Print(InterfaceSlice(stringSlice)...) // Output: Hello, World! } func TestAssertInterfaces(t *testing.T) { tests := []struct { name string is []interface{} want []int wantErr bool }{ { "success", []interface{}{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}, false, }, { "error", []interface{}{1, 2, 3, 4, "foo"}, nil, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := AssertInterfaces[int](tt.is) if (err != nil) != tt.wantErr { t.Errorf("AssertInterfaces() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("AssertInterfaces() = %v, want %v", got, tt.want) } }) } } func ExampleAssertInterfaces() { interfaceSlice := []interface{}{"Hello", "World!"} stringSlice, err := AssertInterfaces[string](interfaceSlice) if err != nil { log.Fatal(err) } s := strings.Join(stringSlice, ", ") fmt.Println(s) interfaceSlice = []interface{}{1, 1.1, "foobar"} intSlice, err := AssertInterfaces[int](interfaceSlice) if err != nil { fmt.Println(err) } fmt.Println(intSlice) // Output: Hello, World! // cannot assert float64 of value 1.1 to int at index 1 // [] } func TestAssertInterfacesP(t *testing.T) { tests := []struct { name string is []interface{} want []int wantErr bool }{ { "success", []interface{}{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}, false, }, { "error", []interface{}{1, 2, 3, 4, "foo"}, nil, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer func() { err, _ := recover().(error) if (err != nil) != tt.wantErr { t.Errorf("AssertInterfaces() error = %v, wantErr %v", err, tt.wantErr) } }() got := AssertInterfacesP[int](tt.is) if !reflect.DeepEqual(got, tt.want) { t.Errorf("AssertInterfaces() = %v, want %v", got, tt.want) } }) } } func ExampleAssertInterfacesP() { interfaceSlice := []interface{}{"Hello", "World!"} s := strings.Join(AssertInterfacesP[string](interfaceSlice), ", ") fmt.Println(s) // Output: Hello, World! } func TestTransform(t *testing.T) { type testA struct { ID int32 S []string } type testB struct { ID int64 S string } transFunc := func(a testA) testB { return testB{ ID: int64(a.ID), S: strings.Join(a.S, ", "), } } tests := []struct { name string as []testA want []testB }{ { "nil", nil, nil, }, { "entries", []testA{ { ID: 1, S: []string{"Hello", "World!"}, }, { ID: 2, S: []string{"foo", "bar"}, }, { ID: 3, S: []string{"spanac"}, }, }, []testB{ { ID: 1, S: "Hello, World!", }, { ID: 2, S: "foo, bar", }, { ID: 3, S: "spanac", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Transform(tt.as, transFunc); !reflect.DeepEqual(got, tt.want) { t.Errorf("Transform() = %v, want %v", got, tt.want) } }) } } func ExampleTransform_itoa() { in := []int{1, 2, 3, 4, 5} out := Transform(in, strconv.Itoa) fmt.Printf("out is of type %T and contains %v", out, out) // Output: out is of type []string and contains [1 2 3 4 5] } func ExampleTransform_struct() { type A struct { ID int32 S []string } type B struct { ID int64 S string } // define a tranformer function transFunc := func(a A) B { return B{ ID: int64(a.ID), S: strings.Join(a.S, ", "), } } in := []A{ { ID: 1, S: []string{"Hello", "World!"}, }, { ID: 2, S: []string{"foo", "bar"}, }, { ID: 3, S: []string{"spanac"}, }, } // create the transformed slice out := Transform(in, transFunc) fmt.Printf("out is of type %T and contains %v", out, out) // Output: out is of type []gu.B and contains [{1 Hello, World!} {2 foo, bar} {3 spanac}] } func TestTransformErr(t *testing.T) { tests := []struct { name string as []string want []int wantErr bool }{ { "nil", nil, nil, false, }, { "succes", []string{"1", "2", "3", "4", "5"}, []int{1, 2, 3, 4, 5}, false, }, { "error", []string{"1", "2", "foo", "4", "5"}, []int{1, 2}, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := TransformErr(tt.as, strconv.Atoi) if (err != nil) != tt.wantErr { t.Errorf("TransformErr() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("TransformErr() = %v, want %v", got, tt.want) } }) } } func ExampleTransformErr_atoi() { in := []string{"1", "2", "3", "4", "5"} out, err := TransformErr(in, strconv.Atoi) if err != nil { panic(err) } fmt.Printf("out is of type %T and contains %v\n", out, out) // this will cause an error in = []string{"1", "2", "foo", "4", "5"} out, err = TransformErr(in, strconv.Atoi) if err != nil { fmt.Println(err) } fmt.Printf("out is of type %T and contains %v\n", out, out) // Output: out is of type []int and contains [1 2 3 4 5] // transform index 2: strconv.Atoi: parsing "foo": invalid syntax // out is of type []int and contains [1 2] }