feat: add.

This commit is contained in:
tx7do
2023-11-06 14:11:20 +08:00
parent 83bb818ade
commit da82d442cc
20 changed files with 545 additions and 94 deletions

View File

@@ -1,4 +1,4 @@
package dateutils
package dateutil
import "time"

View File

@@ -1,21 +1,21 @@
package dateutils_test
package dateutil_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/dateutils"
"github.com/tx7do/go-utils/dateutil"
)
func TestFloor(t *testing.T) {
now := time.Now()
assert.Equal(t, "00:00:00", dateutils.Floor(now).Format("15:04:05"))
assert.Equal(t, "00:00:00", dateutil.Floor(now).Format("15:04:05"))
}
func TestCeil(t *testing.T) {
now := time.Now()
assert.Equal(t, "23:59:59", dateutils.Ceil(now).Format("15:04:05"))
assert.Equal(t, "23:59:59", dateutil.Ceil(now).Format("15:04:05"))
}
func TestBeforeOrEqual(t *testing.T) {
@@ -25,9 +25,9 @@ func TestBeforeOrEqual(t *testing.T) {
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
assert.Equal(t, true, dateutils.BeforeOrEqual(milestone, dBefore))
assert.Equal(t, true, dateutils.BeforeOrEqual(milestone, dEqual))
assert.Equal(t, false, dateutils.BeforeOrEqual(milestone, dAfter))
assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dBefore))
assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dEqual))
assert.Equal(t, false, dateutil.BeforeOrEqual(milestone, dAfter))
}
func TestAfterOrEqual(t *testing.T) {
@@ -37,9 +37,9 @@ func TestAfterOrEqual(t *testing.T) {
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
assert.Equal(t, false, dateutils.AfterOrEqual(milestone, dBefore))
assert.Equal(t, true, dateutils.AfterOrEqual(milestone, dEqual))
assert.Equal(t, true, dateutils.AfterOrEqual(milestone, dAfter))
assert.Equal(t, false, dateutil.AfterOrEqual(milestone, dBefore))
assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dEqual))
assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dAfter))
}
func TestOverlap(t *testing.T) {
@@ -52,8 +52,8 @@ func TestOverlap(t *testing.T) {
s3, _ := time.Parse("2006-01-02", "2023-01-02")
e3, _ := time.Parse("2006-01-02", "2023-01-04")
assert.Equal(t, true, dateutils.Overlap(s1, e1, s2, e2))
assert.Equal(t, false, dateutils.Overlap(s1, e1, s3, e3))
assert.Equal(t, true, dateutil.Overlap(s1, e1, s2, e2))
assert.Equal(t, false, dateutil.Overlap(s1, e1, s3, e3))
s4, _ := time.Parse("2006-01-02", "2023-07-13")
e4, _ := time.Parse("2006-01-02", "2023-07-14")
@@ -61,6 +61,6 @@ func TestOverlap(t *testing.T) {
s5, _ := time.Parse("2006-01-02", "2023-07-10")
e5, _ := time.Parse("2006-01-02", "2023-07-17")
assert.Equal(t, true, dateutils.Overlap(s4, e4, s5, e5))
assert.Equal(t, true, dateutils.Overlap(s5, e5, s4, e4))
assert.Equal(t, true, dateutil.Overlap(s4, e4, s5, e5))
assert.Equal(t, true, dateutil.Overlap(s5, e5, s4, e4))
}

1
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/tx7do/go-utils
go 1.20
require (
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.4.0
github.com/gosimple/slug v1.13.1
github.com/sony/sonyflake v1.2.0

2
go.sum
View File

@@ -1,6 +1,8 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=

12
ioutil/file.go Normal file
View File

@@ -0,0 +1,12 @@
package ioutil
import "os"
// ReadFile 读取文件
func ReadFile(path string) []byte {
content, err := os.ReadFile(path)
if err != nil {
return nil
}
return content
}

View File

@@ -1,8 +1,14 @@
package ioutil
import (
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/gobwas/glob"
)
// GetWorkingDirPath 获取工作路径
@@ -33,18 +39,6 @@ func GetAbsPath() string {
return dir
}
// PathExist 路径是否存在
func PathExist(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
// GetFileList 获取文件夹下面的所有文件的列表
func GetFileList(root string) []string {
var files []string
@@ -75,11 +69,211 @@ func GetFolderNameList(root string) []string {
return names
}
// ReadFile 读取文件
func ReadFile(path string) []byte {
content, err := os.ReadFile(path)
if err != nil {
return nil
// MatchPath Returns whether a given path matches a glob pattern.
//
// via github.com/gobwas/glob:
//
// Compile creates Glob for given pattern and strings (if any present after pattern) as separators.
// The pattern syntax is:
//
// pattern:
// { term }
//
// term:
// `*` matches any sequence of non-separator characters
// `**` matches any sequence of characters
// `?` matches any single non-separator character
// `[` [ `!` ] { character-range } `]`
// character class (must be non-empty)
// `{` pattern-list `}`
// pattern alternatives
// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
// `\` c matches character c
//
// character-range:
// c matches character c (c != `\\`, `-`, `]`)
// `\` c matches character c
// lo `-` hi matches character c for lo <= c <= hi
//
// pattern-list:
// pattern { `,` pattern }
// comma-separated (without spaces) patterns
func MatchPath(pattern string, path string) bool {
if g, err := glob.Compile(pattern); err == nil {
return g.Match(path)
}
return false
}
// ExpandUser replaces the tilde (~) in a path into the current user's home directory.
func ExpandUser(path string) (string, error) {
if u, err := user.Current(); err == nil {
fullTilde := fmt.Sprintf("~%s", u.Name)
if strings.HasPrefix(path, `~/`) || path == `~` {
return strings.Replace(path, `~`, u.HomeDir, 1), nil
}
if strings.HasPrefix(path, fullTilde+`/`) || path == fullTilde {
return strings.Replace(path, fullTilde, u.HomeDir, 1), nil
}
return path, nil
} else {
return path, err
}
}
// IsNonemptyExecutableFile Returns true if the given path is a regular file, is executable by any user, and has a non-zero size.
func IsNonemptyExecutableFile(path string) bool {
if stat, err := os.Stat(path); err == nil && stat.Size() > 0 && (stat.Mode().Perm()&0111) != 0 {
return true
}
return false
}
// IsNonemptyFile Returns true if the given path is a regular file with a non-zero size.
func IsNonemptyFile(path string) bool {
if FileExists(path) {
if stat, err := os.Stat(path); err == nil && stat.Size() > 0 {
return true
}
}
return false
}
// IsNonemptyDir Returns true if the given path is a directory with items in it.
func IsNonemptyDir(path string) bool {
if DirExists(path) {
if entries, err := ioutil.ReadDir(path); err == nil && len(entries) > 0 {
return true
}
}
return false
}
// Exists Returns true if the given path exists.
func Exists(path string) bool {
if _, err := os.Stat(path); err == nil {
return true
}
return false
}
// LinkExists Returns true if the given path exists and is a symbolic link.
func LinkExists(path string) bool {
if stat, err := os.Stat(path); err == nil {
return IsSymlink(stat.Mode())
}
return false
}
// FileExists Returns true if the given path exists and is a regular file.
func FileExists(path string) bool {
if stat, err := os.Stat(path); err == nil {
return stat.Mode().IsRegular()
}
return false
}
// DirExists Returns true if the given path exists and is a directory.
func DirExists(path string) bool {
if stat, err := os.Stat(path); err == nil {
return stat.IsDir()
}
return false
}
// PathExist 路径是否存在
func PathExist(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
func IsDevice(mode os.FileMode) bool {
return mode&os.ModeDevice != 0
}
func IsCharDevice(mode os.FileMode) bool {
return mode&os.ModeCharDevice != 0
}
func IsNamedPipe(mode os.FileMode) bool {
return mode&os.ModeNamedPipe != 0
}
func IsSocket(mode os.FileMode) bool {
return mode&os.ModeSocket != 0
}
func IsSticky(mode os.FileMode) bool {
return mode&os.ModeSticky != 0
}
func IsSetuid(mode os.FileMode) bool {
return mode&os.ModeSetuid != 0
}
func IsSetgid(mode os.FileMode) bool {
return mode&os.ModeSetgid != 0
}
func IsTemporary(mode os.FileMode) bool {
return mode&os.ModeTemporary != 0
}
func IsExclusive(mode os.FileMode) bool {
return mode&os.ModeExclusive != 0
}
func IsAppend(mode os.FileMode) bool {
return mode&os.ModeAppend != 0
}
// IsReadable Returns true if the given file can be opened for reading by the current user.
func IsReadable(filename string) bool {
if f, err := os.OpenFile(filename, os.O_RDONLY, 0); err == nil {
defer f.Close()
return true
} else {
return false
}
}
// IsWritable Returns true if the given file can be opened for writing by the current user.
func IsWritable(filename string) bool {
if f, err := os.OpenFile(filename, os.O_WRONLY, 0); err == nil {
defer f.Close()
return true
} else {
return false
}
}
// IsAppendable Returns true if the given file can be opened for appending by the current user.
func IsAppendable(filename string) bool {
if f, err := os.OpenFile(filename, os.O_APPEND, 0); err == nil {
defer f.Close()
return true
} else {
return false
}
return content
}

101
ioutil/path_test.go Normal file
View File

@@ -0,0 +1,101 @@
package ioutil
import (
"os"
"os/user"
"testing"
"github.com/stretchr/testify/require"
)
func TestExpandUser(t *testing.T) {
assert := require.New(t)
var v string
var err error
u, _ := user.Current()
v, err = ExpandUser(`/dev/null`)
assert.Equal(v, `/dev/null`)
assert.Nil(err)
v, err = ExpandUser(`~`)
assert.Equal(v, u.HomeDir)
assert.Nil(err)
v, err = ExpandUser("~" + u.Name)
assert.Equal(v, u.HomeDir)
assert.Nil(err)
v, err = ExpandUser("~/test-123")
assert.Equal(v, u.HomeDir+"/test-123")
assert.Nil(err)
v, err = ExpandUser("~" + u.Name + "/test-123")
assert.Equal(v, u.HomeDir+"/test-123")
assert.Nil(err)
v, err = ExpandUser("~/test-123/~/123")
assert.Equal(v, u.HomeDir+"/test-123/~/123")
assert.Nil(err)
v, err = ExpandUser("~" + u.Name + "/test-123/~" + u.Name + "/123")
assert.Equal(v, u.HomeDir+"/test-123/~"+u.Name+"/123")
assert.Nil(err)
assert.False(IsNonemptyFile(`/nonexistent.txt`))
assert.False(IsNonemptyDir(`/nonexistent/dir`))
assert.True(IsNonemptyFile(`/etc/hosts`))
assert.True(IsNonemptyDir(`/etc`))
x, err := os.Executable()
assert.NoError(err)
assert.True(IsNonemptyExecutableFile(x))
}
func TestMatchPath(t *testing.T) {
require.True(t, MatchPath(`**`, `/hello/there.txt`))
require.True(t, MatchPath(`*.txt`, `/hello/there.txt`))
require.True(t, MatchPath(`**/*.txt`, `/hello/there.txt`))
require.False(t, MatchPath(`**/*.txt`, `/hello/there.jpg`))
require.True(t, MatchPath("* ?at * eyes", "my cat has very bright eyes"))
require.True(t, MatchPath("", ""))
require.False(t, MatchPath("", "b"))
require.True(t, MatchPath("*ä", "åä"))
require.True(t, MatchPath("abc", "abc"))
require.True(t, MatchPath("a*c", "abc"))
require.True(t, MatchPath("a*c", "a12345c"))
require.True(t, MatchPath("a?c", "a1c"))
require.True(t, MatchPath("?at", "cat"))
require.True(t, MatchPath("?at", "fat"))
require.True(t, MatchPath("*", "abc"))
require.True(t, MatchPath(`\*`, "*"))
require.False(t, MatchPath("?at", "at"))
require.True(t, MatchPath("*test", "this is a test"))
require.True(t, MatchPath("this*", "this is a test"))
require.True(t, MatchPath("*is *", "this is a test"))
require.True(t, MatchPath("*is*a*", "this is a test"))
require.True(t, MatchPath("**test**", "this is a test"))
require.True(t, MatchPath("**is**a***test*", "this is a test"))
require.False(t, MatchPath("*is", "this is a test"))
require.False(t, MatchPath("*no*", "this is a test"))
require.True(t, MatchPath("[!a]*", "this is a test3"))
require.True(t, MatchPath("*abc", "abcabc"))
require.True(t, MatchPath("**abc", "abcabc"))
require.True(t, MatchPath("???", "abc"))
require.True(t, MatchPath("?*?", "abc"))
require.True(t, MatchPath("?*?", "ac"))
require.False(t, MatchPath("sta", "stagnation"))
require.True(t, MatchPath("sta*", "stagnation"))
require.False(t, MatchPath("sta?", "stagnation"))
require.False(t, MatchPath("sta?n", "stagnation"))
require.True(t, MatchPath("{abc,def}ghi", "defghi"))
require.True(t, MatchPath("{abc,abcd}a", "abcda"))
require.True(t, MatchPath("{a,ab}{bc,f}", "abc"))
require.True(t, MatchPath("{*,**}{a,b}", "ab"))
require.False(t, MatchPath("{*,**}{a,b}", "ac"))
require.True(t, MatchPath("/{rate,[a-z][a-z][a-z]}*", "/rate"))
require.True(t, MatchPath("/{rate,[0-9][0-9][0-9]}*", "/rate"))
require.True(t, MatchPath("/{rate,[a-z][a-z][a-z]}*", "/usd"))
}

View File

@@ -1,7 +1,7 @@
// This package includes utility functions for handling and manipulating a slice.
// It draws inspiration from JavaScript and Python and uses Go generics as a basis.
package sliceutils
package sliceutil
import (
"golang.org/x/exp/constraints"

View File

@@ -1,4 +1,4 @@
package sliceutils_test
package sliceutil_test
import (
"strconv"
@@ -6,7 +6,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/sliceutils"
"github.com/tx7do/go-utils/sliceutil"
)
type MyInt int
@@ -23,7 +23,7 @@ var lastNames = []string{"Jacobs", "Vin", "Jacobs", "Smith"}
func TestFilter(t *testing.T) {
expectedResult := []int{0, 2, 4, 6, 8}
actualResult := sliceutils.Filter(numerals, func(value int, _ int, _ []int) bool {
actualResult := sliceutil.Filter(numerals, func(value int, _ int, _ []int) bool {
return value%2 == 0
})
assert.Equal(t, expectedResult, actualResult)
@@ -31,7 +31,7 @@ func TestFilter(t *testing.T) {
func TestForEach(t *testing.T) {
result := 0
sliceutils.ForEach(numerals, func(value int, _ int, _ []int) {
sliceutil.ForEach(numerals, func(value int, _ int, _ []int) {
result += value
})
assert.Equal(t, 45, result)
@@ -39,23 +39,23 @@ func TestForEach(t *testing.T) {
func TestMap(t *testing.T) {
expectedResult := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
actualResult := sliceutils.Map(numerals, func(value int, _ int, _ []int) string {
actualResult := sliceutil.Map(numerals, func(value int, _ int, _ []int) string {
return strconv.Itoa(value)
})
assert.Equal(t, expectedResult, actualResult)
assert.Nil(t, sliceutils.Map([]int{}, func(_ int, _ int, _ []int) string {
assert.Nil(t, sliceutil.Map([]int{}, func(_ int, _ int, _ []int) string {
return ""
}))
assert.Nil(t, sliceutils.Map([]int(nil), func(_ int, _ int, _ []int) string {
assert.Nil(t, sliceutil.Map([]int(nil), func(_ int, _ int, _ []int) string {
return ""
}))
}
func TestReduce(t *testing.T) {
expectedResult := map[string]string{"result": "0123456789"}
actualResult := sliceutils.Reduce(
actualResult := sliceutil.Reduce(
numerals,
func(acc map[string]string, cur int, _ int, _ []int) map[string]string {
acc["result"] += strconv.Itoa(cur)
@@ -68,139 +68,139 @@ func TestReduce(t *testing.T) {
func TestFind(t *testing.T) {
expectedResult := "Wednesday"
actualResult := sliceutils.Find(days, func(value string, index int, slice []string) bool {
actualResult := sliceutil.Find(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Wed")
})
assert.Equal(t, expectedResult, *actualResult)
assert.Nil(t, sliceutils.Find(days, func(value string, index int, slice []string) bool {
assert.Nil(t, sliceutil.Find(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Rishon")
}))
}
func TestFindIndex(t *testing.T) {
expectedResult := 3
actualResult := sliceutils.FindIndex(days, func(value string, index int, slice []string) bool {
actualResult := sliceutil.FindIndex(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Wed")
})
assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindIndex(days, func(value string, index int, slice []string) bool {
assert.Equal(t, -1, sliceutil.FindIndex(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Rishon")
}))
}
func TestFindIndexOf(t *testing.T) {
expectedResult := 3
actualResult := sliceutils.FindIndexOf(days, "Wednesday")
actualResult := sliceutil.FindIndexOf(days, "Wednesday")
assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindIndexOf(days, "Rishon"))
assert.Equal(t, -1, sliceutil.FindIndexOf(days, "Rishon"))
}
func TestFindLastIndex(t *testing.T) {
expectedResult := 2
actualResult := sliceutils.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
actualResult := sliceutil.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
return value == "Jacobs"
})
assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
assert.Equal(t, -1, sliceutil.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
return value == "Hamudi"
}))
}
func TestFindLastIndexOf(t *testing.T) {
expectedResult := 2
actualResult := sliceutils.FindLastIndexOf(lastNames, "Jacobs")
actualResult := sliceutil.FindLastIndexOf(lastNames, "Jacobs")
assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindLastIndexOf(lastNames, "Hamudi"))
assert.Equal(t, -1, sliceutil.FindLastIndexOf(lastNames, "Hamudi"))
}
func TestFindIndexes(t *testing.T) {
expectedResult := []int{0, 2}
actualResult := sliceutils.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
actualResult := sliceutil.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
return value == "Jacobs"
})
assert.Equal(t, expectedResult, actualResult)
assert.Nil(t, sliceutils.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
assert.Nil(t, sliceutil.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
return value == "Hamudi"
}))
}
func TestFindIndexesOf(t *testing.T) {
expectedResult := []int{0, 2}
actualResult := sliceutils.FindIndexesOf(lastNames, "Jacobs")
actualResult := sliceutil.FindIndexesOf(lastNames, "Jacobs")
assert.Equal(t, expectedResult, actualResult)
assert.Nil(t, sliceutils.FindIndexesOf(lastNames, "Hamudi"))
assert.Nil(t, sliceutil.FindIndexesOf(lastNames, "Hamudi"))
}
func TestIncludes(t *testing.T) {
assert.True(t, sliceutils.Includes(numerals, 1))
assert.False(t, sliceutils.Includes(numerals, 11))
assert.True(t, sliceutil.Includes(numerals, 1))
assert.False(t, sliceutil.Includes(numerals, 11))
}
func TestAny(t *testing.T) {
assert.True(t, sliceutils.Some(numerals, func(value int, _ int, _ []int) bool {
assert.True(t, sliceutil.Some(numerals, func(value int, _ int, _ []int) bool {
return value%5 == 0
}))
assert.False(t, sliceutils.Some(numerals, func(value int, _ int, _ []int) bool {
assert.False(t, sliceutil.Some(numerals, func(value int, _ int, _ []int) bool {
return value == 11
}))
}
func TestAll(t *testing.T) {
assert.True(t, sliceutils.Every([]int{1, 1, 1}, func(value int, _ int, _ []int) bool {
assert.True(t, sliceutil.Every([]int{1, 1, 1}, func(value int, _ int, _ []int) bool {
return value == 1
}))
assert.False(t, sliceutils.Every([]int{1, 1, 1, 2}, func(value int, _ int, _ []int) bool {
assert.False(t, sliceutil.Every([]int{1, 1, 1, 2}, func(value int, _ int, _ []int) bool {
return value == 1
}))
}
func TestMerge(t *testing.T) {
result := sliceutils.Merge(numerals[:5], numerals[5:])
result := sliceutil.Merge(numerals[:5], numerals[5:])
assert.Equal(t, numerals, result)
assert.Nil(t, sliceutils.Merge([]int(nil), []int(nil)))
assert.Nil(t, sliceutils.Merge([]int{}, []int{}))
assert.Nil(t, sliceutil.Merge([]int(nil), []int(nil)))
assert.Nil(t, sliceutil.Merge([]int{}, []int{}))
}
func TestSum(t *testing.T) {
result := sliceutils.Sum(numerals)
result := sliceutil.Sum(numerals)
assert.Equal(t, 45, result)
}
func TestSum2(t *testing.T) {
result := sliceutils.Sum(numeralsWithUserDefinedType)
result := sliceutil.Sum(numeralsWithUserDefinedType)
assert.Equal(t, MyInt(45), result)
}
func TestRemove(t *testing.T) {
testSlice := []int{1, 2, 3}
result := sliceutils.Remove(testSlice, 1)
result := sliceutil.Remove(testSlice, 1)
assert.Equal(t, []int{1, 3}, result)
assert.Equal(t, []int{1, 2, 3}, testSlice)
result = sliceutils.Remove(result, 1)
result = sliceutil.Remove(result, 1)
assert.Equal(t, []int{1}, result)
result = sliceutils.Remove(result, 3)
result = sliceutil.Remove(result, 3)
assert.Equal(t, []int{1}, result)
result = sliceutils.Remove(result, 0)
result = sliceutil.Remove(result, 0)
assert.Equal(t, []int{}, result)
result = sliceutils.Remove(result, 1)
result = sliceutil.Remove(result, 1)
assert.Equal(t, []int{}, result)
}
func TestCopy(t *testing.T) {
testSlice := []int{1, 2, 3}
copiedSlice := sliceutils.Copy(testSlice)
copiedSlice := sliceutil.Copy(testSlice)
copiedSlice[0] = 2
assert.NotEqual(t, testSlice, copiedSlice)
}
func TestInsert(t *testing.T) {
testSlice := []int{1, 2}
result := sliceutils.Insert(testSlice, 0, 3)
result := sliceutil.Insert(testSlice, 0, 3)
assert.Equal(t, []int{3, 1, 2}, result)
assert.NotEqual(t, testSlice, result)
assert.Equal(t, []int{1, 3, 2}, sliceutils.Insert(testSlice, 1, 3))
assert.Equal(t, []int{1, 2, 3}, sliceutils.Insert(testSlice, 2, 3))
assert.Equal(t, []int{1, 3, 2}, sliceutil.Insert(testSlice, 1, 3))
assert.Equal(t, []int{1, 2, 3}, sliceutil.Insert(testSlice, 2, 3))
}
func TestIntersection(t *testing.T) {
@@ -210,7 +210,7 @@ func TestIntersection(t *testing.T) {
second := []int{2, 3, 4, 5, 6}
third := []int{3, 4, 5, 6, 7}
assert.Equal(t, expectedResult, sliceutils.Intersection(first, second, third))
assert.Equal(t, expectedResult, sliceutil.Intersection(first, second, third))
}
func TestDifference(t *testing.T) {
@@ -220,7 +220,7 @@ func TestDifference(t *testing.T) {
second := []int{2, 3, 4, 5, 6}
third := []int{3, 4, 5, 6, 7}
assert.Equal(t, expectedResult, sliceutils.Difference(first, second, third))
assert.Equal(t, expectedResult, sliceutil.Difference(first, second, third))
}
func TestUnion(t *testing.T) {
@@ -230,24 +230,24 @@ func TestUnion(t *testing.T) {
second := []int{2, 3, 4, 5, 6}
third := []int{3, 4, 5, 6, 7}
assert.Equal(t, expectedResult, sliceutils.Union(first, second, third))
assert.Equal(t, expectedResult, sliceutil.Union(first, second, third))
}
func TestReverse(t *testing.T) {
expectedResult := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
assert.Equal(t, expectedResult, sliceutils.Reverse(numerals))
assert.Equal(t, expectedResult, sliceutil.Reverse(numerals))
// ensure does not modify the original
assert.Equal(t, expectedResult, sliceutils.Reverse(numerals))
assert.Equal(t, expectedResult, sliceutil.Reverse(numerals))
// test basic odd length case
expectedResult = []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
assert.Equal(t, expectedResult, sliceutils.Reverse(numerals[1:]))
assert.Equal(t, expectedResult, sliceutil.Reverse(numerals[1:]))
}
func TestUnique(t *testing.T) {
duplicates := []int{6, 6, 6, 9, 0, 0, 0}
expectedResult := []int{6, 9, 0}
assert.Equal(t, expectedResult, sliceutils.Unique(duplicates))
assert.Equal(t, expectedResult, sliceutil.Unique(duplicates))
// Ensure original is unaltered
assert.NotEqual(t, expectedResult, duplicates)
}
@@ -255,8 +255,8 @@ func TestUnique(t *testing.T) {
func TestChunk(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
assert.Equal(t, [][]int{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}, sliceutils.Chunk(numbers, 2))
assert.Equal(t, [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10}}, sliceutils.Chunk(numbers, 3))
assert.Equal(t, [][]int{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}, sliceutil.Chunk(numbers, 2))
assert.Equal(t, [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10}}, sliceutil.Chunk(numbers, 3))
}
func TestPluck(t *testing.T) {
@@ -271,10 +271,10 @@ func TestPluck(t *testing.T) {
},
}
assert.Equal(t, []string{"azer", "tyuio"}, sliceutils.Pluck(items, func(item Pluckable) *string {
assert.Equal(t, []string{"azer", "tyuio"}, sliceutil.Pluck(items, func(item Pluckable) *string {
return &item.Code
}))
assert.Equal(t, []string{"Azer", "Tyuio"}, sliceutils.Pluck(items, func(item Pluckable) *string {
assert.Equal(t, []string{"Azer", "Tyuio"}, sliceutil.Pluck(items, func(item Pluckable) *string {
return &item.Value
}))
}
@@ -287,15 +287,15 @@ func TestFlatten(t *testing.T) {
{9, 10, 11},
}
flattened := sliceutils.Flatten(items)
flattened := sliceutil.Flatten(items)
assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, flattened)
assert.Nil(t, sliceutils.Flatten([][]int{}))
assert.Nil(t, sliceutils.Flatten([][]int(nil)))
assert.Nil(t, sliceutil.Flatten([][]int{}))
assert.Nil(t, sliceutil.Flatten([][]int(nil)))
assert.Nil(t, sliceutils.Flatten([][]int{{}, {}}))
assert.Nil(t, sliceutils.Flatten([][]int{nil, nil}))
assert.Nil(t, sliceutil.Flatten([][]int{{}, {}}))
assert.Nil(t, sliceutil.Flatten([][]int{nil, nil}))
assert.Nil(t, sliceutils.Flatten([][]int{{}, nil}))
assert.Nil(t, sliceutil.Flatten([][]int{{}, nil}))
}

37
structutil/structutils.go Normal file
View File

@@ -0,0 +1,37 @@
package structutil
import "reflect"
// ForEach given a struct, calls the passed in function with each visible struct field's name, value and tag.
func ForEach[T any](structInstance T, function func(key string, value any, tag reflect.StructTag)) {
typeOf := reflect.TypeOf(structInstance)
valueOf := reflect.ValueOf(structInstance)
fields := reflect.VisibleFields(typeOf)
for _, field := range fields {
value := valueOf.FieldByName(field.Name)
function(field.Name, value.Interface(), field.Tag)
}
}
// ToMap given a struct, converts it to a map[string]any.
// This function also takes struct tag names as optional parameters - if passed in, the struct tags will be used to remap or omit values.
func ToMap[T any](structInstance T, structTags ...string) map[string]any {
output := make(map[string]any, 0)
ForEach(structInstance, func(key string, value any, tag reflect.StructTag) {
if tag != "" {
for _, s := range structTags {
if tagValue, isPresent := tag.Lookup(s); isPresent && tagValue != "" {
key = tagValue
break
}
}
}
if key != "-" {
output[key] = value
}
})
return output
}

View File

@@ -0,0 +1,55 @@
package structutil_test
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/structutil"
)
type TestStruct struct {
First string
Second int
Third bool `struct:"third"`
}
func TestForEach(t *testing.T) {
instance := TestStruct{
"moishe",
22,
true,
}
structutil.ForEach(instance, func(key string, value any, tag reflect.StructTag) {
switch key {
case "First":
assert.Equal(t, "moishe", value.(string))
assert.Zero(t, tag)
case "Second":
assert.Equal(t, 22, value.(int))
assert.Zero(t, tag)
case "Third":
assert.Equal(t, true, value.(bool))
assert.Equal(t, "third", tag.Get("struct"))
}
})
}
func TestToMap(t *testing.T) {
instance := TestStruct{
"moishe",
22,
true,
}
assert.Equal(t, map[string]any{
"First": "moishe",
"Second": 22,
"Third": true,
}, structutil.ToMap(instance))
assert.Equal(t, map[string]any{
"First": "moishe",
"Second": 22,
"third": true,
}, structutil.ToMap(instance, "struct"))
}

View File

@@ -1,4 +1,4 @@
git tag v1.1.5
git tag v1.1.6
git tag bank_card/v1.1.0
git tag entgo/v1.1.6
git tag geoip/v1.1.0

View File

@@ -1,5 +1,7 @@
package util
import "time"
const (
DateLayout = "2006-01-02"
ClockLayout = "15:04:05"
@@ -7,3 +9,5 @@ const (
DefaultTimeLocationName = "Asia/Shanghai"
)
var ReferenceTimeValue time.Time = time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.FixedZone("MST", -7*60*60))

45
timeutil/format.go Normal file
View File

@@ -0,0 +1,45 @@
package util
import (
"fmt"
"strings"
"time"
)
// ReferenceTime Return the standard Golang reference time (2006-01-02T15:04:05.999999999Z07:00)
func ReferenceTime() time.Time {
return ReferenceTimeValue
}
// FormatTimer Formats the given duration in a colon-separated timer format in the form
// [HH:]MM:SS.
func FormatTimer(d time.Duration) string {
h, m, s := DurationHMS(d)
out := fmt.Sprintf("%02d:%02d:%02d", h, m, s)
out = strings.TrimPrefix(out, `00:`)
out = strings.TrimPrefix(out, `0`)
return out
}
// FormatTimerf Formats the given duration using the given format string. The string follows
// the same formatting rules as described in the fmt package, and will receive
// three integer arguments: hours, minutes, and seconds.
func FormatTimerf(format string, d time.Duration) string {
h, m, s := DurationHMS(d)
out := fmt.Sprintf(format, h, m, s)
return out
}
// DurationHMS Extracts the hours, minutes, and seconds from the given duration.
func DurationHMS(d time.Duration) (int, int, int) {
d = d.Round(time.Second)
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
d -= m * time.Minute
s := d / time.Second
return int(h), int(m), int(s)
}