]> git.uzoombox.com Git - uzsdk.git/commitdiff
feat(main): 1. add strutil/unicode.go. master v0.3.3
authorbruce <bruce@nanzoom.com>
Fri, 22 Aug 2025 01:57:27 +0000 (01:57 +0000)
committerbruce <bruce@nanzoom.com>
Fri, 22 Aug 2025 01:57:27 +0000 (01:57 +0000)
  2. add convutil.MergeStructs.
  3. add fsutil.Sha1string.
  4. add osutil.[RunCommand, IsPathExecutable].

18 files changed:
utils/chrome/chromedp.go
utils/convutil/reflect.go
utils/convutil/sort.go
utils/dbutil/format.go
utils/dsutil/array.go
utils/fmtutil/format.go
utils/fsutil/archive.go
utils/fsutil/crypto.go
utils/fsutil/encoding.go
utils/netutil/mime.go
utils/osutil/exec.go
utils/osutil/path.go
utils/osutil/syscall.go
utils/strutil/encoding.go
utils/strutil/unicode.go [new file with mode: 0644]
uzsdk/logger/logger.go
uzsdk/service.go
version.go

index 829bff3b72f98f01be3edb8249cf8e6d6da2f6df..0d8845f14f1d64194c4c310c8ef0f785878e0617 100644 (file)
@@ -14,12 +14,12 @@ import (
 )
 
 // ClickandWait click an element with selector 'sel' if it's exist, and wait for done if 'wait' is true.
-func ClickandWait(ctx context.Context, sel string, options ...interface{}) error {
+func ClickandWait(ctx context.Context, sel string, options ...any) error {
        wait := true
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case bool:
-                       wait = option.(bool)
+                       wait = option
                }
        }
        if err := TryWaiting(ctx, sel, time.Millisecond*10, 1); err == nil {
@@ -59,15 +59,15 @@ func FillInput(ctx context.Context, sel, value string) error {
 }
 
 // TryWaiting try to wait an element with selector 'sel' for exist.
-func TryWaiting(ctx context.Context, sel string, options ...interface{}) error {
+func TryWaiting(ctx context.Context, sel string, options ...any) error {
        interval := time.Millisecond * 20
        count := 10
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case time.Duration:
-                       interval = option.(time.Duration)
+                       interval = option
                case int:
-                       count = option.(int)
+                       count = option
                }
        }
 
index c8bebbba5463699fcb26f908b414f4bb78f92cb5..e0210f16deb2fea9bb6e85f534be5d7bacf235c7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2010-2022 nanzoom.com. All Rights Reserved.
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
 
 package convutil
 
@@ -13,7 +13,7 @@ import (
 )
 
 // PrintStruct print information of struct, v can be ptr or struct.
-func PrintStruct(v interface{}) error {
+func PrintStruct(v any) error {
        vv := reflect.ValueOf(v)
        if vv.Kind() == reflect.Ptr && !vv.IsNil() {
                vv = vv.Elem()
@@ -33,10 +33,10 @@ func PrintStruct(v interface{}) error {
        return nil
 }
 
-// AssignStruct set struct dst's value with map[string]interface{} src,
+// AssignStruct set struct dst's value with map[string]any src,
 // if force=true: assign value anytime,
 // else: assign value when field does not exist or field value is zero.
-func AssignStruct(dst interface{}, src interface{}, force bool) error {
+func AssignStruct(dst any, src any, force bool) error {
        vd := reflect.ValueOf(dst)
        vs := reflect.ValueOf(src)
        if vd.Kind() != reflect.Ptr || vd.IsNil() {
@@ -88,23 +88,23 @@ func AssignStruct(dst interface{}, src interface{}, force bool) error {
 //
 //     excludes []string: MUST NOT copy fields.
 //     includes []string: MUST copy fields that even it's value is nil.
-func CopyStruct(v interface{}, notnil bool, options ...interface{}) interface{} {
+func CopyStruct(v any, notnil bool, options ...any) any {
        // parse options.
        var excludes []string
        var includes []string
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case []string:
-                       excludes = option.([]string)
-               case map[string]interface{}:
-                       for k, v := range option.(map[string]interface{}) {
-                               switch v.(type) {
+                       excludes = option
+               case map[string]any:
+                       for k, v := range option {
+                               switch v := v.(type) {
                                case []string:
                                        switch k {
                                        case "excludes":
-                                               excludes = v.([]string)
+                                               excludes = v
                                        case "includes":
-                                               includes = v.([]string)
+                                               includes = v
                                        }
                                }
                        }
@@ -164,9 +164,87 @@ func CopyStruct(v interface{}, notnil bool, options ...interface{}) interface{}
        return vd.Interface()
 }
 
+// MergeStructs merge struct src's value to struct dst
+func MergeStructs(dst, src any) error {
+       vd := reflect.ValueOf(dst)
+       vs := reflect.ValueOf(src)
+       if vd.Kind() != reflect.Ptr {
+               return fmt.Errorf("dst is not a valid pointer.")
+       }
+       if vs.Kind() != reflect.Ptr {
+               return fmt.Errorf("src is not a valid pointer.")
+       }
+
+       vd = vd.Elem()
+       vs = vs.Elem()
+       if vd.Kind() != reflect.Struct {
+               return fmt.Errorf("dst must be a struct.")
+       }
+       if vs.Kind() != reflect.Struct {
+               return fmt.Errorf("src must be a struct.")
+       }
+       if vd.Type() != vs.Type() {
+               return fmt.Errorf("dst and src must be the same type.")
+       }
+
+       for i := range vs.NumField() {
+               srcField, srcFieldType := vs.Field(i), vs.Type().Field(i)
+               dstField := vd.FieldByName(srcFieldType.Name)
+               if !dstField.IsValid() {
+                       fmt.Printf("Skipping invalid field: %s\n", srcFieldType.Name)
+                       continue
+               }
+               if !dstField.CanSet() {
+                       fmt.Printf("Skipping unexported or unsettable field: %s\n", srcFieldType.Name)
+                       continue
+               }
+
+               isDstFieldZero := dstField.IsZero()
+               if srcField.Kind() == reflect.Struct {
+                       // 如果字段是结构体
+                       if isDstFieldZero {
+                               // 目标结构体是零值,并且源结构体有值,则直接用源结构体覆盖目标结构体
+                               if !srcField.IsZero() {
+                                       dstField.Set(srcField)
+                               }
+                       } else {
+                               // 目标结构体非零,递归合并其字段
+                               dstFieldAddr := dstField.Addr().Interface()
+                               srcFieldAddr := srcField.Addr().Interface()
+                               if err := MergeStructs(dstFieldAddr, srcFieldAddr); err != nil {
+                                       return fmt.Errorf("error merging nested struct %s: %w", srcFieldType.Name, err)
+                               }
+                       }
+               } else if srcField.Kind() == reflect.Ptr && srcField.Elem().Kind() == reflect.Struct {
+                       // 如果字段是指向结构体的指针
+                       if dstField.IsNil() {
+                               // 目标指针是 nil, 并且源指针不是 nil,则复制整个指向的结构体
+                               if !srcField.IsNil() {
+                                       dstField.Set(srcField)
+                               }
+                       } else if !srcField.IsNil() {
+                               // 目标和源指针都不是 nil,递归合并它们指向的结构体
+                               if err := MergeStructs(dstField.Interface(), srcField.Interface()); err != nil {
+                                       return fmt.Errorf("error merging nested ptr struct %s: %w", srcFieldType.Name, err)
+                               }
+                       }
+               } else {
+                       // 对于基本类型或非结构体指针类型
+                       if isDstFieldZero {
+                               // 如果目标字段是零值,并且源字段不是零值
+                               if srcField.IsValid() && srcField.Type().AssignableTo(dstField.Type()) {
+                                       dstField.Set(srcField)
+                               }
+                       }
+               }
+       }
+
+       return nil
+}
+
 // BindString make a string from value,
 // and returns the point of it for binding.
-func BindString(value interface{}) *string {
+func BindString(value any) *string {
        val, _ := conv.String(value)
        return &val
 }
index 7110d268ccf1c30948e42cc0f2d0287c23ca27d8..cb686f3210f5a08584fd6c15cbe2ab225d804883 100644 (file)
@@ -8,7 +8,7 @@ import (
 )
 
 // SortMap returns an sorted array of map keys.
-func SortMap(v interface{}) []string {
+func SortMap(v any) []string {
        vv := reflect.ValueOf(v)
        if vv.Kind() != reflect.Map {
                return nil
@@ -22,7 +22,7 @@ func SortMap(v interface{}) []string {
 }
 
 // SortMapInt returns an sorted int array of map keys.
-func SortMapInt(v interface{}) []int {
+func SortMapInt(v any) []int {
        vv := reflect.ValueOf(v)
        if vv.Kind() != reflect.Map {
                return nil
@@ -49,7 +49,7 @@ func SortMapInt(v interface{}) []int {
 }
 
 // SortMapFloat64 returns an sorted float array of map keys.
-func SortMapFloat64(v interface{}) []float64 {
+func SortMapFloat64(v any) []float64 {
        vv := reflect.ValueOf(v)
        if vv.Kind() != reflect.Map {
                return nil
@@ -72,7 +72,7 @@ func SortMapFloat64(v interface{}) []float64 {
 }
 
 // SortMapString returns an sorted string array of map keys.
-func SortMapString(v interface{}) []string {
+func SortMapString(v any) []string {
        vv := reflect.ValueOf(v)
        if vv.Kind() != reflect.Map {
                return nil
index 0285ec2d9457117906b8bd13230f9db53101ca25..9fc363e85b1ada232f21bd1701ef8f274b931122 100644 (file)
@@ -29,55 +29,55 @@ type Query struct {
 }
 
 // Parse returns request list to Query.
-func (query *Query) Parse(params interface{}) {
+func (query *Query) Parse(params any) {
        switch params.(type) {
-       case map[string]interface{}:
+       case map[string]any:
        default:
                return
        }
-       for key, value := range params.(map[string]interface{}) {
+       for key, value := range params.(map[string]any) {
                switch strings.ToLower(key) {
                case "fields":
                        query.Fields = make([]string, 0)
-                       switch value.(type) {
+                       switch value := value.(type) {
                        case string:
-                               query.Fields = append(query.Fields, value.(string))
-                       case []interface{}:
-                               for _, v := range value.([]interface{}) {
-                                       switch v.(type) {
+                               query.Fields = append(query.Fields, value)
+                       case []any:
+                               for _, v := range value {
+                                       switch v := v.(type) {
                                        case string:
-                                               query.Fields = append(query.Fields, v.(string))
+                                               query.Fields = append(query.Fields, v)
                                        }
                                }
                        }
                case "wheres":
                        query.Wheres = make(map[string]string)
                        switch value.(type) {
-                       case map[string]interface{}:
-                               for k, v := range value.(map[string]interface{}) {
-                                       switch v.(type) {
+                       case map[string]any:
+                               for k, v := range value.(map[string]any) {
+                                       switch v := v.(type) {
                                        case string:
-                                               query.Wheres[k] = fmt.Sprintf("%s", v.(string))
-                                       case []interface{}:
+                                               query.Wheres[k] = fmt.Sprintf("%s", v)
+                                       case []any:
                                                vt := make([]string, 0)
-                                               for _, t := range v.([]interface{}) {
-                                                       switch t.(type) {
+                                               for _, t := range v {
+                                                       switch t := t.(type) {
                                                        case string:
-                                                               vt = append(vt, fmt.Sprintf("'%s'", t.(string)))
+                                                               vt = append(vt, fmt.Sprintf("'%s'", t))
                                                        }
                                                }
                                                if len(vt) >= 2 {
                                                        query.Wheres[k] = fmt.Sprintf("BETWEEN %s AND %s", vt[0], vt[1])
                                                }
-                                       case map[string]interface{}:
-                                               for _, t := range v.(map[string]interface{}) {
-                                                       switch t.(type) {
-                                                       case []interface{}:
+                                       case map[string]any:
+                                               for _, t := range v {
+                                                       switch t := t.(type) {
+                                                       case []any:
                                                                vt := make([]string, 0)
-                                                               for _, p := range t.([]interface{}) {
-                                                                       switch p.(type) {
+                                                               for _, p := range t {
+                                                                       switch p := p.(type) {
                                                                        case string:
-                                                                               vt = append(vt, fmt.Sprintf("'%s'", p.(string)))
+                                                                               vt = append(vt, fmt.Sprintf("'%s'", p))
                                                                        }
                                                                }
                                                                if len(vt) > 0 {
@@ -90,20 +90,20 @@ func (query *Query) Parse(params interface{}) {
                                }
                        }
                case "groupby":
-                       switch value.(type) {
+                       switch value := value.(type) {
                        case string:
-                               query.Groupby = value.(string)
+                               query.Groupby = value
                        }
                case "orderby":
-                       switch value.(type) {
+                       switch value := value.(type) {
                        case string:
-                               query.OrderBy = value.(string)
+                               query.OrderBy = value
                        }
                case "limits":
                        query.Limits = make(map[string]int64)
-                       switch value.(type) {
-                       case map[string]interface{}:
-                               for k, v := range value.(map[string]interface{}) {
+                       switch value := value.(type) {
+                       case map[string]any:
+                               for k, v := range value {
                                        switch strings.ToLower(k) {
                                        case "offset":
                                                query.Limits["offset"], _ = strconv.ParseInt(fmt.Sprint(v), 0, 64)
@@ -114,9 +114,9 @@ func (query *Query) Parse(params interface{}) {
                        }
                case "params":
                        query.Params = make(map[string]string)
-                       switch value.(type) {
-                       case map[string]interface{}:
-                               for k, v := range value.(map[string]interface{}) {
+                       switch value := value.(type) {
+                       case map[string]any:
+                               for k, v := range value {
                                        query.Params[k] = fmt.Sprint(v)
                                }
                        }
@@ -179,25 +179,25 @@ type Stats struct {
 
 // StatsRes defines response stats object.
 type StatsRes struct {
-       Minute  *interface{} `db:"minute"`
-       Hour    *interface{} `db:"hour"`
-       Day     *interface{} `db:"day"`
-       Week    *interface{} `db:"week"`
-       Month   *interface{} `db:"month"`
-       Quarter *interface{} `db:"quarter"`
-       Year    *interface{} `db:"year"`
-       All     *interface{} `db:"all"`
+       Minute  *any `db:"minute"`
+       Hour    *any `db:"hour"`
+       Day     *any `db:"day"`
+       Week    *any `db:"week"`
+       Month   *any `db:"month"`
+       Quarter *any `db:"quarter"`
+       Year    *any `db:"year"`
+       All     *any `db:"all"`
 }
 
 // Parse returns request list to Stats.
-func (stats *Stats) Parse(params interface{}) {
+func (stats *Stats) Parse(params any) {
        switch params.(type) {
-       case map[string]interface{}:
+       case map[string]any:
        default:
                return
        }
 
-       for key, value := range params.(map[string]interface{}) {
+       for key, value := range params.(map[string]any) {
                switch strings.ToLower(key) {
                case "name":
                        stats.Name = fmt.Sprint(value)
@@ -206,45 +206,45 @@ func (stats *Stats) Parse(params interface{}) {
                case "timezone":
                        stats.Timezone = fmt.Sprint(value)
                case "series":
-                       switch value.(type) {
+                       switch value := value.(type) {
                        case string:
-                               stats.Series = append(stats.Series, value.(string))
-                       case []interface{}:
-                               for _, v := range value.([]interface{}) {
-                                       switch v.(type) {
+                               stats.Series = append(stats.Series, value)
+                       case []any:
+                               for _, v := range value {
+                                       switch v := v.(type) {
                                        case string:
-                                               stats.Series = append(stats.Series, v.(string))
+                                               stats.Series = append(stats.Series, v)
                                        }
                                }
                        }
                case "wheres":
                        stats.Wheres = make(map[string]string)
                        switch value.(type) {
-                       case map[string]interface{}:
-                               for k, v := range value.(map[string]interface{}) {
-                                       switch v.(type) {
+                       case map[string]any:
+                               for k, v := range value.(map[string]any) {
+                                       switch v := v.(type) {
                                        case string:
-                                               stats.Wheres[k] = fmt.Sprintf("%s", v.(string))
-                                       case []interface{}:
+                                               stats.Wheres[k] = fmt.Sprintf("%s", v)
+                                       case []any:
                                                vt := make([]string, 0)
-                                               for _, t := range v.([]interface{}) {
-                                                       switch t.(type) {
+                                               for _, t := range v {
+                                                       switch t := t.(type) {
                                                        case string:
-                                                               vt = append(vt, fmt.Sprintf("'%s'", t.(string)))
+                                                               vt = append(vt, fmt.Sprintf("'%s'", t))
                                                        }
                                                }
                                                if len(vt) >= 2 {
                                                        stats.Wheres[k] = fmt.Sprintf("BETWEEN %s AND %s", vt[0], vt[1])
                                                }
-                                       case map[string]interface{}:
-                                               for _, t := range v.(map[string]interface{}) {
+                                       case map[string]any:
+                                               for _, t := range v {
                                                        switch t.(type) {
-                                                       case []interface{}:
+                                                       case []any:
                                                                vt := make([]string, 0)
-                                                               for _, p := range t.([]interface{}) {
-                                                                       switch p.(type) {
+                                                               for _, p := range t.([]any) {
+                                                                       switch p := p.(type) {
                                                                        case string:
-                                                                               vt = append(vt, fmt.Sprintf("'%s'", p.(string)))
+                                                                               vt = append(vt, fmt.Sprintf("'%s'", p))
                                                                        }
                                                                }
                                                                if len(vt) > 0 {
@@ -322,19 +322,19 @@ type Serie struct {
 
 // SerieRes defines response serie object.
 type SerieRes struct {
-       Field []interface{}
-       Value []interface{}
+       Field []any
+       Value []any
 }
 
 // Parse returns request list to Stats.
-func (serie *Serie) Parse(params interface{}) {
+func (serie *Serie) Parse(params any) {
        switch params.(type) {
-       case map[string]interface{}:
+       case map[string]any:
        default:
                return
        }
 
-       for key, value := range params.(map[string]interface{}) {
+       for key, value := range params.(map[string]any) {
                switch strings.ToLower(key) {
                case "name":
                        serie.Name = fmt.Sprint(value)
@@ -351,31 +351,31 @@ func (serie *Serie) Parse(params interface{}) {
                case "wheres":
                        serie.Wheres = make(map[string]string)
                        switch value.(type) {
-                       case map[string]interface{}:
-                               for k, v := range value.(map[string]interface{}) {
-                                       switch v.(type) {
+                       case map[string]any:
+                               for k, v := range value.(map[string]any) {
+                                       switch v := v.(type) {
                                        case string:
-                                               serie.Wheres[k] = fmt.Sprintf("%s", v.(string))
-                                       case []interface{}:
+                                               serie.Wheres[k] = fmt.Sprintf("%s", v)
+                                       case []any:
                                                vt := make([]string, 0)
-                                               for _, t := range v.([]interface{}) {
-                                                       switch t.(type) {
+                                               for _, t := range v {
+                                                       switch t := t.(type) {
                                                        case string:
-                                                               vt = append(vt, fmt.Sprintf("'%s'", t.(string)))
+                                                               vt = append(vt, fmt.Sprintf("'%s'", t))
                                                        }
                                                }
                                                if len(vt) >= 2 {
                                                        serie.Wheres[k] = fmt.Sprintf("BETWEEN %s AND %s", vt[0], vt[1])
                                                }
-                                       case map[string]interface{}:
-                                               for _, t := range v.(map[string]interface{}) {
+                                       case map[string]any:
+                                               for _, t := range v {
                                                        switch t.(type) {
-                                                       case []interface{}:
+                                                       case []any:
                                                                vt := make([]string, 0)
-                                                               for _, p := range t.([]interface{}) {
-                                                                       switch p.(type) {
+                                                               for _, p := range t.([]any) {
+                                                                       switch p := p.(type) {
                                                                        case string:
-                                                                               vt = append(vt, fmt.Sprintf("'%s'", p.(string)))
+                                                                               vt = append(vt, fmt.Sprintf("'%s'", p))
                                                                        }
                                                                }
                                                                if len(vt) > 0 {
@@ -478,12 +478,12 @@ LEFT JOIN (
 
 // Wheres returns an formatted sql where string without 'WHERE' from obj's value,
 // exp: {ad_client_id: '1', name: 'test'} => "ad_client_id='1' AND name='test'"
-func Wheres(obj interface{}) string {
+func Wheres(obj any) string {
        var wheres string
-       m := make(map[string]interface{})
-       switch obj.(type) {
-       case map[string]interface{}:
-               m = obj.(map[string]interface{})
+       m := make(map[string]any)
+       switch obj := obj.(type) {
+       case map[string]any:
+               m = obj
        default:
                m = structs.Map(obj)
        }
index 807fe9ca0fca1eab90ab562cffc7d02a15b38897..effd2225284676ca286eac87fba894c8d78b5cb8 100644 (file)
@@ -7,7 +7,7 @@ import (
 )
 
 // Equal returns true if array equals to val.
-func Equal(array interface{}, val interface{}) bool {
+func Equal(array any, val any) bool {
        va, vv := reflect.ValueOf(array), reflect.ValueOf(val)
        if (va.Kind() != reflect.Array && va.Kind() != reflect.Slice) ||
                (vv.Kind() != reflect.Array && vv.Kind() != reflect.Slice) {
@@ -29,7 +29,7 @@ func Equal(array interface{}, val interface{}) bool {
 }
 
 // Includes returns true if an array includes a certain value.
-func Includes(array interface{}, val interface{}) bool {
+func Includes(array any, val any) bool {
        va := reflect.ValueOf(array)
        if va.Kind() != reflect.Array && va.Kind() != reflect.Slice {
                return false
@@ -47,7 +47,7 @@ func Includes(array interface{}, val interface{}) bool {
 }
 
 // IncludesAt returns index if array includes val.
-func IncludesAt(array interface{}, val interface{}) int {
+func IncludesAt(array any, val any) int {
        va, vv := reflect.ValueOf(array), reflect.ValueOf(val)
        if (va.Kind() != reflect.Array && va.Kind() != reflect.Slice) || 0 >= va.Len() {
                return -1
@@ -86,13 +86,13 @@ func IncludesAt(array interface{}, val interface{}) int {
 }
 
 // ContainsAny returns true if array contains any elements in val.
-func ContainsAny(array interface{}, val interface{}) bool {
+func ContainsAny(array any, val any) bool {
        i, _ := ContainsAnyAt(array, val)
        return i != -1
 }
 
 // ContainsAnyAt returns index if array contains any elements in val.
-func ContainsAnyAt(array interface{}, val interface{}) (int, int) {
+func ContainsAnyAt(array any, val any) (int, int) {
        va, vv := reflect.ValueOf(array), reflect.ValueOf(val)
        if (va.Kind() != reflect.Array && va.Kind() != reflect.Slice) || 0 >= va.Len() {
                return -1, -1
@@ -126,13 +126,13 @@ func ContainsAnyAt(array interface{}, val interface{}) (int, int) {
 }
 
 // ContainsAll returns true if array contains all elements in val.
-func ContainsAll(array interface{}, val interface{}) bool {
+func ContainsAll(array any, val any) bool {
        res := ContainsAllAt(array, val)
        return res != nil
 }
 
 // ContainsAllAt returns index if array contains all elements in val.
-func ContainsAllAt(array interface{}, val interface{}) []int {
+func ContainsAllAt(array any, val any) []int {
        va, vv := reflect.ValueOf(array), reflect.ValueOf(val)
        if (va.Kind() != reflect.Array && va.Kind() != reflect.Slice) || 0 >= va.Len() {
                return nil
index efb217067727eedfc0804ae14ac02bbef2288d19..9dc0fc06d62b7c05108de0ffd51d116e85511878 100644 (file)
@@ -10,13 +10,13 @@ import (
 )
 
 // FormatDate returns a string formatted value with type: date.storage.
-func FormatDate(value interface{}, timezone, format string) string {
+func FormatDate(value any, timezone, format string) string {
        var s time.Time
-       switch value.(type) {
+       switch value := value.(type) {
        case string:
-               s, _ = time.Parse(time.RFC3339, value.(string))
+               s, _ = time.Parse(time.RFC3339, value)
        case *time.Time:
-               s = *value.(*time.Time)
+               s = *value
        }
 
        var options string
@@ -37,13 +37,13 @@ func FormatDate(value interface{}, timezone, format string) string {
 }
 
 // FormatStorage returns a string formatted value with type: uom.storage.
-func FormatStorage(value interface{}) string {
+func FormatStorage(value any) string {
        var bytes int64
-       switch value.(type) {
+       switch value := value.(type) {
        case int64:
-               bytes = value.(int64)
+               bytes = value
        case *string:
-               bytes, _ = strconv.ParseInt(*value.(*string), 0, 64)
+               bytes, _ = strconv.ParseInt(*value, 0, 64)
        }
 
        if bytes == 0 {
index a3a6aa638b74415cbb41eaa121826ac5e491469e..1ce97882e5c881589d82b2dd0f0289087fd6392f 100644 (file)
@@ -48,15 +48,15 @@ func ZipNamedTree(tree map[string]ZipNode, target string) error {
 //
 //     recursive bool: zip directory recursively, default: true.
 //     files []string: only zip these files, default zip all files.
-func ZipFilepath(paths []string, target string, options ...interface{}) error {
+func ZipFilepath(paths []string, target string, options ...any) error {
        var recursive = true
        var files []string
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case bool:
-                       recursive = option.(bool)
+                       recursive = option
                case []string:
-                       files = option.([]string)
+                       files = option
                }
        }
 
index aac6e8315d424c1d09bbfba7fd49b77d2a108f35..b0c625dd40fe6d505ceae18bf48733b92c59ee7a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2010-2022 nanzoom.com. All Rights Reserved.
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
 
 package fsutil
 
@@ -8,6 +8,7 @@ import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/md5"
+       "crypto/sha1"
        "crypto/sha256"
        "fmt"
        "hash"
@@ -98,6 +99,11 @@ func Md5string(ss ...string) string {
        return hashString(crypto.MD5, ss...)
 }
 
+// Sha1string returns the SHA1 checksum of string(s).
+func Sha1string(ss ...string) string {
+       return hashString(crypto.SHA1, ss...)
+}
+
 // Sha256string returns the SHA256 checksum of string(s).
 func Sha256string(ss ...string) string {
        return hashString(crypto.SHA256, ss...)
@@ -225,6 +231,8 @@ func hashString(t crypto.Hash, ss ...string) string {
        switch t {
        case crypto.MD5:
                h = md5.New()
+       case crypto.SHA1:
+               h = sha1.New()
        case crypto.SHA256:
                h = sha256.New()
        default:
index 2b2d9c89bed5a621daa53459e378e477969894a6..b8caa24da15f501372065af384cf40149559443c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2010-2022 nanzoom.com. All Rights Reserved.
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
 
 package fsutil
 
@@ -21,12 +21,12 @@ func Base64file(path string) string {
 // options will be [noPadding]:
 //
 //     noPadding bool: not padding with '=' if set to true, default: false.
-func Base64Encode(text string, options ...interface{}) string {
+func Base64Encode(text string, options ...any) string {
        var noPadding bool
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case bool:
-                       noPadding = option.(bool)
+                       noPadding = option
                }
        }
 
@@ -41,12 +41,12 @@ func Base64Encode(text string, options ...interface{}) string {
 // options will be [noPadding]:
 //
 //     noPadding bool: not padding with '=' if set to true, default: false.
-func Base64Decode(text string, options ...interface{}) string {
+func Base64Decode(text string, options ...any) string {
        var noPadding bool
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case bool:
-                       noPadding = option.(bool)
+                       noPadding = option
                }
        }
 
index 979847abb35b305885854ca33f10a98ac8d91d91..d5283ccf653fab282268f961d39b2e4e3e4802d9 100644 (file)
@@ -19,17 +19,17 @@ import (
 //     file string: path of file.
 //     fi fs.FileInfo: fileinfo.
 //     re *regexp.Regexp: regexp.
-func Mimetype(options ...interface{}) string {
+func Mimetype(options ...any) string {
        var fi fs.FileInfo
        var re *regexp.Regexp
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case string:
-                       fi, _ = os.Stat(option.(string))
+                       fi, _ = os.Stat(option)
                case fs.FileInfo:
-                       fi = option.(fs.FileInfo)
+                       fi = option
                case *regexp.Regexp:
-                       re = option.(*regexp.Regexp)
+                       re = option
                }
        }
 
@@ -49,14 +49,14 @@ func Mimetype(options ...interface{}) string {
 // options will be [content]:
 //
 //     content [*http.Request, *string]: header["Content-Type"]
-func Multipart(options ...interface{}) string {
+func Multipart(options ...any) string {
        var content string // "Content-Type: multipart/form-data;boundary=---yb1zYhTI38xpQxBK"
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case *http.Request:
-                       content = option.(*http.Request).Header.Get("Content-Type")
+                       content = option.Header.Get("Content-Type")
                case *string:
-                       content = *option.(*string)
+                       content = *option
                }
        }
 
index e84acc6cd0597daf1b2577ff9647e8a7b013061f..9c672b244809dfb394493de6e727465f326b67fb 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2010-2022 nanzoom.com. All Rights Reserved.
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
 
 package osutil
 
@@ -7,6 +7,13 @@ import (
        "os/exec"
 )
 
+// RunCommand construct a command struct and then call cmd.Start and return immediately,
+// returns the command struct and exec status at last.
+func RunCommand(name string, arg ...string) (*exec.Cmd, error) {
+       cmd := exec.Command(name, arg...)
+       return cmd, cmd.Start()
+}
+
 // ExecCommand construct a command struct and then call cmd.Start and cmd.Wait,
 // returns the command struct and exec status at last.
 func ExecCommand(name string, arg ...string) (*exec.Cmd, error) {
index 6a8913b36e38d8147338c7101b7a8cfe414842f8..60dd5bafb9520c7bd0ecac719a6a6c6c832a0632 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2010-2022 nanzoom.com. All Rights Reserved.
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
 
 package osutil
 
@@ -35,6 +35,20 @@ func IsPathExist(path string) bool {
        return true
 }
 
+// IsPathExecutable returns true if the pathfile have permmode:x.
+func IsPathExecutable(path string) bool {
+       info, err := os.Stat(path)
+       if err != nil {
+               return false
+       }
+
+       if info.IsDir() {
+               return false
+       }
+
+       return info.Mode()&0111 != 0
+}
+
 // IsDir returns true if the path is an exist directory.
 func IsDir(path string) bool {
        fi, err := os.Stat(path)
index 376446318d1325826ba75f9d5f5f98bc7cc2eb9c..70a0852dec8f03312aadcb514a7541e3ea8f208c 100644 (file)
@@ -10,8 +10,8 @@ import (
 )
 
 // DiskUsage estimate file space usage, returns the command struct and exec status at last.
-func DiskUsage(path string) (map[string]interface{}, error) {
-       result := make(map[string]interface{})
+func DiskUsage(path string) (map[string]any, error) {
+       result := make(map[string]any)
        var buf unix.Statfs_t
        if err := unix.Statfs(path, &buf); err != nil {
                return result, err
index 1358f9610e3cd612ef8958f80ba03cb88595dda4..aeefc1da48d601fd21b19a26070388c9e578cebb 100644 (file)
@@ -25,7 +25,7 @@ func Base64URLDecode(v string) string {
 }
 
 // Base64URLHash returns a base64 encoded string hashed with murmur of v.
-func Base64URLHash(v interface{}) string {
+func Base64URLHash(v any) string {
        str := fmt.Sprint(v)
        sum := murmur3.Sum64([]byte(str))
        buf := fmt.Sprintf("%.16x", sum)
diff --git a/utils/strutil/unicode.go b/utils/strutil/unicode.go
new file mode 100644 (file)
index 0000000..1558f5a
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
+
+package strutil
+
+import "unicode"
+
+// ContainsNonBasicLatinLetter checks if s contains any Unicode letter
+// that is not a basic Latin letter (part of the "Latin" script and not ASCII a-zA-Z).
+// This is a more robust way to define "English letter" vs "non-English letter".
+func ContainsNonBasicLatinLetter(s string) bool {
+       for _, r := range s {
+               if unicode.IsLetter(r) && !unicode.Is(unicode.Latin, r) {
+                       // It's a letter, but not from the Latin script (e.g., Cyrillic, Han, Greek)
+                       return true
+               }
+               // If it's a Latin script letter, check if it's an extended Latin letter
+               // (like é, ü, ñ) which are Latin but not basic a-zA-Z.
+               // Basic a-zA-Z are ASCII.
+               if unicode.IsLetter(r) && unicode.Is(unicode.Latin, r) && r > unicode.MaxASCII {
+                       // It's a Latin letter but outside the ASCII range (0-127),
+                       // so it must be an extended Latin letter.
+                       return true
+               }
+       }
+       return false
+}
index 84a1fd7f7b515829dd3162de26124c76119dfaa9..cd9422a741e43e0767cc2eedc9150baefb5d5dd8 100644 (file)
@@ -31,7 +31,7 @@ type Logger struct {
 // New creates a new Logger, the path is the filepath of logfile,
 // logv is an string in ['fatal','error','warn','info','debug'],
 // prefix is the content write after timestamp and before message.
-func New(path, logv, prefix string, options ...interface{}) *Logger {
+func New(path, logv, prefix string, options ...any) *Logger {
        var writer *os.File
        if path != "" {
                writer, _ = os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
@@ -65,7 +65,7 @@ func New(path, logv, prefix string, options ...interface{}) *Logger {
 // NewWriter creates a new Logger, the writer is the *os.File of logfile,
 // logv is an string in ['fatal','error','warn','info','debug'],
 // prefix is the content write after timestamp and before message.
-func NewWriter(writer *os.File, logv, prefix string, options ...interface{}) *Logger {
+func NewWriter(writer *os.File, logv, prefix string, options ...any) *Logger {
        if writer == nil {
                writer = os.Stdout
        }
@@ -101,52 +101,52 @@ func (l *Logger) Writer() *os.File {
 }
 
 // Fatal calls output(Fatal, Sprint(v)) to print the log.
-func (l *Logger) Fatal(v ...interface{}) {
+func (l *Logger) Fatal(v ...any) {
        l.Output(Fatal, "FATAL", fmt.Sprint(v...))
 }
 
 // Fatalf calls output(Fatal, Sprintf(format, v)) to print the log.
-func (l *Logger) Fatalf(format string, v ...interface{}) {
+func (l *Logger) Fatalf(format string, v ...any) {
        l.Output(Fatal, "FATAL", fmt.Sprintf(format, v...))
 }
 
 // Error calls output(Error) to print the log.
-func (l *Logger) Error(v ...interface{}) {
+func (l *Logger) Error(v ...any) {
        l.Output(Error, "ERROR", fmt.Sprint(v...))
 }
 
 // Errorf calls output(Error, Sprintf(format, v)) to print the log.
-func (l *Logger) Errorf(format string, v ...interface{}) {
+func (l *Logger) Errorf(format string, v ...any) {
        l.Output(Error, "ERROR", fmt.Sprintf(format, v...))
 }
 
 // Warn calls output(Warn) to print the log.
-func (l *Logger) Warn(v ...interface{}) {
+func (l *Logger) Warn(v ...any) {
        l.Output(Warn, "WARN", fmt.Sprint(v...))
 }
 
 // Warnf calls output(Warn, Sprintf(format, v)) to print the log.
-func (l *Logger) Warnf(format string, v ...interface{}) {
+func (l *Logger) Warnf(format string, v ...any) {
        l.Output(Warn, "WARN", fmt.Sprintf(format, v...))
 }
 
 // Info calls output(Info) to print the log.
-func (l *Logger) Info(v ...interface{}) {
+func (l *Logger) Info(v ...any) {
        l.Output(Info, "INFO", fmt.Sprint(v...))
 }
 
 // Infof calls output(Info, Sprintf(format, v)) to print the log.
-func (l *Logger) Infof(format string, v ...interface{}) {
+func (l *Logger) Infof(format string, v ...any) {
        l.Output(Info, "INFO", fmt.Sprintf(format, v...))
 }
 
 // Debug calls output(Debug) to print the log.
-func (l *Logger) Debug(v ...interface{}) {
+func (l *Logger) Debug(v ...any) {
        l.Output(Debug, "DEBUG", fmt.Sprint(v...))
 }
 
 // Debugf calls output(Debug, Sprintf(format, v)) to print the log.
-func (l *Logger) Debugf(format string, v ...interface{}) {
+func (l *Logger) Debugf(format string, v ...any) {
        l.Output(Debug, "DEBUG", fmt.Sprintf(format, v...))
 }
 
index 42dfd0531fbc053855e052f7d529c8c44e169e22..883c445e846175be3d37e7ddcea2f8cd9bfd7ed6 100644 (file)
@@ -62,24 +62,24 @@ var RespCode = map[string]int64{
 
 // Error defines error object, reference to https://www.jsonrpc.org/specification#error_object.
 type Error struct {
-       Code    int64       `json:"code"`
-       Message string      `json:"message"`
-       Data    interface{} `json:"data"`
+       Code    int64  `json:"code"`
+       Message string `json:"message"`
+       Data    any    `json:"data"`
 }
 
 // Request defines Request object, reference to https://www.jsonrpc.org/specification#request_object.
 type Request struct {
        // Jsonrpc string      `json:"jsonrpc"`
-       Method string      `json:"method"`
-       Params interface{} `json:"params"`
-       ID     interface{} `json:"id"` // string, number or null
-       Token  string      `json:"token"`
+       Method string `json:"method"`
+       Params any    `json:"params"`
+       ID     any    `json:"id"` // string, number or null
+       Token  string `json:"token"`
 }
 
 // ReqParamList defines Request.Param of method:list.
 type ReqParamList struct {
-       Fields []string    `json:"fields"`
-       Wheres interface{} `json:"wheres"` // use gin.H, exp: gin.H{"isactive": "='Y'"}
+       Fields []string `json:"fields"`
+       Wheres any      `json:"wheres"` // use gin.H, exp: gin.H{"isactive": "='Y'"}
        Limits struct {
                Offset int64 `json:"offset"`
                Limit  int64 `json:"limit"`
@@ -91,21 +91,21 @@ type ReqParamList struct {
 // Response defines response object, reference to https://www.jsonrpc.org/specification#response_object.
 type Response struct {
        // Jsonrpc string      `json:"jsonrpc"`
-       Result interface{} `json:"result"`
-       Error  Error       `json:"error"`
-       ID     interface{} `json:"id"` // string, number or null
+       Result any   `json:"result"`
+       Error  Error `json:"error"`
+       ID     any   `json:"id"` // string, number or null
 }
 
 // ResResultExec defines Response.Result of method:[add,del,set].
 type ResResultExec struct {
-       Affected  int64         `json:"affected"`
-       Returnids []interface{} `json:"returnids"`
+       Affected  int64 `json:"affected"`
+       Returnids []any `json:"returnids"`
 }
 
 // ResResultList defines Response.Result of method:list.
 type ResResultList struct {
-       Count int64         `json:"count"`
-       Rows  []interface{} `json:"rows"`
+       Count int64 `json:"count"`
+       Rows  []any `json:"rows"`
 }
 
 // Respcode returns a structure of Respnode that message has been translated using language if options.language is set,
@@ -113,12 +113,12 @@ type ResResultList struct {
 // options will be [language]:
 //
 //     language *string: name of language.
-func Respcode(code string, options ...interface{}) Respnode {
+func Respcode(code string, options ...any) Respnode {
        var language *string = nil
        for _, option := range options {
-               switch option.(type) {
+               switch option := option.(type) {
                case *string:
-                       language = option.(*string)
+                       language = option
                }
        }
 
@@ -136,7 +136,7 @@ type RequireResource struct {
 }
 
 // TokenCheckFunc defines function type of check client privilege by token.
-type TokenCheckFunc func(token string, options ...interface{}) bool
+type TokenCheckFunc func(token string, options ...any) bool
 
 // Instance is the framework's instance, used for server & plugin both.
 type Instance struct {
index d34c29d6a997a212ce39ce3d358da488aaa6a985..f453987cee9329c2e561bf3b7e3f4957d1c2f271 100644 (file)
@@ -1,5 +1,5 @@
-// Copyright 2010-2024 nanzoom.com. All Rights Reserved.
+// Copyright 2010-2025 nanzoom.com. All Rights Reserved.
 package uzsdk
 
 // Version is the current opensdk's version.
-const Version = "0.3.2"
+const Version = "0.3.3"