Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ce578ddf | ||
|
|
413e14ac78 | ||
|
|
50a2e139eb | ||
|
|
18755155ba | ||
|
|
da82d442cc |
@@ -1,4 +1,4 @@
|
|||||||
package dateutils
|
package dateutil
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package dateutils_test
|
package dateutil_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tx7do/go-utils/dateutils"
|
"github.com/tx7do/go-utils/dateutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFloor(t *testing.T) {
|
func TestFloor(t *testing.T) {
|
||||||
now := time.Now()
|
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) {
|
func TestCeil(t *testing.T) {
|
||||||
now := time.Now()
|
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) {
|
func TestBeforeOrEqual(t *testing.T) {
|
||||||
@@ -25,9 +25,9 @@ func TestBeforeOrEqual(t *testing.T) {
|
|||||||
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
|
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
|
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
|
||||||
|
|
||||||
assert.Equal(t, true, dateutils.BeforeOrEqual(milestone, dBefore))
|
assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dBefore))
|
||||||
assert.Equal(t, true, dateutils.BeforeOrEqual(milestone, dEqual))
|
assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dEqual))
|
||||||
assert.Equal(t, false, dateutils.BeforeOrEqual(milestone, dAfter))
|
assert.Equal(t, false, dateutil.BeforeOrEqual(milestone, dAfter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAfterOrEqual(t *testing.T) {
|
func TestAfterOrEqual(t *testing.T) {
|
||||||
@@ -37,9 +37,9 @@ func TestAfterOrEqual(t *testing.T) {
|
|||||||
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
|
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
|
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
|
||||||
|
|
||||||
assert.Equal(t, false, dateutils.AfterOrEqual(milestone, dBefore))
|
assert.Equal(t, false, dateutil.AfterOrEqual(milestone, dBefore))
|
||||||
assert.Equal(t, true, dateutils.AfterOrEqual(milestone, dEqual))
|
assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dEqual))
|
||||||
assert.Equal(t, true, dateutils.AfterOrEqual(milestone, dAfter))
|
assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dAfter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverlap(t *testing.T) {
|
func TestOverlap(t *testing.T) {
|
||||||
@@ -52,8 +52,8 @@ func TestOverlap(t *testing.T) {
|
|||||||
s3, _ := time.Parse("2006-01-02", "2023-01-02")
|
s3, _ := time.Parse("2006-01-02", "2023-01-02")
|
||||||
e3, _ := time.Parse("2006-01-02", "2023-01-04")
|
e3, _ := time.Parse("2006-01-02", "2023-01-04")
|
||||||
|
|
||||||
assert.Equal(t, true, dateutils.Overlap(s1, e1, s2, e2))
|
assert.Equal(t, true, dateutil.Overlap(s1, e1, s2, e2))
|
||||||
assert.Equal(t, false, dateutils.Overlap(s1, e1, s3, e3))
|
assert.Equal(t, false, dateutil.Overlap(s1, e1, s3, e3))
|
||||||
|
|
||||||
s4, _ := time.Parse("2006-01-02", "2023-07-13")
|
s4, _ := time.Parse("2006-01-02", "2023-07-13")
|
||||||
e4, _ := time.Parse("2006-01-02", "2023-07-14")
|
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")
|
s5, _ := time.Parse("2006-01-02", "2023-07-10")
|
||||||
e5, _ := time.Parse("2006-01-02", "2023-07-17")
|
e5, _ := time.Parse("2006-01-02", "2023-07-17")
|
||||||
|
|
||||||
assert.Equal(t, true, dateutils.Overlap(s4, e4, s5, e5))
|
assert.Equal(t, true, dateutil.Overlap(s4, e4, s5, e5))
|
||||||
assert.Equal(t, true, dateutils.Overlap(s5, e5, s4, e4))
|
assert.Equal(t, true, dateutil.Overlap(s5, e5, s4, e4))
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,14 @@ func QueryCommandToOrderConditions(orderBys []string) (error, func(s *sql.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildOrderSelect(s *sql.Selector, field string, desc bool) {
|
||||||
|
if desc {
|
||||||
|
s.OrderBy(sql.Desc(s.C(field)))
|
||||||
|
} else {
|
||||||
|
s.OrderBy(sql.Asc(s.C(field)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BuildOrderSelector(orderBys []string, defaultOrderField string) (error, func(s *sql.Selector)) {
|
func BuildOrderSelector(orderBys []string, defaultOrderField string) (error, func(s *sql.Selector)) {
|
||||||
if len(orderBys) == 0 {
|
if len(orderBys) == 0 {
|
||||||
return nil, func(s *sql.Selector) {
|
return nil, func(s *sql.Selector) {
|
||||||
@@ -43,11 +51,3 @@ func BuildOrderSelector(orderBys []string, defaultOrderField string) (error, fun
|
|||||||
return QueryCommandToOrderConditions(orderBys)
|
return QueryCommandToOrderConditions(orderBys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildOrderSelect(s *sql.Selector, field string, desc bool) {
|
|
||||||
if desc {
|
|
||||||
s.OrderBy(sql.Desc(s.C(field)))
|
|
||||||
} else {
|
|
||||||
s.OrderBy(sql.Asc(s.C(field)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package entgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ func BuildQuerySelector(
|
|||||||
andFilterJsonString, orFilterJsonString string,
|
andFilterJsonString, orFilterJsonString string,
|
||||||
page, pageSize int32, noPaging bool,
|
page, pageSize int32, noPaging bool,
|
||||||
orderBys []string, defaultOrderField string,
|
orderBys []string, defaultOrderField string,
|
||||||
|
selectFields []string,
|
||||||
) (err error, whereSelectors []func(s *sql.Selector), querySelectors []func(s *sql.Selector)) {
|
) (err error, whereSelectors []func(s *sql.Selector), querySelectors []func(s *sql.Selector)) {
|
||||||
err, whereSelectors = BuildFilterSelector(andFilterJsonString, orFilterJsonString)
|
err, whereSelectors = BuildFilterSelector(andFilterJsonString, orFilterJsonString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -24,6 +26,9 @@ func BuildQuerySelector(
|
|||||||
|
|
||||||
pageSelector := BuildPaginationSelector(page, pageSize, noPaging)
|
pageSelector := BuildPaginationSelector(page, pageSize, noPaging)
|
||||||
|
|
||||||
|
var fieldSelector func(s *sql.Selector)
|
||||||
|
err, fieldSelector = BuildFieldSelector(selectFields)
|
||||||
|
|
||||||
if len(whereSelectors) > 0 {
|
if len(whereSelectors) > 0 {
|
||||||
querySelectors = append(querySelectors, whereSelectors...)
|
querySelectors = append(querySelectors, whereSelectors...)
|
||||||
}
|
}
|
||||||
@@ -34,6 +39,9 @@ func BuildQuerySelector(
|
|||||||
if pageSelector != nil {
|
if pageSelector != nil {
|
||||||
querySelectors = append(querySelectors, pageSelector)
|
querySelectors = append(querySelectors, pageSelector)
|
||||||
}
|
}
|
||||||
|
if fieldSelector != nil {
|
||||||
|
querySelectors = append(querySelectors, fieldSelector)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ func TestBuildQuerySelectorDefault(t *testing.T) {
|
|||||||
t.Run("MySQL_Pagination", func(t *testing.T) {
|
t.Run("MySQL_Pagination", func(t *testing.T) {
|
||||||
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, false, []string{}, "created_at")
|
err, whereSelectors, querySelectors := BuildQuerySelector("", "",
|
||||||
|
1, 10, false,
|
||||||
|
[]string{}, "created_at",
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, whereSelectors)
|
require.Nil(t, whereSelectors)
|
||||||
require.NotNil(t, querySelectors)
|
require.NotNil(t, querySelectors)
|
||||||
@@ -120,7 +124,11 @@ func TestBuildQuerySelectorDefault(t *testing.T) {
|
|||||||
t.Run("PostgreSQL_Pagination", func(t *testing.T) {
|
t.Run("PostgreSQL_Pagination", func(t *testing.T) {
|
||||||
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, false, []string{}, "created_at")
|
err, whereSelectors, querySelectors := BuildQuerySelector("", "",
|
||||||
|
1, 10, false,
|
||||||
|
[]string{}, "created_at",
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, whereSelectors)
|
require.Nil(t, whereSelectors)
|
||||||
require.NotNil(t, querySelectors)
|
require.NotNil(t, querySelectors)
|
||||||
@@ -140,7 +148,11 @@ func TestBuildQuerySelectorDefault(t *testing.T) {
|
|||||||
t.Run("MySQL_NoPagination", func(t *testing.T) {
|
t.Run("MySQL_NoPagination", func(t *testing.T) {
|
||||||
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, true, []string{}, "created_at")
|
err, whereSelectors, querySelectors := BuildQuerySelector("", "",
|
||||||
|
1, 10, true,
|
||||||
|
[]string{}, "created_at",
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, whereSelectors)
|
require.Nil(t, whereSelectors)
|
||||||
require.NotNil(t, querySelectors)
|
require.NotNil(t, querySelectors)
|
||||||
@@ -159,7 +171,11 @@ func TestBuildQuerySelectorDefault(t *testing.T) {
|
|||||||
t.Run("PostgreSQL_NoPagination", func(t *testing.T) {
|
t.Run("PostgreSQL_NoPagination", func(t *testing.T) {
|
||||||
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, true, []string{}, "created_at")
|
err, whereSelectors, querySelectors := BuildQuerySelector("", "",
|
||||||
|
1, 10, true,
|
||||||
|
[]string{}, "created_at",
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, whereSelectors)
|
require.Nil(t, whereSelectors)
|
||||||
require.NotNil(t, querySelectors)
|
require.NotNil(t, querySelectors)
|
||||||
|
|||||||
29
entgo/query/select.go
Normal file
29
entgo/query/select.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package entgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/tx7do/go-utils/stringcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildFieldSelect(s *sql.Selector, fields []string) {
|
||||||
|
if len(fields) > 0 {
|
||||||
|
for i, field := range fields {
|
||||||
|
switch {
|
||||||
|
case field == "id_" || field == "_id":
|
||||||
|
field = "id"
|
||||||
|
}
|
||||||
|
fields[i] = stringcase.ToSnakeCase(field)
|
||||||
|
}
|
||||||
|
s.Select(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildFieldSelector(fields []string) (error, func(s *sql.Selector)) {
|
||||||
|
if len(fields) > 0 {
|
||||||
|
return nil, func(s *sql.Selector) {
|
||||||
|
BuildFieldSelect(s, fields)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
48
entgo/query/select_test.go
Normal file
48
entgo/query/select_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package entgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildFieldSelect(t *testing.T) {
|
||||||
|
t.Run("MySQL_2Fields", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
BuildFieldSelect(s, []string{"id", "username"})
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT `id`, `username` FROM `users`", query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_2Fields", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
BuildFieldSelect(s, []string{"id", "username"})
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, `SELECT "id", "username" FROM "users"`, query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("MySQL_AllFields", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
BuildFieldSelect(s, []string{})
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users`", query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_AllFields", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
BuildFieldSelect(s, []string{})
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, `SELECT * FROM "users"`, query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
})
|
||||||
|
}
|
||||||
210
fieldmaskutil/fieldmaskutil.go
Normal file
210
fieldmaskutil/fieldmaskutil.go
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
package fieldmaskutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter keeps the msg fields that are listed in the paths and clears all the rest.
|
||||||
|
//
|
||||||
|
// This is a handy wrapper for NestedMask.Filter method.
|
||||||
|
// If the same paths are used to process multiple proto messages use NestedMask.Filter method directly.
|
||||||
|
func Filter(msg proto.Message, paths []string) {
|
||||||
|
NestedMaskFromPaths(paths).Filter(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune clears all the fields listed in paths from the given msg.
|
||||||
|
//
|
||||||
|
// This is a handy wrapper for NestedMask.Prune method.
|
||||||
|
// If the same paths are used to process multiple proto messages use NestedMask.Filter method directly.
|
||||||
|
func Prune(msg proto.Message, paths []string) {
|
||||||
|
NestedMaskFromPaths(paths).Prune(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite overwrites all the fields listed in paths in the dest msg using values from src msg.
|
||||||
|
//
|
||||||
|
// This is a handy wrapper for NestedMask.Overwrite method.
|
||||||
|
// If the same paths are used to process multiple proto messages use NestedMask.Overwrite method directly.
|
||||||
|
func Overwrite(src, dest proto.Message, paths []string) {
|
||||||
|
NestedMaskFromPaths(paths).Overwrite(src, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NestedMask represents a field mask as a recursive map.
|
||||||
|
type NestedMask map[string]NestedMask
|
||||||
|
|
||||||
|
// NestedMaskFromPaths creates an instance of NestedMask for the given paths.
|
||||||
|
func NestedMaskFromPaths(paths []string) NestedMask {
|
||||||
|
mask := make(NestedMask)
|
||||||
|
for _, path := range paths {
|
||||||
|
curr := mask
|
||||||
|
var letters []rune
|
||||||
|
for _, letter := range path {
|
||||||
|
if letter == '.' {
|
||||||
|
if len(letters) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := string(letters)
|
||||||
|
c, ok := curr[key]
|
||||||
|
if !ok {
|
||||||
|
c = make(NestedMask)
|
||||||
|
curr[key] = c
|
||||||
|
}
|
||||||
|
curr = c
|
||||||
|
letters = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
letters = append(letters, letter)
|
||||||
|
}
|
||||||
|
if len(letters) != 0 {
|
||||||
|
key := string(letters)
|
||||||
|
if _, ok := curr[key]; !ok {
|
||||||
|
curr[key] = make(NestedMask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter keeps the msg fields that are listed in the paths and clears all the rest.
|
||||||
|
//
|
||||||
|
// If the mask is empty then all the fields are kept.
|
||||||
|
// Paths are assumed to be valid and normalized otherwise the function may panic.
|
||||||
|
// See google.golang.org/protobuf/types/known/fieldmaskpb for details.
|
||||||
|
func (mask NestedMask) Filter(msg proto.Message) {
|
||||||
|
if len(mask) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rft := msg.ProtoReflect()
|
||||||
|
rft.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
m, ok := mask[string(fd.Name())]
|
||||||
|
if ok {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if fd.IsMap() {
|
||||||
|
xmap := rft.Get(fd).Map()
|
||||||
|
xmap.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool {
|
||||||
|
if mi, ok := m[mk.String()]; ok {
|
||||||
|
if i, ok := mv.Interface().(protoreflect.Message); ok && len(mi) > 0 {
|
||||||
|
mi.Filter(i.Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
xmap.Clear(mk)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
} else if fd.IsList() {
|
||||||
|
list := rft.Get(fd).List()
|
||||||
|
for i := 0; i < list.Len(); i++ {
|
||||||
|
m.Filter(list.Get(i).Message().Interface())
|
||||||
|
}
|
||||||
|
} else if fd.Kind() == protoreflect.MessageKind {
|
||||||
|
m.Filter(rft.Get(fd).Message().Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rft.Clear(fd)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune clears all the fields listed in paths from the given msg.
|
||||||
|
//
|
||||||
|
// All other fields are kept untouched. If the mask is empty no fields are cleared.
|
||||||
|
// This operation is the opposite of NestedMask.Filter.
|
||||||
|
// Paths are assumed to be valid and normalized otherwise the function may panic.
|
||||||
|
// See google.golang.org/protobuf/types/known/fieldmaskpb for details.
|
||||||
|
func (mask NestedMask) Prune(msg proto.Message) {
|
||||||
|
if len(mask) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rft := msg.ProtoReflect()
|
||||||
|
rft.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
m, ok := mask[string(fd.Name())]
|
||||||
|
if ok {
|
||||||
|
if len(m) == 0 {
|
||||||
|
rft.Clear(fd)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if fd.IsMap() {
|
||||||
|
xmap := rft.Get(fd).Map()
|
||||||
|
xmap.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool {
|
||||||
|
if mi, ok := m[mk.String()]; ok {
|
||||||
|
if i, ok := mv.Interface().(protoreflect.Message); ok && len(mi) > 0 {
|
||||||
|
mi.Prune(i.Interface())
|
||||||
|
} else {
|
||||||
|
xmap.Clear(mk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
} else if fd.IsList() {
|
||||||
|
list := rft.Get(fd).List()
|
||||||
|
for i := 0; i < list.Len(); i++ {
|
||||||
|
m.Prune(list.Get(i).Message().Interface())
|
||||||
|
}
|
||||||
|
} else if fd.Kind() == protoreflect.MessageKind {
|
||||||
|
m.Prune(rft.Get(fd).Message().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite overwrites all the fields listed in paths in the dest msg using values from src msg.
|
||||||
|
//
|
||||||
|
// All other fields are kept untouched. If the mask is empty, no fields are overwritten.
|
||||||
|
// Supports scalars, messages, repeated fields, and maps.
|
||||||
|
// If the parent of the field is nil message, the parent is initiated before overwriting the field
|
||||||
|
// If the field in src is empty value, the field in dest is cleared.
|
||||||
|
// Paths are assumed to be valid and normalized otherwise the function may panic.
|
||||||
|
func (mask NestedMask) Overwrite(src, dest proto.Message) {
|
||||||
|
mask.overwrite(src.ProtoReflect(), dest.ProtoReflect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mask NestedMask) overwrite(src, dest protoreflect.Message) {
|
||||||
|
for k, v := range mask {
|
||||||
|
srcFD := src.Descriptor().Fields().ByName(protoreflect.Name(k))
|
||||||
|
destFD := dest.Descriptor().Fields().ByName(protoreflect.Name(k))
|
||||||
|
if srcFD == nil || destFD == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaf mask -> copy value from src to dest
|
||||||
|
if len(v) == 0 {
|
||||||
|
if srcFD.Kind() == destFD.Kind() { // TODO: Full type equality check
|
||||||
|
val := src.Get(srcFD)
|
||||||
|
if isValid(srcFD, val) {
|
||||||
|
dest.Set(destFD, val)
|
||||||
|
} else {
|
||||||
|
dest.Clear(destFD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if srcFD.Kind() == protoreflect.MessageKind {
|
||||||
|
// If dest field is nil
|
||||||
|
if !dest.Get(destFD).Message().IsValid() {
|
||||||
|
dest.Set(destFD, protoreflect.ValueOf(dest.Get(destFD).Message().New()))
|
||||||
|
}
|
||||||
|
v.overwrite(src.Get(srcFD).Message(), dest.Get(destFD).Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValid(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
|
||||||
|
if fd.IsMap() {
|
||||||
|
return val.Map().IsValid()
|
||||||
|
} else if fd.IsList() {
|
||||||
|
return val.List().IsValid()
|
||||||
|
} else if fd.Message() != nil {
|
||||||
|
return val.Message().IsValid()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
59
fieldmaskutil/fieldmaskutil_test.go
Normal file
59
fieldmaskutil/fieldmaskutil_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package fieldmaskutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NestedMaskFromPaths(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
paths []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want NestedMask
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no nested fields",
|
||||||
|
args: args{paths: []string{"a", "b", "c"}},
|
||||||
|
want: NestedMask{"a": NestedMask{}, "b": NestedMask{}, "c": NestedMask{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with nested fields",
|
||||||
|
args: args{paths: []string{"aaa.bb.c", "dd.e", "f"}},
|
||||||
|
want: NestedMask{
|
||||||
|
"aaa": NestedMask{"bb": NestedMask{"c": NestedMask{}}},
|
||||||
|
"dd": NestedMask{"e": NestedMask{}},
|
||||||
|
"f": NestedMask{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single field",
|
||||||
|
args: args{paths: []string{"a"}},
|
||||||
|
want: NestedMask{"a": NestedMask{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty fields",
|
||||||
|
args: args{paths: []string{}},
|
||||||
|
want: NestedMask{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid input",
|
||||||
|
args: args{paths: []string{".", "..", "..."}},
|
||||||
|
want: NestedMask{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := NestedMaskFromPaths(tt.args.paths); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NestedMaskFromPaths() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNestedMaskFromPaths(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
NestedMaskFromPaths([]string{"aaa.bbb.c.d.e.f", "aa.b.cc.ddddddd", "e", "f", "g.h.i.j.k"})
|
||||||
|
}
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -3,12 +3,14 @@ module github.com/tx7do/go-utils
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.4.0
|
||||||
github.com/gosimple/slug v1.13.1
|
github.com/gosimple/slug v1.13.1
|
||||||
github.com/sony/sonyflake v1.2.0
|
github.com/sony/sonyflake v1.2.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.14.0
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||||
|
google.golang.org/protobuf v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -1,6 +1,11 @@
|
|||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
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/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
|
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
|
||||||
@@ -28,6 +33,10 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
|||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
12
ioutil/file.go
Normal file
12
ioutil/file.go
Normal 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
|
||||||
|
}
|
||||||
230
ioutil/path.go
230
ioutil/path.go
@@ -1,8 +1,14 @@
|
|||||||
package ioutil
|
package ioutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetWorkingDirPath 获取工作路径
|
// GetWorkingDirPath 获取工作路径
|
||||||
@@ -33,18 +39,6 @@ func GetAbsPath() string {
|
|||||||
return dir
|
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 获取文件夹下面的所有文件的列表
|
// GetFileList 获取文件夹下面的所有文件的列表
|
||||||
func GetFileList(root string) []string {
|
func GetFileList(root string) []string {
|
||||||
var files []string
|
var files []string
|
||||||
@@ -75,11 +69,211 @@ func GetFolderNameList(root string) []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFile 读取文件
|
// MatchPath Returns whether a given path matches a glob pattern.
|
||||||
func ReadFile(path string) []byte {
|
//
|
||||||
content, err := os.ReadFile(path)
|
// via github.com/gobwas/glob:
|
||||||
if err != nil {
|
//
|
||||||
return nil
|
// 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
101
ioutil/path_test.go
Normal 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"))
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// This package includes utility functions for handling and manipulating a slice.
|
// 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.
|
// It draws inspiration from JavaScript and Python and uses Go generics as a basis.
|
||||||
|
|
||||||
package sliceutils
|
package sliceutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/exp/constraints"
|
"golang.org/x/exp/constraints"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sliceutils_test
|
package sliceutil_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tx7do/go-utils/sliceutils"
|
"github.com/tx7do/go-utils/sliceutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyInt int
|
type MyInt int
|
||||||
@@ -23,7 +23,7 @@ var lastNames = []string{"Jacobs", "Vin", "Jacobs", "Smith"}
|
|||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
func TestFilter(t *testing.T) {
|
||||||
expectedResult := []int{0, 2, 4, 6, 8}
|
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
|
return value%2 == 0
|
||||||
})
|
})
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
@@ -31,7 +31,7 @@ func TestFilter(t *testing.T) {
|
|||||||
|
|
||||||
func TestForEach(t *testing.T) {
|
func TestForEach(t *testing.T) {
|
||||||
result := 0
|
result := 0
|
||||||
sliceutils.ForEach(numerals, func(value int, _ int, _ []int) {
|
sliceutil.ForEach(numerals, func(value int, _ int, _ []int) {
|
||||||
result += value
|
result += value
|
||||||
})
|
})
|
||||||
assert.Equal(t, 45, result)
|
assert.Equal(t, 45, result)
|
||||||
@@ -39,23 +39,23 @@ func TestForEach(t *testing.T) {
|
|||||||
|
|
||||||
func TestMap(t *testing.T) {
|
func TestMap(t *testing.T) {
|
||||||
expectedResult := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
|
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)
|
return strconv.Itoa(value)
|
||||||
})
|
})
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
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 ""
|
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 ""
|
return ""
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReduce(t *testing.T) {
|
func TestReduce(t *testing.T) {
|
||||||
expectedResult := map[string]string{"result": "0123456789"}
|
expectedResult := map[string]string{"result": "0123456789"}
|
||||||
actualResult := sliceutils.Reduce(
|
actualResult := sliceutil.Reduce(
|
||||||
numerals,
|
numerals,
|
||||||
func(acc map[string]string, cur int, _ int, _ []int) map[string]string {
|
func(acc map[string]string, cur int, _ int, _ []int) map[string]string {
|
||||||
acc["result"] += strconv.Itoa(cur)
|
acc["result"] += strconv.Itoa(cur)
|
||||||
@@ -68,139 +68,139 @@ func TestReduce(t *testing.T) {
|
|||||||
|
|
||||||
func TestFind(t *testing.T) {
|
func TestFind(t *testing.T) {
|
||||||
expectedResult := "Wednesday"
|
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")
|
return strings.Contains(value, "Wed")
|
||||||
})
|
})
|
||||||
assert.Equal(t, expectedResult, *actualResult)
|
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")
|
return strings.Contains(value, "Rishon")
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindIndex(t *testing.T) {
|
func TestFindIndex(t *testing.T) {
|
||||||
expectedResult := 3
|
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")
|
return strings.Contains(value, "Wed")
|
||||||
})
|
})
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
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")
|
return strings.Contains(value, "Rishon")
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindIndexOf(t *testing.T) {
|
func TestFindIndexOf(t *testing.T) {
|
||||||
expectedResult := 3
|
expectedResult := 3
|
||||||
actualResult := sliceutils.FindIndexOf(days, "Wednesday")
|
actualResult := sliceutil.FindIndexOf(days, "Wednesday")
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
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) {
|
func TestFindLastIndex(t *testing.T) {
|
||||||
expectedResult := 2
|
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"
|
return value == "Jacobs"
|
||||||
})
|
})
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
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"
|
return value == "Hamudi"
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindLastIndexOf(t *testing.T) {
|
func TestFindLastIndexOf(t *testing.T) {
|
||||||
expectedResult := 2
|
expectedResult := 2
|
||||||
actualResult := sliceutils.FindLastIndexOf(lastNames, "Jacobs")
|
actualResult := sliceutil.FindLastIndexOf(lastNames, "Jacobs")
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
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) {
|
func TestFindIndexes(t *testing.T) {
|
||||||
expectedResult := []int{0, 2}
|
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"
|
return value == "Jacobs"
|
||||||
})
|
})
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
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"
|
return value == "Hamudi"
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindIndexesOf(t *testing.T) {
|
func TestFindIndexesOf(t *testing.T) {
|
||||||
expectedResult := []int{0, 2}
|
expectedResult := []int{0, 2}
|
||||||
actualResult := sliceutils.FindIndexesOf(lastNames, "Jacobs")
|
actualResult := sliceutil.FindIndexesOf(lastNames, "Jacobs")
|
||||||
assert.Equal(t, expectedResult, actualResult)
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
assert.Nil(t, sliceutils.FindIndexesOf(lastNames, "Hamudi"))
|
assert.Nil(t, sliceutil.FindIndexesOf(lastNames, "Hamudi"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIncludes(t *testing.T) {
|
func TestIncludes(t *testing.T) {
|
||||||
assert.True(t, sliceutils.Includes(numerals, 1))
|
assert.True(t, sliceutil.Includes(numerals, 1))
|
||||||
assert.False(t, sliceutils.Includes(numerals, 11))
|
assert.False(t, sliceutil.Includes(numerals, 11))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAny(t *testing.T) {
|
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
|
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
|
return value == 11
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAll(t *testing.T) {
|
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
|
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
|
return value == 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMerge(t *testing.T) {
|
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.Equal(t, numerals, result)
|
||||||
|
|
||||||
assert.Nil(t, sliceutils.Merge([]int(nil), []int(nil)))
|
assert.Nil(t, sliceutil.Merge([]int(nil), []int(nil)))
|
||||||
assert.Nil(t, sliceutils.Merge([]int{}, []int{}))
|
assert.Nil(t, sliceutil.Merge([]int{}, []int{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSum(t *testing.T) {
|
func TestSum(t *testing.T) {
|
||||||
result := sliceutils.Sum(numerals)
|
result := sliceutil.Sum(numerals)
|
||||||
assert.Equal(t, 45, result)
|
assert.Equal(t, 45, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSum2(t *testing.T) {
|
func TestSum2(t *testing.T) {
|
||||||
result := sliceutils.Sum(numeralsWithUserDefinedType)
|
result := sliceutil.Sum(numeralsWithUserDefinedType)
|
||||||
assert.Equal(t, MyInt(45), result)
|
assert.Equal(t, MyInt(45), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
testSlice := []int{1, 2, 3}
|
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, 3}, result)
|
||||||
assert.Equal(t, []int{1, 2, 3}, testSlice)
|
assert.Equal(t, []int{1, 2, 3}, testSlice)
|
||||||
result = sliceutils.Remove(result, 1)
|
result = sliceutil.Remove(result, 1)
|
||||||
assert.Equal(t, []int{1}, result)
|
assert.Equal(t, []int{1}, result)
|
||||||
result = sliceutils.Remove(result, 3)
|
result = sliceutil.Remove(result, 3)
|
||||||
assert.Equal(t, []int{1}, result)
|
assert.Equal(t, []int{1}, result)
|
||||||
result = sliceutils.Remove(result, 0)
|
result = sliceutil.Remove(result, 0)
|
||||||
assert.Equal(t, []int{}, result)
|
assert.Equal(t, []int{}, result)
|
||||||
result = sliceutils.Remove(result, 1)
|
result = sliceutil.Remove(result, 1)
|
||||||
assert.Equal(t, []int{}, result)
|
assert.Equal(t, []int{}, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCopy(t *testing.T) {
|
func TestCopy(t *testing.T) {
|
||||||
testSlice := []int{1, 2, 3}
|
testSlice := []int{1, 2, 3}
|
||||||
copiedSlice := sliceutils.Copy(testSlice)
|
copiedSlice := sliceutil.Copy(testSlice)
|
||||||
copiedSlice[0] = 2
|
copiedSlice[0] = 2
|
||||||
assert.NotEqual(t, testSlice, copiedSlice)
|
assert.NotEqual(t, testSlice, copiedSlice)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsert(t *testing.T) {
|
func TestInsert(t *testing.T) {
|
||||||
testSlice := []int{1, 2}
|
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.Equal(t, []int{3, 1, 2}, result)
|
||||||
assert.NotEqual(t, testSlice, result)
|
assert.NotEqual(t, testSlice, result)
|
||||||
assert.Equal(t, []int{1, 3, 2}, sliceutils.Insert(testSlice, 1, 3))
|
assert.Equal(t, []int{1, 3, 2}, sliceutil.Insert(testSlice, 1, 3))
|
||||||
assert.Equal(t, []int{1, 2, 3}, sliceutils.Insert(testSlice, 2, 3))
|
assert.Equal(t, []int{1, 2, 3}, sliceutil.Insert(testSlice, 2, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntersection(t *testing.T) {
|
func TestIntersection(t *testing.T) {
|
||||||
@@ -210,7 +210,7 @@ func TestIntersection(t *testing.T) {
|
|||||||
second := []int{2, 3, 4, 5, 6}
|
second := []int{2, 3, 4, 5, 6}
|
||||||
third := []int{3, 4, 5, 6, 7}
|
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) {
|
func TestDifference(t *testing.T) {
|
||||||
@@ -220,7 +220,7 @@ func TestDifference(t *testing.T) {
|
|||||||
second := []int{2, 3, 4, 5, 6}
|
second := []int{2, 3, 4, 5, 6}
|
||||||
third := []int{3, 4, 5, 6, 7}
|
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) {
|
func TestUnion(t *testing.T) {
|
||||||
@@ -230,24 +230,24 @@ func TestUnion(t *testing.T) {
|
|||||||
second := []int{2, 3, 4, 5, 6}
|
second := []int{2, 3, 4, 5, 6}
|
||||||
third := []int{3, 4, 5, 6, 7}
|
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) {
|
func TestReverse(t *testing.T) {
|
||||||
expectedResult := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
|
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
|
// 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
|
// test basic odd length case
|
||||||
expectedResult = []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
|
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) {
|
func TestUnique(t *testing.T) {
|
||||||
duplicates := []int{6, 6, 6, 9, 0, 0, 0}
|
duplicates := []int{6, 6, 6, 9, 0, 0, 0}
|
||||||
expectedResult := []int{6, 9, 0}
|
expectedResult := []int{6, 9, 0}
|
||||||
assert.Equal(t, expectedResult, sliceutils.Unique(duplicates))
|
assert.Equal(t, expectedResult, sliceutil.Unique(duplicates))
|
||||||
// Ensure original is unaltered
|
// Ensure original is unaltered
|
||||||
assert.NotEqual(t, expectedResult, duplicates)
|
assert.NotEqual(t, expectedResult, duplicates)
|
||||||
}
|
}
|
||||||
@@ -255,8 +255,8 @@ func TestUnique(t *testing.T) {
|
|||||||
func TestChunk(t *testing.T) {
|
func TestChunk(t *testing.T) {
|
||||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
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}}, sliceutil.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, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPluck(t *testing.T) {
|
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
|
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
|
return &item.Value
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -287,15 +287,15 @@ func TestFlatten(t *testing.T) {
|
|||||||
{9, 10, 11},
|
{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.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, flattened)
|
||||||
|
|
||||||
assert.Nil(t, sliceutils.Flatten([][]int{}))
|
assert.Nil(t, sliceutil.Flatten([][]int{}))
|
||||||
assert.Nil(t, sliceutils.Flatten([][]int(nil)))
|
assert.Nil(t, sliceutil.Flatten([][]int(nil)))
|
||||||
|
|
||||||
assert.Nil(t, sliceutils.Flatten([][]int{{}, {}}))
|
assert.Nil(t, sliceutil.Flatten([][]int{{}, {}}))
|
||||||
assert.Nil(t, sliceutils.Flatten([][]int{nil, nil}))
|
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
37
structutil/structutils.go
Normal 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
|
||||||
|
}
|
||||||
55
structutil/structutils_test.go
Normal file
55
structutil/structutils_test.go
Normal 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"))
|
||||||
|
}
|
||||||
4
tag.bat
4
tag.bat
@@ -1,6 +1,6 @@
|
|||||||
git tag v1.1.5
|
git tag v1.1.7
|
||||||
git tag bank_card/v1.1.0
|
git tag bank_card/v1.1.0
|
||||||
git tag entgo/v1.1.6
|
git tag entgo/v1.1.8
|
||||||
git tag geoip/v1.1.0
|
git tag geoip/v1.1.0
|
||||||
|
|
||||||
git push origin --tags
|
git push origin --tags
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DateLayout = "2006-01-02"
|
DateLayout = "2006-01-02"
|
||||||
ClockLayout = "15:04:05"
|
ClockLayout = "15:04:05"
|
||||||
@@ -7,3 +9,5 @@ const (
|
|||||||
|
|
||||||
DefaultTimeLocationName = "Asia/Shanghai"
|
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
45
timeutil/format.go
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user