diff --git a/dbtool/dbtool.go b/dbtool/dbtool.go deleted file mode 100644 index 28720da..0000000 --- a/dbtool/dbtool.go +++ /dev/null @@ -1,152 +0,0 @@ -package dbtool - -import ( - "database/sql" - "errors" - "reflect" - "strconv" - "strings" - "time" -) - -// ScanToStruct - scan query rows to struct -func ScanToStruct(rows *sql.Rows, ss interface{}) (ok bool, err error) { - data, err := ResultToMap(rows) - if err != nil { - return false, err - } - if len(data) == 0 { - return false, nil - } - err = MapToStruct(data[0], ss) - if err != nil { - return false, err - } - return true, nil -} - -// ScanToStructAll - -func ScanToStructAll(rows *sql.Rows, ss interface{}) error { - sliceVal := reflect.Indirect(reflect.ValueOf(ss)) - if sliceVal.Kind() != reflect.Slice { - return errors.New("need a pointer to a slice ") - } - vt := sliceVal.Type().Elem() - if vt.Kind() != reflect.Struct { - return errors.New("need struct slice") - } - - data, err := ResultToMap(rows) - if err != nil { - return err - } - - for i := range data { - tmp := reflect.New(vt) - iface := tmp.Interface() - err = MapToStruct(data[i], iface) - if err != nil { - return err - } - sliceVal.Set(reflect.Append(sliceVal, reflect.ValueOf(iface).Elem())) - } - - return nil -} - -// ResultToMap - -func ResultToMap(r *sql.Rows) ([]map[string]interface{}, error) { - result := make([]map[string]interface{}, 0) - cols, err := r.Columns() - if err != nil { - return nil, err - } - vals := make([]sql.RawBytes, len(cols)) - scans := make([]interface{}, len(vals)) - - for i := range vals { - scans[i] = &vals[i] - } - - for r.Next() { - tmp := make(map[string]interface{}) - err := r.Scan(scans...) - if err != nil { - return nil, err - } - for i, v := range vals { - tmp[cols[i]] = v - vals[i] = nil - } - result = append(result, tmp) - } - - return result, nil -} - -// MapToStruct - -func MapToStruct(data map[string]interface{}, out interface{}) error { - ss := reflect.ValueOf(out).Elem() - for i := 0; i < ss.NumField(); i++ { - tag := ss.Type().Field(i).Tag.Get("sql") - name := ss.Type().Field(i).Name - fname := strings.ToLower(name) - if len(tag) > 0 { - fname = tag - } - - if fname == "-" { - continue - } - - d, ok := data[fname] - if !ok { - continue - } - - switch ss.Field(i).Interface().(type) { - case string: - ss.Field(i).SetString(string(d.(sql.RawBytes))) - case int, int8, int16, int32, int64: - str := string(d.(sql.RawBytes)) - tmpi, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return err - } - ss.Field(i).SetInt(tmpi) - case uint, uint8, uint16, uint32, uint64: - str := string(d.(sql.RawBytes)) - tmpi, err := strconv.ParseUint(str, 10, 64) - if err != nil { - return err - } - ss.Field(i).SetUint(tmpi) - case bool: - str := string(d.(sql.RawBytes)) - tmpb, err := strconv.ParseBool(str) - if err != nil { - return err - } - ss.Field(i).SetBool(tmpb) - case float32, float64: - str := string(d.(sql.RawBytes)) - tmpf, err := strconv.ParseFloat(str, 64) - if err != nil { - return err - } - ss.Field(i).SetFloat(tmpf) - case time.Time: - str := string(d.(sql.RawBytes)) - t, err := time.Parse(time.RFC3339, str) - if err != nil { - return err - } - ss.Field(i).Set(reflect.ValueOf(t)) - default: - str := string(d.(sql.RawBytes)) - ss.Field(i).Set(reflect.ValueOf(str)) - } - } - - return nil -} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..59c3f8d --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module git.trj.tw/golang/utils diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..479934e --- /dev/null +++ b/utils.go @@ -0,0 +1,166 @@ +package utils + +import ( + "math" + "os" + "path" + "reflect" + "regexp" + "runtime" + "strings" +) + +// PageObject - +type PageObject struct { + Page int `json:"page" cc:"page"` + Total int `json:"total" cc:"total"` + Offset int `json:"offset" cc:"offset"` + Limit int `json:"limit" cc:"limit"` +} + +// CalcPage - +func CalcPage(count, page, max int) (po PageObject) { + if count < 0 { + count = 0 + } + if page < 1 { + page = 1 + } + if max < 1 { + max = 1 + } + + total := int(math.Ceil(float64(count) / float64(max))) + if total < 1 { + total = 1 + } + if page > total { + page = total + } + offset := (page - 1) * max + if offset > count { + offset = count + } + limit := max + + po = PageObject{} + po.Limit = limit + po.Page = page + po.Offset = offset + po.Total = total + + return +} + +// ToMap struct to map[string]interface{} +func ToMap(ss interface{}) map[string]interface{} { + t := reflect.ValueOf(ss) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + smap := make(map[string]interface{}) + mtag := regexp.MustCompile(`cc:\"(.+)\"`) + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + tag := string(t.Type().Field(i).Tag) + str := mtag.FindStringSubmatch(tag) + name := t.Type().Field(i).Name + leveling := false + if len(str) > 1 { + strArr := strings.Split(str[1], ",") + name = strArr[0] + if len(strArr) > 1 && strArr[1] == "<<" { + leveling = true + } + } + + if f.Kind() == reflect.Slice { + if name == "-" { + continue + } + if f.Len() > 0 && f.Index(0).Kind() != reflect.Struct { + smap[name] = f.Interface() + continue + } + + tmp := make([]map[string]interface{}, f.Len()) + for i := 0; i < f.Len(); i++ { + tmp[i] = ToMap(f.Index(i).Interface()) + } + smap[name] = tmp + continue + } + + if f.Kind() == reflect.Struct { + if name == "-" && leveling == false { + continue + } + + if leveling == true { + tmp := ToMap(f.Interface()) + for k, v := range tmp { + smap[k] = v + } + } else if name != "-" && leveling == false { + smap[name] = f.Interface() + } + continue + } + + if name != "-" { + smap[name] = f.Interface() + } + } + + return smap +} + +// ParsePath - parse file path to absPath +func ParsePath(dst string) string { + wd, err := os.Getwd() + if err != nil { + wd = "" + } + + if []rune(dst)[0] == '~' { + home := UserHomeDir() + if len(home) > 0 { + dst = strings.Replace(dst, "~", home, -1) + } + } + + if path.IsAbs(dst) { + dst = path.Clean(dst) + return dst + } + + str := path.Join(wd, dst) + str = path.Clean(str) + return str +} + +// UserHomeDir - get user home directory +func UserHomeDir() string { + env := "HOME" + if runtime.GOOS == "windows" { + env = "USERPROFILE" + } else if runtime.GOOS == "plan9" { + env = "home" + } + return os.Getenv(env) +} + +// CheckExists - check file exists +func CheckExists(filePath string, allowDir bool) bool { + filePath = ParsePath(filePath) + stat, err := os.Stat(filePath) + if err != nil && !os.IsExist(err) { + return false + } + if !allowDir && stat.IsDir() { + return false + } + return true +} diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000..f9f1224 --- /dev/null +++ b/utils_test.go @@ -0,0 +1,58 @@ +package utils + +import ( + "testing" + "time" +) + +// Commands - struct +type Commands struct { + Cmd string `db:"cmd" cc:"cmd"` + Message string `db:"message" cc:"message"` + Group string `db:"group" cc:"group"` + Ctime time.Time `db:"ctime" cc:"ctime"` + Mtime time.Time `db:"mtime" cc:"ctime"` +} + +// CommandsWithGroup - +type CommandsWithGroup struct { + Commands `cc:"-,<<"` + GroupName string `db:"group_name" cc:"group_name"` +} + +func TestToMap(t *testing.T) { + cmd := Commands{} + cmd.Cmd = "test" + cmd.Message = "test message" + cmd.Group = "" + cmd.Ctime = time.Now() + cmd.Mtime = time.Now() + + cmdWGroup := CommandsWithGroup{} + cmdWGroup.Commands = cmd + cmdWGroup.GroupName = "asd" + + ToMap(cmdWGroup) +} + +func TestCalcPage(t *testing.T) { + page := CalcPage(10, 1, 10) + if page.Page != 1 { + t.Error("Page Calc fail") + } + if page.Total != 1 { + t.Error("Page Calc fail") + } + if page.Limit != 10 { + t.Error("limit calc fail") + } + if page.Offset != 0 { + t.Error("offset calc fail") + } +} + +func BenchmarkCalcPage(b *testing.B) { + for i := 0; i < b.N; i++ { + CalcPage(10000, 30, 10) + } +}