commit 4d539bba28ee27ca2a6261eaaa2418a9df3ff7a9 Author: 李光春 Date: Tue May 24 23:54:18 2022 +0800 - init diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..c56c479 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,17 @@ +kind: pipeline +type: docker +name: clone + +steps: + - name: Test + image: golang:1.18 + commands: + - go env -w GO111MODULE=on + - go env -w GOPROXY=https://goproxy.cn,direct + - go test -v ./... + - name: Benchmark + image: golang:1.18 + commands: + - go env -w GO111MODULE=on + - go env -w GOPROXY=https://goproxy.cn,direct + - go test -bench=. -benchmem \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4794692 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.env +.git +.svn +.idea +.vscode +*.log +goinit.sh +gomod.sh +/vendor/ \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..44d34b8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module go.dtapp.net/gophp + +go 1.18 + +require go.dtapp.net/gostring v1.0.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5d8b1b3 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +go.dtapp.net/gostring v1.0.3 h1:KSOq4D77/g5yZN/bqWfZ0kOOaPr/P1240vg03+XdENI= +go.dtapp.net/gostring v1.0.3/go.mod h1:+ggrOvgQDQturi1QGsXEpyRN/ZPoRDaqhMujIk5lrgQ= diff --git a/gophp.go b/gophp.go new file mode 100644 index 0000000..5c41b63 --- /dev/null +++ b/gophp.go @@ -0,0 +1,116 @@ +package gophp + +import ( + "go.dtapp.net/gophp/serialize" + "strconv" + "strings" + "unicode" +) + +// Serialize 序列 +func Serialize(value any) ([]byte, error) { + return serialize.Marshal(value) +} + +// Unserialize 反序列 +func Unserialize(data []byte) (any, error) { + return serialize.UnMarshal(data) +} +func BaseConvert(number string, frombase, tobase int) (string, error) { + i, err := strconv.ParseInt(number, frombase, 0) + if err != nil { + return "", err + } + return strconv.FormatInt(i, tobase), nil +} + +// ArrayColumn array_column() +func ArrayColumn(input map[string]map[string]any, columnKey string) []any { + columns := make([]any, 0, len(input)) + for _, val := range input { + if v, ok := val[columnKey]; ok { + columns = append(columns, v) + } + } + return columns +} + +// Strtr strtr() +// +// If the parameter length is 1, type is: map[string]string +// Strtr("baab", map[string]string{"ab": "01"}) will return "ba01" +// If the parameter length is 2, type is: string, string +// Strtr("baab", "ab", "01") will return "1001", a => 0; b => 1. +func Strtr(haystack string, params ...interface{}) string { + ac := len(params) + if ac == 1 { + pairs := params[0].(map[string]string) + length := len(pairs) + if length == 0 { + return haystack + } + oldnew := make([]string, length*2) + for o, n := range pairs { + if o == "" { + return haystack + } + oldnew = append(oldnew, o, n) + } + return strings.NewReplacer(oldnew...).Replace(haystack) + } else if ac == 2 { + from := params[0].(string) + to := params[1].(string) + trlen, lt := len(from), len(to) + if trlen > lt { + trlen = lt + } + if trlen == 0 { + return haystack + } + str := make([]uint8, len(haystack)) + var xlat [256]uint8 + var i int + var j uint8 + if trlen == 1 { + for i = 0; i < len(haystack); i++ { + if haystack[i] == from[0] { + str[i] = to[0] + } else { + str[i] = haystack[i] + } + } + return string(str) + } + // trlen != 1 + for { + xlat[j] = j + if j++; j == 0 { + break + } + } + for i = 0; i < trlen; i++ { + xlat[from[i]] = to[i] + } + for i = 0; i < len(haystack); i++ { + str[i] = xlat[haystack[i]] + } + return string(str) + } + return haystack +} +func Rtrim(str string, characterMask ...string) string { + if len(characterMask) == 0 { + return strings.TrimRightFunc(str, unicode.IsSpace) + } + return strings.TrimRight(str, characterMask[0]) +} +func StrPad(input string, padLength int, padString string) string { + output := padString + for padLength > len(output) { + output += output + } + if len(input) >= padLength { + return input + } + return output[:padLength-len(input)] + input +} diff --git a/gophp_test.go b/gophp_test.go new file mode 100644 index 0000000..9813389 --- /dev/null +++ b/gophp_test.go @@ -0,0 +1,78 @@ +package gophp + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestSerialize(t *testing.T) { + t.Log(Unserialize([]byte(`a:1:{s:4:"test";i:34343;}`))) + serialize, _ := Serialize(map[string]interface{}{ + "test": 34343, + }) + t.Log(string(serialize)) +} + +func BenchmarkSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + serialize, _ := Serialize(map[string]interface{}{ + "test": 34343, + }) + b.Log(string(serialize)) + } +} + +func TestUnserialize(t *testing.T) { + t.Log(Unserialize([]byte(`a:1:{s:4:"test";i:34343;}`))) +} + +func BenchmarkUnserialize(b *testing.B) { + for i := 0; i < b.N; i++ { + b.Log(Unserialize([]byte(`a:1:{s:4:"test";i:34343;}`))) + } +} + +type student struct { + Amount string `json:"amount"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` + UserID string `json:"user_id"` +} + +func TestPhp2(t *testing.T) { + stu := student{ + Amount: "1", + } + + m := make(map[string]interface{}) + // struct 转json + j, _ := json.Marshal(stu) + // json 转map + json.Unmarshal(j, &m) + + fmt.Printf("Serialize %s\n", j) + + serialize, err := Serialize(j) + fmt.Printf("Serialize %s\n", serialize) + fmt.Printf("Serialize %s\n", err) +} + +func TestUnserialize2(t *testing.T) { + unserialize, _ := Unserialize([]byte(`a:2:{i:0;a:4:{s:7:"user_id";s:5:"10118";s:6:"amount";s:5:"69.00";s:11:"create_time";s:19:"2021-01-04 16:29:03";s:11:"update_time";s:19:"2021-06-15 16:02:46";}i:1;a:4:{s:7:"user_id";s:5:"10088";s:6:"amount";s:5:"10.00";s:11:"create_time";s:19:"2021-01-04 15:46:10";s:11:"update_time";s:19:"2021-06-15 15:50:06";}}`)) + fmt.Printf("%s\n", unserialize) + arr, _ := json.Marshal(unserialize) + fmt.Printf("arr:%s\n", arr) + var stu []student + _ = json.Unmarshal(arr, &stu) + fmt.Printf("stu:%v\n", stu) +} + +func TestUnserialize3(t *testing.T) { + unserialize, err := Unserialize([]byte(`a:3:{s:4:"self";a:5:{s:2:"id";i:32;s:7:"user_id";i:10118;s:16:"merchant_user_id";i:10150;s:5:"level";i:3;s:5:"split";d:2.825;}s:5:"split";a:2:{i:0;a:7:{s:2:"id";i:12;s:7:"user_id";i:10000;s:13:"agent_user_id";i:10088;s:16:"user_golden_bean";d:5;s:5:"level";i:1;s:11:"calculation";d:0.010000000000000002;s:5:"split";d:0.57;}i:1;a:7:{s:2:"id";i:19;s:7:"user_id";i:10088;s:13:"agent_user_id";i:10118;s:16:"user_golden_bean";d:4;s:5:"level";i:2;s:11:"calculation";d:0.04;s:5:"split";d:2.26;}}s:11:"golden_bean";a:6:{s:4:"fans";d:56.5;s:12:"fan_referrer";d:16.95;s:8:"merchant";d:4.5200000000000005;s:5:"agent";d:2.825;s:5:"count";d:80.795;s:2:"bf";a:9:{s:2:"id";i:2;s:7:"user_id";i:10088;s:4:"fans";d:50;s:12:"fan_referrer";d:30;s:8:"merchant";d:8;s:6:"system";d:0;s:5:"agent";d:5;s:11:"create_time";s:19:"2020-11-20 14:47:21";s:11:"update_time";s:19:"2020-11-20 14:47:21";}}}`)) + fmt.Printf("%+v\n", unserialize) + if err != nil { + fmt.Printf("err:%v\n", err) + return + } +} diff --git a/serialize/less.go b/serialize/less.go new file mode 100644 index 0000000..d4cb912 --- /dev/null +++ b/serialize/less.go @@ -0,0 +1,45 @@ +package serialize + +import ( + "reflect" + "strings" +) + +func numericalValue(value reflect.Value) (float64, bool) { + switch value.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(value.Int()), true + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(value.Uint()), true + + case reflect.Float32, reflect.Float64: + return value.Float(), true + + default: + return 0, false + } +} + +func lessValue(a, b reflect.Value) bool { + aValue, aNumerical := numericalValue(a) + bValue, bNumerical := numericalValue(b) + + if aNumerical && bNumerical { + return aValue < bValue + } + + if !aNumerical && !bNumerical { + // In theory this should mean they are both strings. In reality + // they could be any other type and the String() representation + // will be something like "" if it is not a string. Since + // distinct values of non-strings still return the same value + // here that's what makes the ordering undefined. + return strings.Compare(a.String(), b.String()) < 0 + } + + // Numerical values are always treated as less than other types + // (including strings that might represent numbers themselves). The + // inverse is also true. + return aNumerical && !bNumerical +} diff --git a/serialize/serialize.go b/serialize/serialize.go new file mode 100644 index 0000000..5f30815 --- /dev/null +++ b/serialize/serialize.go @@ -0,0 +1,108 @@ +package serialize + +import ( + "bytes" + "fmt" + "go.dtapp.net/gostring" + "reflect" + "sort" +) + +func Marshal(value interface{}) ([]byte, error) { + if value == nil { + return MarshalNil(), nil + } + t := reflect.TypeOf(value) + switch t.Kind() { + case reflect.Bool: + return MarshalBool(value.(bool)), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return MarshalNumber(value), nil + case reflect.String: + return MarshalString(value.(string)), nil + case reflect.Map: + return MarshalMap(value) + case reflect.Slice: + return MarshalSlice(value) + default: + return nil, fmt.Errorf("marshal: Unknown type %T with value %#v", t, value) + } +} + +func MarshalNil() []byte { + return []byte("N;") +} + +func MarshalBool(value bool) []byte { + if value { + return []byte("b:1;") + } + return []byte("b:0;") +} + +func MarshalNumber(value interface{}) []byte { + var val string + isFloat := false + switch value.(type) { + default: + val = "0" + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + val, _ = gostring.NumericalToString(value) + case float32, float64: + val, _ = gostring.NumericalToString(value) + isFloat = true + } + if isFloat { + return []byte("d:" + val + ";") + + } else { + return []byte("i:" + val + ";") + } +} + +func MarshalString(value string) []byte { + return []byte(fmt.Sprintf("s:%d:\"%s\";", len(value), value)) +} + +func MarshalMap(value interface{}) ([]byte, error) { + s := reflect.ValueOf(value) + mapKeys := s.MapKeys() + sort.Slice(mapKeys, func(i, j int) bool { + return lessValue(mapKeys[i], mapKeys[j]) + }) + var buffer bytes.Buffer + for _, mapKey := range mapKeys { + m, err := Marshal(mapKey.Interface()) + if err != nil { + return nil, err + } + buffer.Write(m) + + m, err = Marshal(s.MapIndex(mapKey).Interface()) + if err != nil { + return nil, err + } + buffer.Write(m) + } + return []byte(fmt.Sprintf("a:%d:{%s}", s.Len(), buffer.String())), nil +} + +func MarshalSlice(value interface{}) ([]byte, error) { + s := reflect.ValueOf(value) + var buffer bytes.Buffer + for i := 0; i < s.Len(); i++ { + m, err := Marshal(i) + if err != nil { + return nil, err + } + buffer.Write(m) + m, err = Marshal(s.Index(i).Interface()) + if err != nil { + return nil, err + } + buffer.Write(m) + } + return []byte(fmt.Sprintf("a:%d:{%s}", s.Len(), buffer.String())), nil +} diff --git a/serialize/unserialize.go b/serialize/unserialize.go new file mode 100644 index 0000000..b5bfc68 --- /dev/null +++ b/serialize/unserialize.go @@ -0,0 +1,227 @@ +package serialize + +import ( + "bytes" + "fmt" + "go.dtapp.net/gostring" + "strconv" +) + +const UnserializableObjectMaxLen = int64(10 * 1024 * 1024 * 1024) + +func UnMarshal(data []byte) (interface{}, error) { + reader := bytes.NewReader(data) + return unMarshalByReader(reader) +} + +func unMarshalByReader(reader *bytes.Reader) (interface{}, error) { + for { + if token, _, err := reader.ReadRune(); err == nil { + switch token { + default: + return nil, fmt.Errorf("UnMarshal: Unknown token %#U", token) + case 'N': + return unMarshalNil(reader) + case 'b': + return unMarshalBool(reader) + case 'i': + return unMarshalNumber(reader, false) + case 'd': + return unMarshalNumber(reader, true) + case 's': + return unMarshalString(reader, true) + case 'a': + return unMarshalArray(reader) + } + } + return nil, nil + } +} + +func unMarshalNil(reader *bytes.Reader) (interface{}, error) { + err := expect(reader, ';') + if err != nil { + return nil, err + } + return nil, nil +} + +func unMarshalBool(reader *bytes.Reader) (interface{}, error) { + var ( + raw rune + err error + ) + err = expect(reader, ':') + if err != nil { + return nil, err + } + if raw, _, err = reader.ReadRune(); err != nil { + return nil, fmt.Errorf("UnMarshal: Error while reading bool value: %v", err) + } + err = expect(reader, ';') + if err != nil { + return nil, err + } + return raw == '1', nil +} + +func unMarshalNumber(reader *bytes.Reader, isFloat bool) (interface{}, error) { + var ( + raw string + err error + val interface{} + ) + err = expect(reader, ':') + if err != nil { + return nil, err + } + if raw, err = readUntil(reader, ';'); err != nil { + return nil, fmt.Errorf("UnMarshal: Error while reading number value: %v", err) + } else { + if isFloat { + if val, err = strconv.ParseFloat(raw, 64); err != nil { + return nil, fmt.Errorf("UnMarshal: Unable to convert %s to float: %v", raw, err) + } + } else { + if val, err = strconv.Atoi(raw); err != nil { + return nil, fmt.Errorf("UnMarshal: Unable to convert %s to int: %v", raw, err) + } + } + } + return val, nil +} + +func unMarshalString(reader *bytes.Reader, isFinal bool) (interface{}, error) { + var ( + err error + val interface{} + strLen int + readLen int + ) + strLen, err = readLength(reader) + err = expect(reader, '"') + if err != nil { + return nil, err + } + if strLen > 0 { + buf := make([]byte, strLen, strLen) + if readLen, err = reader.Read(buf); err != nil { + return nil, fmt.Errorf("UnMarshal: Error while reading string value: %v", err) + } else { + if readLen != strLen { + return nil, fmt.Errorf("UnMarshal: Unable to read string. Expected %d but have got %d bytes", strLen, readLen) + } else { + val = string(buf) + } + } + } + err = expect(reader, '"') + if err != nil { + return nil, err + } + if isFinal { + err = expect(reader, ';') + if err != nil { + return nil, err + } + } + return val, nil +} + +func unMarshalArray(reader *bytes.Reader) (interface{}, error) { + var arrLen int + var err error + val := make(map[string]interface{}) + arrLen, err = readLength(reader) + if err != nil { + return nil, err + } + err = expect(reader, '{') + if err != nil { + return nil, err + } + indexLen := 0 + for i := 0; i < arrLen; i++ { + k, err := unMarshalByReader(reader) + if err != nil { + return nil, err + } + v, err := unMarshalByReader(reader) + if err != nil { + return nil, err + } + switch t := k.(type) { + default: + return nil, fmt.Errorf("UnMarshal: Unexpected key type %T", t) + case string: + stringKey, _ := k.(string) + val[stringKey] = v + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + stringKey, _ := gostring.NumericalToString(k) + val[stringKey] = v + if i == k { + indexLen++ + } + } + } + err = expect(reader, '}') + if err != nil { + return nil, err + } + if indexLen == arrLen { + var slice []interface{} + for _, row := range val { + slice = append(slice, row) + } + return slice, nil + } + return val, nil +} + +func expect(reader *bytes.Reader, expected rune) error { + if token, _, err := reader.ReadRune(); err != nil { + return fmt.Errorf("UnMarshal: Error while reading expected rune %#U: %v", expected, err) + } else if token != expected { + return fmt.Errorf("UnMarshal: Expected %#U but have got %#U", expected, token) + } + return nil +} + +func readUntil(reader *bytes.Reader, stop rune) (string, error) { + var ( + token rune + err error + ) + buf := bytes.NewBuffer([]byte{}) + for { + if token, _, err = reader.ReadRune(); err != nil || token == stop { + break + } else { + buf.WriteRune(token) + } + } + return buf.String(), err +} + +func readLength(reader *bytes.Reader) (int, error) { + var ( + raw string + err error + val int + ) + err = expect(reader, ':') + if err != nil { + return 0, err + } + if raw, err = readUntil(reader, ':'); err != nil { + return 0, fmt.Errorf("UnMarshal: Error while reading length of value: %v", err) + } else { + if val, err = strconv.Atoi(raw); err != nil { + return 0, fmt.Errorf("UnMarshal: Unable to convert %s to int: %v", raw, err) + } else if int64(val) > UnserializableObjectMaxLen { + return 0, fmt.Errorf("UnMarshal: Unserializable object length looks too big(%d). If you are sure you wanna unserialise it, please increase UNSERIALIZABLE_OBJECT_MAX_LEN const", val) + val = 0 + } + } + return val, nil +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..0573dbd --- /dev/null +++ b/version.go @@ -0,0 +1,3 @@ +package gophp + +const Version = "1.0.0" diff --git a/version_test.go b/version_test.go new file mode 100644 index 0000000..3b23f4d --- /dev/null +++ b/version_test.go @@ -0,0 +1,7 @@ +package gophp + +import "testing" + +func TestVersion(t *testing.T) { + t.Log(Version) +}