Initial commit of modash

This was taken from github.com/lmika/gopkgs/fp
This commit is contained in:
Leon Mika 2025-01-27 13:19:52 +11:00
commit a20530ddfd
20 changed files with 425 additions and 0 deletions

View file

@ -0,0 +1,23 @@
---
name: 'ci'
on:
push:
branches:
- main
jobs:
deploy:
runs-on: docker
steps:
- name: Cloning repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.22.4'
- name: Test
run: |
go test ./...

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# modash
A lodash-inspired utility package.

10
go.mod Normal file
View file

@ -0,0 +1,10 @@
module lmika.dev/pkg/modash
go 1.23.3
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

9
go.sum Normal file
View file

@ -0,0 +1,9 @@
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

30
momap/fromslice.go Normal file
View file

@ -0,0 +1,30 @@
package momap
func FromSliceGroups[T any, K comparable](ts []T, fn func(t T) K) map[K][]T {
kvs := make(map[K][]T)
for _, t := range ts {
k := fn(t)
kvs[k] = append(kvs[k], t)
}
return kvs
}
func FromSlice[T any, K comparable, V any](ts []T, fn func(t T) (K, V)) map[K]V {
m, _ := FromSliceWithError(ts, func(t T) (k K, v V, _ error) {
k, v = fn(t)
return k, v, nil
})
return m
}
func FromSliceWithError[T any, K comparable, V any](ts []T, fn func(t T) (K, V, error)) (map[K]V, error) {
kvs := make(map[K]V)
for _, t := range ts {
k, v, err := fn(t)
if err != nil {
return nil, err
}
kvs[k] = v
}
return kvs, nil
}

9
momap/keys.go Normal file
View file

@ -0,0 +1,9 @@
package momap
func Keys[K comparable, V any](m map[K]V) []K {
ks := make([]K, 0, len(m))
for k := range m {
ks = append(ks, k)
}
return ks
}

29
momap/toslice.go Normal file
View file

@ -0,0 +1,29 @@
package momap
func ToSlice[K comparable, V, T any](m map[K]V, fn func(k K, v V) T) []T {
if m == nil {
return nil
}
ts := make([]T, 0, len(m))
for k, v := range m {
ts = append(ts, fn(k, v))
}
return ts
}
func ToSliceWithError[K comparable, V, T any](m map[K]V, fn func(k K, v V) (T, error)) ([]T, error) {
if m == nil {
return nil, nil
}
ts := make([]T, 0, len(m))
for k, v := range m {
w, err := fn(k, v)
if err != nil {
return nil, err
}
ts = append(ts, w)
}
return ts, nil
}

28
momap/toslice_test.go Normal file
View file

@ -0,0 +1,28 @@
package momap_test
import (
"lmika.dev/pkg/modash/momap"
"testing"
"github.com/stretchr/testify/assert"
)
func TestToSlice(t *testing.T) {
type pair struct {
left int
right string
}
ms := map[int]string{
1: "one",
2: "two",
3: "three",
}
pairs := momap.ToSlice(ms, func(k int, v string) pair { return pair{k, v} })
assert.Len(t, pairs, 3)
assert.Contains(t, pairs, pair{1, "one"})
assert.Contains(t, pairs, pair{2, "two"})
assert.Contains(t, pairs, pair{3, "three"})
}

17
momap/values.go Normal file
View file

@ -0,0 +1,17 @@
package momap
func Values[K comparable, V any](m map[K]V) []V {
vs := make([]V, 0, len(m))
for _, v := range m {
vs = append(vs, v)
}
return vs
}
func MapValues[K comparable, V any, W any](m map[K]V, fn func(v V, k K) W) map[K]W {
ws := make(map[K]W)
for k, v := range m {
ws[k] = fn(v, k)
}
return ws
}

18
moslice/filter.go Normal file
View file

@ -0,0 +1,18 @@
package moslice
// Filter returns a slice containing all the elements of ts for which the passed in
// predicate returns true. If no items match the predicate, the function will return
// an empty slice. If ts is nil, the function will also return nil.
func Filter[T any](ts []T, predicate func(t T) bool) []T {
if ts == nil {
return nil
}
filteredTs := make([]T, 0)
for _, t := range ts {
if predicate(t) {
filteredTs = append(filteredTs, t)
}
}
return filteredTs
}

29
moslice/filter_test.go Normal file
View file

@ -0,0 +1,29 @@
package moslice_test
import (
"lmika.dev/pkg/modash/moslice"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFilter(t *testing.T) {
var (
ints = []int{1, 2, 3, 4, 5}
strs = []string{"foo", "bar", "baz"}
)
t.Run("should filter items matching the predicate", func(t *testing.T) {
assert.Equal(t, []int{2, 4}, moslice.Filter(ints, func(x int) bool { return x%2 == 0 }))
assert.Equal(t, []string{"bar", "baz"}, moslice.Filter(strs, func(x string) bool { return strings.Contains(x, "b") }))
})
t.Run("should moslice nil if the passed in slice is nil", func(t *testing.T) {
assert.Nil(t, moslice.Filter(nil, func(x int) bool { return x%2 == 0 }))
})
t.Run("should return empty slice if the passed in slice is empty slice", func(t *testing.T) {
assert.Equal(t, []int{}, moslice.Filter([]int{}, func(x int) bool { return x%2 == 0 }))
})
}

21
moslice/find.go Normal file
View file

@ -0,0 +1,21 @@
package moslice
func Contains[T comparable](ts []T, needle T) bool {
for _, t := range ts {
if t == needle {
return true
}
}
return false
}
func FindWhere[T comparable](ts []T, predicate func(t T) bool) (T, bool) {
var zeroT T
for _, t := range ts {
if predicate(t) {
return t, true
}
}
return zeroT, false
}

25
moslice/find_test.go Normal file
View file

@ -0,0 +1,25 @@
package moslice_test
import (
"lmika.dev/pkg/modash/moslice"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContains(t *testing.T) {
var (
ints = []int{1, 2, 3}
strs = []string{"a", "b", "c"}
)
t.Run("should find items in the slice", func(t *testing.T) {
assert.True(t, moslice.Contains(ints, 2))
assert.True(t, moslice.Contains(strs, "c"))
})
t.Run("should return false if items not in slice", func(t *testing.T) {
assert.False(t, moslice.Contains(ints, 131))
assert.False(t, moslice.Contains(strs, "bla"))
})
}

19
moslice/flatten.go Normal file
View file

@ -0,0 +1,19 @@
package moslice
func Flatten[T any](tss [][]T) []T {
if len(tss) == 0 {
return nil
}
entireLen := 0
for _, ts := range tss {
entireLen += len(ts)
}
newTs := make([]T, 0, entireLen)
for _, ts := range tss {
newTs = append(newTs, ts...)
}
return newTs
}

13
moslice/foreach.go Normal file
View file

@ -0,0 +1,13 @@
package moslice
// ForEachWithError runs the passed in function for each element of T. If an error
// is encountered, the error is returned immediately and any subsequence elements
// will not be processed.
func ForEachWithError[T any](ts []T, fn func(t T) error) error {
for _, t := range ts {
if err := fn(t); err != nil {
return err
}
}
return nil
}

33
moslice/map.go Normal file
View file

@ -0,0 +1,33 @@
package moslice
// Map returns a new slice containing the elements of ts transformed by the passed in function.
func Map[T, U any](ts []T, fn func(t T) U) (us []U) {
if ts == nil {
return nil
}
us = make([]U, len(ts))
for i, t := range ts {
us[i] = fn(t)
}
return us
}
// Map returns a new slice containing the elements of ts transformed by the passed in function, which
// can either either a mapped value of U, or an error. If the mapping function returns an error, MapWithError
// will return nil and the returned error.
func MapWithError[T, U any](ts []T, fn func(t T) (U, error)) (us []U, err error) {
if ts == nil {
return nil, nil
}
us = make([]U, len(ts))
for i, t := range ts {
var e error
us[i], e = fn(t)
if e != nil {
return nil, e
}
}
return us, nil
}

58
moslice/map_test.go Normal file
View file

@ -0,0 +1,58 @@
package moslice_test
import (
"errors"
"lmika.dev/pkg/modash/moslice"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMap(t *testing.T) {
t.Run("should return a mapped slice", func(t *testing.T) {
ts := []int{1, 2, 3}
us := moslice.Map(ts, func(x int) int { return x + 2 })
assert.Equal(t, []int{3, 4, 5}, us)
})
t.Run("should return nil if passed in nil", func(t *testing.T) {
ts := []int(nil)
us := moslice.Map(ts, func(x int) int { return x + 2 })
assert.Nil(t, us)
})
}
func TestMapWithError(t *testing.T) {
t.Run("should return a mapped slice with no error", func(t *testing.T) {
ts := []int{1, 2, 3}
us, err := moslice.MapWithError(ts, func(x int) (int, error) { return x + 2, nil })
assert.Equal(t, []int{3, 4, 5}, us)
assert.NoError(t, err)
})
t.Run("should return nil with an error when mapping function returns an error", func(t *testing.T) {
ts := []int{1, 2, 3}
us, err := moslice.MapWithError(ts, func(x int) (int, error) {
if x == 2 {
return 0, errors.New("bang")
}
return x + 2, nil
})
assert.Nil(t, us)
assert.Error(t, err)
})
t.Run("should return nil if passed in nil", func(t *testing.T) {
ts := []int(nil)
us, err := moslice.MapWithError(ts, func(x int) (int, error) { return x + 2, nil })
assert.Nil(t, us)
assert.NoError(t, err)
})
}

19
moslice/uniq.go Normal file
View file

@ -0,0 +1,19 @@
package moslice
func Uniq[T comparable](ts []T) []T {
if len(ts) < 2 {
return ts
}
outT := make([]T, 0)
seenT := make(map[T]struct{})
for _, t := range ts {
if _, ok := seenT[t]; !ok {
outT = append(outT, t)
seenT[t] = struct{}{}
}
}
return outT
}

31
moslice/uniq_test.go Normal file
View file

@ -0,0 +1,31 @@
package moslice_test
import (
"fmt"
"lmika.dev/pkg/modash/moslice"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUniq(t *testing.T) {
t.Run("should return a slice with unique elements", func(t *testing.T) {
scenarios := []struct {
in []int
want []int
}{
{in: nil, want: nil},
{in: []int{}, want: []int{}},
{in: []int{2}, want: []int{2}},
{in: []int{1, 2}, want: []int{1, 2}},
{in: []int{2, 2}, want: []int{2}},
{in: []int{3, 1, 4, 2, 3, 5, 1, 4}, want: []int{3, 1, 4, 2, 5}},
}
for i, s := range scenarios {
t.Run(fmt.Sprint(i), func(t *testing.T) {
assert.Equal(t, s.want, moslice.Uniq(s.in))
})
}
})
}