├── .github └── workflows │ └── go.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── array ├── array.go └── array_test.go ├── astutil ├── expr.go ├── expr_test.go ├── log.go ├── node.go ├── package.go ├── query.go └── query_test.go ├── char ├── char.go └── char_test.go ├── collection ├── skiplist.go └── skiplist_test.go ├── crypt ├── crypt.go └── crypt_test.go ├── date ├── carbon │ ├── carbon.go │ ├── carbon_test.go │ ├── constant.go │ └── location.go ├── date.go └── date_test.go ├── dict ├── map_dict.go └── map_dict_test.go ├── digo ├── digo.go ├── digo_impl.go ├── digo_impl_test.go ├── digo_stub.go ├── digo_test.go └── readme.md ├── dump ├── cli_dumper.go ├── cli_dumper_test.go ├── dump.go ├── dump_test.go ├── helper.go ├── helper_test.go ├── readme.md ├── serialize.go ├── serialize_test.go └── stub.s ├── dynamic ├── caller.go ├── caller_test.go ├── compact.go ├── function.go ├── function_test.go ├── init_test.go ├── log.go ├── readme.md ├── variant_name.go └── variant_name_test.go ├── encoding ├── base64.go ├── encoding_test.go └── json.go ├── firewall ├── firewall.go ├── limiter.go ├── mutexlimiter.go ├── mutexlimiter_test.go ├── readme.md ├── resource_limiter.go ├── semaphore.go ├── semaphore_test.go ├── sleeplimiter.go └── sleeplimiter_test.go ├── go.mod ├── go.sum ├── gotemplate └── function.go ├── http └── form │ └── form.go ├── httpclient ├── client.go ├── go.mod ├── go.sum ├── readme.md ├── request.go ├── request_test.go ├── response.go ├── wrapper.go └── wrapper_test.go ├── mapx └── slice_map.go ├── p ├── compact.go ├── compact_alias_internal_test.go ├── compact_alias_test.go ├── compact_test.go ├── dump.go ├── dump_test.go ├── goroutine.go ├── goroutine_test.go └── readme.md ├── pipe ├── examples │ └── cpipe.c ├── exec_pipe.go ├── exec_pipe_test.go ├── pipe.go ├── pipes.go ├── pipes_test.go └── readme.md ├── string ├── string.go └── string_test.go ├── test ├── assert.go ├── assert_test.go ├── assert_util.go ├── assert_util_test.go ├── file.go └── runner.go ├── version ├── compare.go ├── compare_test.go ├── helper.go ├── semver.go └── semver_test.go └── word ├── case_convert.go └── case_convert_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | .idea/** 16 | .vscode/** 17 | 18 | 19 | .vendor/** 20 | 21 | **/main/** 22 | *.pprof 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.19.x" 5 | - "1.18.x" 6 | 7 | before_script: 8 | - gcc ./pipe/examples/cpipe.c -o ./pipe/a.out 9 | 10 | script: go test ./... 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kretech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *XGo* 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/Kretech/xgo)](https://goreportcard.com/report/github.com/Kretech/xgo) 4 | [![Build Status](https://travis-ci.org/Kretech/xgo.svg?branch=master)](https://travis-ci.org/Kretech/xgo) 5 | [![Coverage Status](https://coveralls.io/repos/github/Kretech/xgo/badge.svg)](https://coveralls.io/github/Kretech/xgo) 6 | 7 | Go Components 8 | 9 | ## Release 10 | 11 | - [digo](https://github.com/Kretech/xgo/tree/master/digo) dependency-injector container 12 | - [dumper](https://github.com/Kretech/xgo/tree/master/dump) var_dumper could show var names by dynamic-lib 13 | - [httpclient](https://github.com/Kretech/xgo/tree/master/httpclient) fantastic httpclient 14 | - [dynamic](https://github.com/Kretech/xgo/tree/master/dynamic) use ast and reflect simply at runtime 15 | - [firewall](https://github.com/Kretech/xgo/tree/master/firewall) rate limiter in the meanwhile 16 | - [version](https://github.com/Kretech/xgo/tree/master/version) semantic version's operation and compares 17 | - [string](https://github.com/Kretech/xgo/blob/master/string/string.go) wraped string class using chain's functions 18 | - [astutil](https://github.com/Kretech/xgo/tree/master/astutil) helpers for ast package 19 | - [pipe](https://github.com/Kretech/xgo/tree/master/pipe) concurrently call bin in pipe 20 | - [gotemplate](https://github.com/Kretech/xgo/tree/master/gotemplate) helpers for html/template 21 | 22 | ## todo 23 | 24 | - [Carbon](https://github.com/Kretech/xgo/tree/master/date/carbon) wraped time class like [php-carbon](https://carbon.nesbot.com/) 25 | - [word](https://github.com/Kretech/xgo/tree/master/word) helpers for words 26 | - GoQuery: helpers use ast like jQuery 27 | - Scanner: code scanner 28 | - Dict: like Php/Array or Python/dict 29 | - Array: slice without sliceHeader 30 | 31 | ## Requirements 32 | 33 | Go >= 1.10 34 | 35 | ## Inspired By 36 | 37 | - https://symfony.com/ 38 | - https://commons.apache.org/ 39 | 40 | -------------------------------------------------------------------------------- /array/array.go: -------------------------------------------------------------------------------- 1 | package array 2 | 3 | import ( 4 | "bytes" 5 | "container/list" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/Kretech/xgo/dict" 10 | ) 11 | 12 | type any = interface{} 13 | 14 | // Stream 提供对一组数据集的操作,接口 api 参考自 Laravel 的 Collection 和 Java 的 stream 15 | type Array struct { 16 | *list.List 17 | } 18 | 19 | func (a *Array) String() string { 20 | b := bytes.NewBuffer([]byte{}) 21 | a.each(func(it *list.Element) { 22 | b.WriteString(fmt.Sprintf("%v", it.Value)) 23 | if it.Next() != nil { 24 | b.WriteByte(' ') 25 | } 26 | }) 27 | 28 | return b.String() 29 | } 30 | 31 | func newArray() *Array { 32 | return &Array{ 33 | list.New(), 34 | } 35 | } 36 | 37 | // Values 通过传入一组任意元素来构造 Array 38 | func Values(elements ...any) *Array { 39 | a := newArray() 40 | for idx, _ := range elements { 41 | a.PushBack(elements[idx]) 42 | } 43 | return a 44 | } 45 | 46 | // Slice 通过 slice 构造 Array 47 | func Slice(slice any) *Array { 48 | e := reflect.ValueOf(slice) 49 | if e.Kind() != reflect.Slice { 50 | panic("array.Slice() must receive a slice ([]type)") 51 | } 52 | 53 | l := newArray() 54 | for i := 0; i < e.Len(); i++ { 55 | l.PushBack(e.Index(i).Interface()) 56 | } 57 | 58 | return l 59 | } 60 | 61 | func (this *Array) KeyBy(field string) *dict.MapDict { 62 | d := dict.NewMapDict() 63 | 64 | for it := this.Front(); it != nil; it = it.Next() { 65 | key := getField(it.Value, field) 66 | d.Set(key, it.Value) 67 | } 68 | return d 69 | } 70 | 71 | func (this *Array) each(fn func(*list.Element)) { 72 | for it := this.Front(); it != nil; it = it.Next() { 73 | fn(it) 74 | } 75 | } 76 | 77 | func getField(v any, field string) any { 78 | 79 | elem := reflect.ValueOf(v).Elem() 80 | switch elem.Kind() { 81 | case reflect.Struct: 82 | return getStructField(elem, field) 83 | case reflect.Map: 84 | return getMapField(elem, field) 85 | } 86 | 87 | panic("") 88 | } 89 | 90 | func getMapField(v reflect.Value, field string) any { 91 | return v.MapIndex(reflect.ValueOf(field)).Interface() 92 | } 93 | 94 | func getStructField(v reflect.Value, field string) any { 95 | return v.FieldByName(field).Interface() 96 | } 97 | 98 | func toString(v any) string { 99 | return fmt.Sprintf("%v", v) 100 | } 101 | -------------------------------------------------------------------------------- /array/array_test.go: -------------------------------------------------------------------------------- 1 | package array 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/Kretech/xgo/test" 9 | ) 10 | 11 | type people struct { 12 | Id int 13 | Name string 14 | extra interface{} 15 | } 16 | 17 | func TestArray_Demo(t *testing.T) { 18 | peoples := []*people{{3, "zhang", nil}, {5, "wang", nil}, {4, "li", nil}, {6, "zhao", nil}} 19 | Values(peoples[0], peoples[1], peoples[2], peoples[3]) 20 | } 21 | 22 | func TestArray_KeyBy(t *testing.T) { 23 | 24 | peoples := []*people{{3, "zhang", nil}, {5, "wang", nil}, {4, "li", nil}, {6, "zhao", nil}} 25 | a1 := Values(peoples[0], peoples[1], peoples[2], peoples[3]) 26 | 27 | test.AssertEqual(t, a1.String(), `&{3 zhang } &{5 wang } &{4 li } &{6 zhao }`) 28 | 29 | // d1 := a1.KeyBy("Id") 30 | v := getStructField(reflect.ValueOf(peoples[0]).Elem(), "Id") 31 | fmt.Println(v) 32 | 33 | // fmt.Println(d1) 34 | // fmt.Println(d1.Data()) 35 | // fmt.Println(encoding.JsonEncode(d1.Data())) 36 | 37 | // a2 := Slice(peoples) 38 | // fmt.Println(a2) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /astutil/expr.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "strings" 7 | ) 8 | 9 | //ExprString generates code by ast.Expr like printer.Fprint() 10 | // 经过拼装,可能与源码不完全一致 11 | // TODO 2019.9.28 最开始不知道有 printer 这个包,手写了一坨,后面可能会直接接过去。。 12 | // Deprecated 13 | func ExprString(expr ast.Expr) (name string) { 14 | switch exp := expr.(type) { 15 | 16 | // 字面值 literal 17 | case *ast.BasicLit: 18 | name = exp.Value 19 | 20 | // a.b 21 | case *ast.SelectorExpr: 22 | name = ExprString(exp.X) + "." + exp.Sel.Name 23 | 24 | case *ast.CompositeLit: 25 | name = ExprString(exp.Type) + ExprString(exp.Elts[0]) 26 | 27 | if len(exp.Elts) > 0 { 28 | elts := make([]string, 0, len(exp.Elts)) 29 | for _, elt := range exp.Elts { 30 | elts = append(elts, ExprString(elt)) 31 | } 32 | name = `{` + strings.Join(elts, `,`) + `}` 33 | } 34 | 35 | case *ast.MapType: 36 | name = fmt.Sprintf("map[%s]%s", ExprString(exp.Key), ExprString(exp.Value)) 37 | 38 | // @todo interface 先都显示 interface{} 39 | case *ast.InterfaceType: 40 | name = `interface{}` 41 | 42 | case *ast.KeyValueExpr: 43 | name = ExprString(exp.Key) + ":" + ExprString(exp.Value) 44 | 45 | case *ast.ArrayType: 46 | name = "[" + ExprString(exp.Len) + "]" + ExprString(exp.Elt) 47 | 48 | case *ast.StarExpr: 49 | name = "*" + ExprString(exp.X) 50 | 51 | // a 52 | case *ast.Ident: 53 | name = exp.Name 54 | 55 | case *ast.CallExpr: 56 | name = ExprString(exp.Fun) 57 | 58 | name += `(` 59 | 60 | if len(exp.Args) > 0 { 61 | args := make([]string, 0, len(exp.Args)) 62 | for _, arg := range exp.Args { 63 | args = append(args, ExprString(arg)) 64 | } 65 | name += strings.Join(args, `,`) 66 | } 67 | 68 | name += `)` 69 | 70 | // &a 71 | case *ast.UnaryExpr: 72 | name = "&" + ExprString(exp.X) 73 | 74 | // a["3"] 75 | case *ast.IndexExpr: 76 | name = ExprString(exp.X) + "[" + ExprString(exp.Index) + "]" 77 | 78 | case *ast.BinaryExpr: 79 | name = ExprString(exp.X) + exp.Op.String() + ExprString(exp.Y) 80 | 81 | case nil: 82 | return "" 83 | 84 | default: 85 | name = fmt.Sprintf("Unknown(%T)", expr) 86 | } 87 | 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /astutil/expr_test.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "go/ast" 5 | "testing" 6 | ) 7 | 8 | func TestExprString(t *testing.T) { 9 | exprs, err := Find(astFile, []interface{}{new(ast.Expr)}) 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | 14 | for _, exp := range exprs { 15 | t.Log(SrcOf(exp), ExprString(exp.(ast.Expr))) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /astutil/log.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | type Writer struct { 11 | io.Writer 12 | } 13 | 14 | func (w *Writer) Write(p []byte) (n int, err error) { 15 | return w.Writer.Write(p) 16 | } 17 | 18 | func (w *Writer) SetOutput(writer io.Writer) { 19 | w.Writer = writer 20 | } 21 | 22 | var logOutput = &Writer{ioutil.Discard} 23 | 24 | func SetLogOutput(writer io.Writer) { 25 | logOutput.Writer = writer 26 | } 27 | 28 | func EnableLog() { 29 | SetLogOutput(os.Stderr) 30 | } 31 | 32 | func DisableLog() { 33 | SetLogOutput(ioutil.Discard) 34 | } 35 | 36 | var vlog = log.New(logOutput, `ast_util: `, log.LstdFlags|log.Lshortfile) 37 | -------------------------------------------------------------------------------- /astutil/node.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "go/ast" 5 | "go/printer" 6 | "go/token" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | //SrcOf print ast.Node to a string 12 | func SrcOf(node ast.Node) string { 13 | buf := strings.Builder{} 14 | err := printer.Fprint(&buf, token.NewFileSet(), node) 15 | if err != nil { 16 | vlog.Println(`print error `, err) 17 | } 18 | return buf.String() 19 | } 20 | 21 | func Name(node ast.Node) string { 22 | return reflect.TypeOf(node).String() 23 | } 24 | 25 | func typeNoPtr(v interface{}) (t reflect.Type) { 26 | if v == nil { 27 | return 28 | } 29 | 30 | t = reflect.TypeOf(v) 31 | if t.Kind() == reflect.Ptr { 32 | t = t.Elem() 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /astutil/package.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "os" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | var ( 15 | // 开了缓存,ops 可以提高三个数量级 16 | OptPackageCache = true 17 | ) 18 | 19 | // ReadPackage is simple wrapper for parserDir 20 | // use pkgName with last segment in pkgPath 21 | func ReadPackage(pkgPath string) (pkg *ast.Package, err error) { 22 | short := `` 23 | for i := len(pkgPath) - 1; i >= 0; i-- { 24 | if pkgPath[i] == '/' { 25 | short = pkgPath[:i] 26 | break 27 | } 28 | } 29 | return ReadPackageWithName(pkgPath, short, `.`, func(info os.FileInfo) bool { 30 | return true 31 | }) 32 | } 33 | 34 | var pkgCache sync.Map 35 | 36 | // ReadPackageWithName read package with specified package name 37 | // fileScope used for cache key 38 | func ReadPackageWithName(pkgPath string, pkgName string, fileScope string, filter func(os.FileInfo) bool) (pkg *ast.Package, err error) { 39 | 40 | if !OptPackageCache { 41 | return readPackageWithNameNoCache(pkgPath, pkgName, filter) 42 | } 43 | 44 | cacheKey := pkgPath + `|` + pkgName + `|` + fileScope 45 | value, ok := pkgCache.Load(cacheKey) 46 | if ok { 47 | pkg = value.(*ast.Package) 48 | return 49 | } 50 | 51 | pkg, err = readPackageWithNameNoCache(pkgPath, pkgName, filter) 52 | 53 | pkgCache.Store(cacheKey, pkg) 54 | 55 | return 56 | } 57 | 58 | func readPackageWithNameNoCache(pkgPath string, pkgName string, filter func(os.FileInfo) bool) (pkg *ast.Package, err error) { 59 | pkgs, err := parser.ParseDir(token.NewFileSet(), pkgPath, filter, parser.ParseComments) 60 | if err != nil { 61 | err = errors.WithStack(err) 62 | return 63 | } 64 | 65 | pkg, ok := pkgs[pkgName] 66 | if !ok { 67 | keys := []string{} 68 | for name := range pkgs { 69 | keys = append(keys, name) 70 | } 71 | err = errors.Errorf("no package %s in [%s]", pkgName, strings.Join(keys, `,`)) 72 | } 73 | 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /astutil/query.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "go/ast" 5 | "reflect" 6 | ) 7 | 8 | var ( 9 | OptReturnOnError = true 10 | ) 11 | 12 | // Find returns a slice of sub-nodes like jQuery 13 | func Find(parent ast.Node, queries []interface{}) (children []ast.Node, err error) { 14 | if parent == nil || len(queries) == 0 { 15 | return 16 | } 17 | 18 | query := queries[0] 19 | if query == nil { 20 | return 21 | } 22 | 23 | queryT := reflect.TypeOf(query) 24 | if queryT.Kind() == reflect.Ptr && queryT.Elem().Kind() == reflect.Interface { 25 | queryT = queryT.Elem() 26 | } 27 | vlog.Printf("finding <%v> in %v\n", queryT, Name(parent)) 28 | 29 | ast.Inspect(parent, func(node ast.Node) bool { 30 | if node == nil { 31 | return false 32 | } 33 | node.End() 34 | nodeT := reflect.TypeOf(node) 35 | 36 | vlog.Println(` > with`, nodeT, nodeT == queryT, nodeT.AssignableTo(queryT), len(queries)) 37 | if nodeT == queryT || nodeT.AssignableTo(queryT) { 38 | if len(queries) == 1 { 39 | children = append(children, node) 40 | 41 | // if both parent and child is match, only parent-node can return 42 | return false 43 | } 44 | 45 | grandChildren, subErr := Find(node, queries[1:]) 46 | if subErr != nil && OptReturnOnError { 47 | err = subErr 48 | return false 49 | } 50 | 51 | children = append(children, grandChildren...) 52 | } 53 | 54 | return true 55 | }) 56 | 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /astutil/query_test.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "bytes" 5 | "go/ast" 6 | "go/parser" 7 | "go/printer" 8 | "go/token" 9 | "log" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | var code = ` 15 | package main 16 | // this package is ... 17 | 18 | import "fmt" 19 | 20 | var ConstName = "const" 21 | 22 | func main() { 23 | a := 1 24 | b := 2 25 | fmt.Println(a+b) 26 | } 27 | 28 | ` 29 | 30 | var astFile, _ = parser.ParseFile(token.NewFileSet(), "", code, parser.ParseComments) 31 | 32 | func init() { 33 | log.SetFlags(log.LstdFlags | log.Lshortfile) 34 | } 35 | 36 | func TestFind(t *testing.T) { 37 | 38 | t.Run(`print`, func(t *testing.T) { 39 | buf := bytes.NewBufferString(``) 40 | err := printer.Fprint(buf, token.NewFileSet(), astFile) 41 | t.Log(buf.String(), err) 42 | }) 43 | 44 | t.Run(`queryField`, func(t *testing.T) { 45 | EnableLog() 46 | DisableLog() 47 | t.Run(`func`, func(t *testing.T) { 48 | f, err := Find(astFile, []interface{}{new(ast.FuncDecl)}) 49 | t.Log(Name(f[0]), err) // assert len(f)>0 50 | }) 51 | 52 | t.Run(`func.assign`, func(t *testing.T) { 53 | children, _ := Find(astFile, []interface{}{new(ast.FuncDecl), new(ast.AssignStmt)}) 54 | for _, child := range children { 55 | t.Log(Name(child), SrcOf(child)) 56 | } 57 | }) 58 | 59 | t.Run(`expr`, func(t *testing.T) { 60 | exps, _ := Find(astFile, []interface{}{new(ast.Expr)}) 61 | for _, child := range exps { 62 | t.Log(Name(child), SrcOf(child)) 63 | } 64 | }) 65 | }) 66 | 67 | } 68 | 69 | type _a interface { 70 | Do() 71 | } 72 | 73 | type _b struct{} 74 | 75 | func (_ *_b) Do() {} 76 | 77 | func TestExampleChildren(t *testing.T) { 78 | t.Log(reflect.TypeOf(new(_b)).AssignableTo(reflect.TypeOf((*_a)(nil)).Elem())) 79 | } 80 | -------------------------------------------------------------------------------- /char/char.go: -------------------------------------------------------------------------------- 1 | package char 2 | 3 | import "regexp" 4 | 5 | func IsUpper(c byte) bool { 6 | return c >= 'A' && c <= 'Z' 7 | } 8 | 9 | func IsLower(c byte) bool { 10 | return c >= 'a' && c <= 'z' 11 | } 12 | 13 | func IsAlpha(c byte) bool { 14 | return IsLower(c) || IsUpper(c) 15 | } 16 | 17 | func IsNumber(c byte) bool { 18 | return c >= '0' && c <= '9' 19 | } 20 | 21 | func IsHan(b rune) bool { 22 | return IsHanString(string(b)) 23 | } 24 | 25 | func IsHanString(s string) bool { 26 | re := regexp.MustCompile(`[\p{Han}]+`) 27 | return re.MatchString(s) 28 | } 29 | -------------------------------------------------------------------------------- /char/char_test.go: -------------------------------------------------------------------------------- 1 | package char 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkIsAlpha(b *testing.B) { 9 | } 10 | 11 | func TestIsAlpha(t *testing.T) { 12 | fmt.Println( 13 | IsUpper('A'), 14 | IsUpper('a'), 15 | IsUpper('z'), 16 | IsUpper('Z'), 17 | IsNumber('0'), 18 | IsNumber('9'), 19 | IsNumber(9), 20 | ) 21 | } 22 | 23 | func TestIsHan(t *testing.T) { 24 | fmt.Println(IsHan('汉')) 25 | fmt.Println(IsHan('a')) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /collection/skiplist.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import "math/rand" 4 | 5 | const MAX_LEVEL = 32 6 | const P = 4 7 | 8 | type skipList struct { 9 | head *SkipListNode 10 | tail *SkipListNode 11 | size int 12 | level int 13 | } 14 | 15 | type SkipListNode struct { 16 | Score int 17 | Data interface{} 18 | prev *SkipListNode 19 | // 层深,区间为 [0,level) 20 | level []*nodeLevelInfo 21 | } 22 | 23 | type nodeLevelInfo struct { 24 | next *SkipListNode 25 | span int // 暂时没用 26 | } 27 | 28 | func NewSkipList() *skipList { 29 | return &skipList{ 30 | head: newSkipListNode(-1, nil, MAX_LEVEL), 31 | tail: nil, 32 | size: 0, 33 | level: 1, 34 | } 35 | } 36 | 37 | func newSkipListNode(score int, data interface{}, level int) *SkipListNode { 38 | node := &SkipListNode{ 39 | Score: score, 40 | Data: data, 41 | level: make([]*nodeLevelInfo, level), 42 | } 43 | for i := range node.level { 44 | node.level[i] = &nodeLevelInfo{} 45 | } 46 | return node 47 | } 48 | 49 | func (l *skipList) Put(score int, data interface{}) *SkipListNode { 50 | 51 | var preves [MAX_LEVEL]*SkipListNode 52 | 53 | // 查找 score 对应位置的左节点 54 | prev := l.head 55 | for level := l.level - 1; level >= 0; level-- { 56 | for prev != nil && 57 | prev.level[level].next != nil && 58 | score > prev.level[level].next.Score { 59 | prev = prev.level[level].next 60 | } 61 | 62 | preves[level] = prev 63 | } 64 | 65 | // 生成level 66 | level := randomLevel() 67 | if level > l.level { 68 | for i := l.level; i < level; i++ { 69 | preves[i] = l.head 70 | } 71 | l.level = level 72 | } 73 | 74 | // 插入 75 | node := newSkipListNode(score, data, level) 76 | for i := 0; i < level; i++ { 77 | // .next 78 | node.level[i].next = preves[i].level[i].next 79 | preves[i].level[i].next = node 80 | } 81 | 82 | // .prev 83 | node.prev = preves[0] 84 | if next := node.level[0].next; next != nil { 85 | next.prev = node 86 | } 87 | 88 | l.size++ 89 | return node 90 | } 91 | 92 | func (l *skipList) DelByScore(score int) { 93 | preves := make([]*SkipListNode, l.level) 94 | 95 | cur := l.head 96 | for i := l.level - 1; i >= 0; i-- { 97 | for cur != nil && 98 | cur.level[i].next != nil && 99 | cur.level[i].next.Score < score { 100 | cur = cur.level[i].next 101 | } 102 | preves[i] = cur 103 | } 104 | 105 | if next := cur.level[0].next; next != nil { 106 | l.DelNode(next, preves) 107 | } 108 | } 109 | 110 | func (l *skipList) DelNode(node *SkipListNode, preves []*SkipListNode) { 111 | for i := 0; i < len(node.level); i++ { 112 | preves[i].level[i].next = node.level[i].next 113 | } 114 | 115 | if next := node.level[0].next; next != nil { 116 | next.prev = preves[0] 117 | } 118 | 119 | if l.head.level[l.level-1].next == nil { 120 | l.level-- 121 | } 122 | } 123 | 124 | func (l *skipList) Each(do func(score int, data interface{})) { 125 | for cur := l.head.level[0].next; cur != nil; cur = cur.level[0].next { 126 | do(cur.Score, cur.Data) 127 | } 128 | } 129 | 130 | func (l *skipList) EachNode(do func(node *SkipListNode)) { 131 | for cur := l.head.level[0].next; cur != nil; cur = cur.level[0].next { 132 | do(cur) 133 | } 134 | } 135 | 136 | func (l *skipList) RangeByScore(left int, right int, do func(node *SkipListNode)) { 137 | cur := l.head.level[l.level-1].next 138 | for i := l.level - 1; i >= 0; i-- { 139 | if cur != nil && cur.Score < left { 140 | cur = cur.level[i].next 141 | } 142 | } 143 | 144 | for cur != nil && cur.Score >= left && cur.Score <= right { 145 | do(cur) 146 | cur = cur.level[0].next 147 | } 148 | } 149 | 150 | // 1/p 的概率使其加一层 151 | func randomLevel() int { 152 | var level = 1 153 | for rand.Int()&0xFFFF*P < 0xFFFF { 154 | level += 1 155 | } 156 | return level 157 | } 158 | -------------------------------------------------------------------------------- /collection/skiplist_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/Kretech/xgo/test" 10 | ) 11 | 12 | func newSimpleSkipList() *skipList { 13 | l := NewSkipList() 14 | 15 | l.Put(4, 6) 16 | l.Put(10, 1) 17 | l.Put(2, 8) 18 | l.Put(6, 4) 19 | 20 | return l 21 | } 22 | 23 | func TestBasic(t *testing.T) { 24 | 25 | l := newSimpleSkipList() 26 | 27 | l.Each(func(score int, data interface{}) { 28 | fmt.Println(score, data) 29 | }) 30 | 31 | } 32 | 33 | func TestSkipList_RangeByScore(t *testing.T) { 34 | l := newSimpleSkipList() 35 | l.RangeByScore(3, 7, func(node *SkipListNode) { 36 | fmt.Print(node.Score, ",") 37 | }) 38 | } 39 | 40 | func TestSkipList_DelByScore(t *testing.T) { 41 | l := NewSkipList() 42 | 43 | l.DelByScore(4) 44 | 45 | l.EachNode(func(node *SkipListNode) { 46 | fmt.Print(node.Data, " ") 47 | }) 48 | 49 | } 50 | 51 | func TestSkipList_Sort(t *testing.T) { 52 | l := NewSkipList() 53 | 54 | num := 10 55 | a := make([]int, num) 56 | for i := 0; i < num; i++ { 57 | a[i] = rand.Int() % 1000000 58 | l.Put(a[i], a[i]) 59 | } 60 | 61 | i := 0 62 | l.Each(func(score int, data interface{}) { 63 | a[i] = data.(int) 64 | i++ 65 | }) 66 | 67 | test.BeTrue(t, sort.IntsAreSorted(a)) 68 | } 69 | -------------------------------------------------------------------------------- /crypt/crypt.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "fmt" 7 | ) 8 | 9 | func Md5(s string) string { 10 | return fmt.Sprintf(`%x`, md5.Sum([]byte(s))) 11 | } 12 | 13 | func Sha1(s string) string { 14 | return fmt.Sprintf(`%x`, sha1.Sum([]byte(s))) 15 | } 16 | -------------------------------------------------------------------------------- /crypt/crypt_test.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/test" 7 | ) 8 | 9 | func TestMd5(t *testing.T) { 10 | test.AssertEqual(t, Md5(`hello`), `5d41402abc4b2a76b9719d911017c592`) 11 | } 12 | 13 | func TestSha1(t *testing.T) { 14 | test.AssertEqual(t, Sha1(`hello`), `aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d`) 15 | } 16 | -------------------------------------------------------------------------------- /date/carbon/carbon.go: -------------------------------------------------------------------------------- 1 | package carbon 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Kretech/xgo/date" 7 | ) 8 | 9 | // Carbon 10 | type Carbon struct { 11 | t time.Time 12 | } 13 | 14 | // 假定多数情况下,一个进程给一个location就够了 15 | var defaultLoc = time.Local 16 | 17 | // 设置全局时区 18 | func In(loc *time.Location) { 19 | defaultLoc = loc 20 | } 21 | 22 | func Now() *Carbon { 23 | return &Carbon{time.Now()} 24 | } 25 | 26 | func UnixOf(sec int64, nsec int64) *Carbon { 27 | t := time.Unix(sec, nsec).In(defaultLoc) 28 | return &Carbon{t} 29 | } 30 | 31 | func TimeOf(t time.Time) *Carbon { 32 | return &Carbon{t} 33 | } 34 | 35 | // 解析时间文本 36 | func StrOf(str string) *Carbon { 37 | return Parse(str) 38 | } 39 | 40 | // todo 41 | // alias to StrOf 42 | func Parse(str string) *Carbon { 43 | t := time.Now().In(defaultLoc) 44 | return &Carbon{t} 45 | } 46 | 47 | // wrap of time.Parse but with the carbon layout 48 | // 使用 Go time.Time 解析时间文本 49 | func TParse(layout, value string) *Carbon { 50 | t, _ := time.Parse(date.ToGoFormat(layout), value) 51 | return TimeOf(t) 52 | } 53 | 54 | func carbonOf(c *Carbon) *Carbon { 55 | return TimeOf(c.t) 56 | } 57 | 58 | func (c *Carbon) Clone() *Carbon { 59 | return carbonOf(c) 60 | } 61 | 62 | func (c *Carbon) Time() time.Time { 63 | return c.t 64 | } 65 | 66 | // 设置时区 67 | func (c Carbon) In(loc *time.Location) *Carbon { 68 | c.t = c.t.In(loc) 69 | return &c 70 | } 71 | 72 | func (c Carbon) Add(d time.Duration) *Carbon { 73 | c.t = c.t.Add(d) 74 | return &c 75 | } 76 | 77 | // 减去另一个时间:计算两个时间的差 78 | func (c *Carbon) SubTime(t time.Time) time.Duration { 79 | return c.Time().Sub(t) 80 | } 81 | 82 | // 减去一段时间 83 | func (c Carbon) Sub(d time.Duration) *Carbon { 84 | c.t = c.t.Add(-d) 85 | return &c 86 | } 87 | 88 | // Format 格式化代码 89 | // c.Format("Y-m-d H:i:s") 90 | // @see http://php.net/manual/zh/function.date.php#refsect1-function.date-parameters 91 | func (c *Carbon) Format(format string) string { 92 | format = date.ToGoFormat(format) 93 | return c.t.Format(format) 94 | } 95 | -------------------------------------------------------------------------------- /date/carbon/carbon_test.go: -------------------------------------------------------------------------------- 1 | package carbon 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/Kretech/xgo/test" 8 | ) 9 | 10 | func TestUnixOf(t *testing.T) { 11 | In(Shanghai) 12 | test.AssertEqual(t, UnixOf(0, 0).Format("Y-m-d H:i:s"), "1970-01-01 08:00:00") 13 | 14 | In(time.UTC) 15 | test.AssertEqual(t, UnixOf(0, 0).In(Shanghai).Format("Y-m-d H:i:s"), "1970-01-01 08:00:00") 16 | test.AssertEqual(t, UnixOf(0, 0).Format("Y-m-d H:i:s"), "1970-01-01 00:00:00") 17 | 18 | In(time.UTC) 19 | test.AssertEqual(t, UnixOf(0, 0).Time().String(), "1970-01-01 00:00:00 +0000 UTC") 20 | test.AssertEqual(t, UnixOf(0, 0).Time().Unix(), 0) 21 | test.AssertEqual(t, UnixOf(0, 0).Format("Y-m-d H:i:s"), "1970-01-01 00:00:00") 22 | test.AssertEqual(t, UnixOf(0, 0).In(Shanghai).Time(), "1970-01-01 08:00:00 +0800 CST") 23 | test.AssertEqual(t, UnixOf(0, 0).In(Shanghai).Time().Unix(), 0) 24 | test.AssertEqual(t, UnixOf(0, 0).In(Shanghai).Format("Y-m-d H:i:s"), "1970-01-01 08:00:00") 25 | } 26 | 27 | func TestParse(t *testing.T) { 28 | Parse("2012-1-1 01:02:03") 29 | Parse("+1 day") 30 | } 31 | 32 | func TestCarbon_Format(t *testing.T) { 33 | In(Shanghai) 34 | test.AssertEqual(t, UnixOf(1524379525, 0).Format(`Y-m-d`), `2018-04-22`) 35 | test.AssertEqual(t, UnixOf(1524379525, 0).Format(`Y-n-j`), `2018-4-22`) 36 | test.AssertEqual(t, UnixOf(1523031400, 0).Format(`Y-n-j`), `2018-4-7`) 37 | test.AssertEqual(t, UnixOf(1524379525, 0).Format(`Y-m-d H:i:s`), `2018-04-22 14:45:25`) 38 | } 39 | 40 | func TestCarbon_In(t *testing.T) { 41 | t1 := Now() 42 | test.AssertEqual(t, t1.Time().Location(), time.Local) 43 | 44 | t2 := t1.In(Shanghai) 45 | // t1未修改 46 | test.AssertEqual(t, t1.Time().Location(), time.Local) 47 | // t2修改成功 48 | test.AssertEqual(t, t2.Time().Location(), Shanghai) 49 | } 50 | 51 | func TestCarbon_Sub(t *testing.T) { 52 | t1 := TParse("Y-m-d H:i:s", "2018-01-02 09:00:00") 53 | t2 := t1.Sub(time.Hour) 54 | test.AssertEqual(t, t2.Format("Y-m-d H:i:s"), "2018-01-02 08:00:00") 55 | } 56 | -------------------------------------------------------------------------------- /date/carbon/constant.go: -------------------------------------------------------------------------------- 1 | package carbon 2 | 3 | import "time" 4 | 5 | const ( 6 | Nanosecond = time.Nanosecond 7 | Microsecond = 1000 * Nanosecond 8 | Millisecond = 1000 * Microsecond 9 | Second = 1000 * Millisecond 10 | Minute = 60 * Second 11 | Hour = 60 * Minute 12 | Day = 24 * Hour 13 | Week = 7 * Day 14 | ) 15 | -------------------------------------------------------------------------------- /date/carbon/location.go: -------------------------------------------------------------------------------- 1 | package carbon 2 | 3 | import "time" 4 | 5 | var ( 6 | Shanghai, _ = time.LoadLocation("Asia/Shanghai") 7 | ) 8 | -------------------------------------------------------------------------------- /date/date.go: -------------------------------------------------------------------------------- 1 | package date 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | ) 7 | 8 | var timeOffset time.Duration = 0 9 | 10 | func TimeOffset(offset time.Duration) { 11 | timeOffset = offset 12 | } 13 | 14 | // ToGoFormat 把 Y-m-d 转成 Go 格式 15 | func ToGoFormat(format string) string { 16 | rule := []string{ 17 | `Y`, `2006`, // 年 18 | `y`, `06`, 19 | 20 | `m`, `01`, // 月 21 | `n`, `1`, 22 | 23 | `d`, `02`, // 日 24 | `j`, `2`, 25 | 26 | `H`, `15`, // 时 27 | `h`, `03`, 28 | `g`, `3`, 29 | `G`, `15`, // 应该木有前导零 30 | 31 | `i`, `04`, // 分 32 | `s`, `05`, // 秒 33 | 34 | `D`, `Mon`, // 周 35 | `N`, `1`, 36 | } 37 | 38 | specs := func(expr string) string { 39 | switch expr { 40 | case `S`: 41 | return `st/nd/rd/th` 42 | case `z`: 43 | return `The day of the year` 44 | case `t`: 45 | return `Number of days in the given month` 46 | case `L`: 47 | return `Whether it's a leap year` 48 | case `a`: 49 | return `am or pm` 50 | case `A`: 51 | return `AM or PM` 52 | default: 53 | return expr 54 | } 55 | } 56 | 57 | size := len(rule) 58 | for i := 0; i < size; i += 2 { 59 | format = strings.Replace(format, rule[i], rule[i+1], -1) 60 | } 61 | 62 | format = specs(format) 63 | 64 | return format 65 | } 66 | 67 | func LocalFormat(format string, timestamp ...int64) string { 68 | var seconds int64 69 | if len(timestamp) > 0 { 70 | seconds = timestamp[0] 71 | } else { 72 | seconds = time.Now().Local().Add(timeOffset).Unix() 73 | } 74 | 75 | format = ToGoFormat(format) 76 | 77 | return time.Unix(seconds, 0).Local().Format(format) 78 | } 79 | 80 | // @see http://php.net/manual/en/function.strtotime.php#example-2803 81 | func StrToTime(expr string) int64 { 82 | t := time.Now().Local() 83 | //pattern := `\d+ [(year)|(month)|(day)|(hour)|(minute)|(second)]` 84 | 85 | for i := 1; i < 10; i++ { 86 | 87 | } 88 | return t.Unix() 89 | } 90 | -------------------------------------------------------------------------------- /date/date_test.go: -------------------------------------------------------------------------------- 1 | package date 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDate(t *testing.T) { 8 | } 9 | 10 | func TestStrToTime(t *testing.T) { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /dict/map_dict.go: -------------------------------------------------------------------------------- 1 | package dict 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/Kretech/xgo/encoding" 11 | ) 12 | 13 | var ( 14 | ErrNotDict = errors.New(`parsed object is not map[string]interface{}`) 15 | ) 16 | 17 | type MapDict struct { 18 | data map[string]interface{} 19 | } 20 | 21 | func (d *MapDict) String() string { 22 | return "" 23 | } 24 | 25 | func NewMapDict() *MapDict { 26 | return &MapDict{ 27 | data: newMap(), 28 | } 29 | } 30 | 31 | func (d *MapDict) IsEmpty() bool { 32 | return len(d.data) == 0 33 | } 34 | 35 | func (d *MapDict) Len() int { 36 | return len(d.data) 37 | } 38 | 39 | func (d *MapDict) Get(k interface{}) interface{} { 40 | paths := strings.Split(toString(k), `.`) 41 | 42 | var current interface{} 43 | current = toMap(d.data) 44 | 45 | size := len(paths) 46 | for i := 0; i < size-1; i++ { 47 | m := toMap(current) 48 | current = m[paths[i]] 49 | } 50 | 51 | return toMap(current)[paths[size-1]] 52 | } 53 | 54 | func (d *MapDict) Set(k interface{}, v interface{}) { 55 | paths := strings.Split(toString(k), `.`) 56 | 57 | parent := d.data 58 | 59 | size := len(paths) 60 | for idx := 0; idx < size-1; idx++ { 61 | //fmt.Println(idx, d.provider, parent, &d.provider, &parent) 62 | seq := paths[idx] 63 | 64 | i := parent[seq] 65 | if _, ok := i.(map[string]interface{}); !ok { 66 | parent[seq] = newMap() 67 | parent = parent[seq].(map[string]interface{}) 68 | } else { 69 | parent = i.(map[string]interface{}) 70 | } 71 | } 72 | 73 | parent[paths[size-1]] = v 74 | } 75 | 76 | func (d *MapDict) Forget(k interface{}) { 77 | d.Set(k, nil) 78 | } 79 | 80 | func (d *MapDict) ParseJsonString(data []byte) (err error) { 81 | d.data, err = JsonToMap(data) 82 | return 83 | } 84 | 85 | func JsonToMap(data []byte) (m map[string]interface{}, err error) { 86 | m = make(map[string]interface{}) 87 | 88 | var i interface{} 89 | err = json.Unmarshal(data, &i) 90 | if err != nil { 91 | return 92 | } 93 | 94 | m, ok := i.(map[string]interface{}) 95 | if !ok { 96 | return m, ErrNotDict 97 | } 98 | 99 | return 100 | } 101 | 102 | func (d *MapDict) Keys() (keys []string) { 103 | for k := range d.data { 104 | keys = append(keys, k) 105 | } 106 | return 107 | } 108 | 109 | func (d *MapDict) Values() (values []interface{}) { 110 | for _, v := range d.data { 111 | values = append(values, v) 112 | } 113 | return 114 | } 115 | 116 | func (d *MapDict) Filter(fn func(interface{}, string) bool) *MapDict { 117 | instance := NewMapDict() 118 | d.Each(func(v interface{}, k string) { 119 | if fn(v, k) { 120 | instance.Set(k, v) 121 | } 122 | }) 123 | return instance 124 | } 125 | 126 | func (d *MapDict) Each(fn func(interface{}, string)) { 127 | for k, v := range d.data { 128 | fn(v, k) 129 | } 130 | } 131 | 132 | func (d *MapDict) Data() map[string]interface{} { 133 | return d.data 134 | } 135 | 136 | func (d *MapDict) SetData(data map[string]interface{}) { 137 | d.data = data 138 | } 139 | 140 | func (d *MapDict) Json() string { 141 | return encoding.JsonEncode(d.data) 142 | } 143 | 144 | func toString(k interface{}) string { 145 | switch k.(type) { 146 | case fmt.Stringer: 147 | return k.(fmt.Stringer).String() 148 | case string: 149 | return k.(string) 150 | case int: 151 | return strconv.FormatInt(int64(k.(int)), 10) 152 | default: 153 | return fmt.Sprintf("%v", k) 154 | } 155 | } 156 | 157 | func newMap() map[string]interface{} { 158 | return make(map[string]interface{}) 159 | } 160 | 161 | func toMap(i interface{}) map[string]interface{} { 162 | m, ok := i.(map[string]interface{}) 163 | if !ok { 164 | //fmt.Printf("%v,%v\n", i, m) 165 | m = make(map[string]interface{}) 166 | //fmt.Printf("%v,%v\n", i, m) 167 | } 168 | 169 | return m 170 | } 171 | -------------------------------------------------------------------------------- /dict/map_dict_test.go: -------------------------------------------------------------------------------- 1 | package dict 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | . "github.com/Kretech/xgo/test" 8 | ) 9 | 10 | // test Getter/Setter 11 | func TestNewDict(t *testing.T) { 12 | d := NewMapDict() 13 | AssertEqual(t, d.Get(`a.b.c.d.e`), nil) 14 | 15 | d.Set(`a.b.c.d.e`, `ooo`) 16 | AssertEqual(t, d.Get(`a.b.c.d.e`), `ooo`) 17 | 18 | d.Set(`a.b.c.d.e`, 4324) 19 | AssertEqual(t, d.Get(`a.b.c.d.e`), 4324) 20 | 21 | d.Set(78, 88) 22 | AssertEqual(t, d.Get(78), 88) 23 | 24 | d.Forget(78) 25 | AssertEqual(t, d.Get(78), nil) 26 | } 27 | 28 | func TestDict_Filter(t *testing.T) { 29 | d1 := NewMapDict() 30 | for i := 1; i < 10; i++ { 31 | d1.Set(i, i*i) 32 | } 33 | 34 | AssertEqual(t, d1.Len(), 9) 35 | 36 | d2 := d1.Filter(func(v interface{}, k string) bool { 37 | i, _ := strconv.Atoi(k) 38 | return i > 5 39 | }) 40 | 41 | AssertEqual(t, d2.Len(), 4) 42 | 43 | d3 := d1.Filter(func(v interface{}, k string) bool { 44 | return v.(int) < 10 45 | }) 46 | 47 | AssertEqual(t, d3.Len(), 3) 48 | } 49 | 50 | // test parse from json 51 | func TestDict_ParseJson(t *testing.T) { 52 | d := NewMapDict() 53 | AssertEqual(t, len(d.data), 0) 54 | d.ParseJsonString([]byte(`{"name":"zhr","age":18,"address":["yuncheng","beijing"]}`)) 55 | AssertEqual(t, len(d.data), 3) 56 | 57 | err := d.ParseJsonString([]byte(`["a","b"]`)) 58 | AssertEqual(t, len(d.data), 0) 59 | AssertEqual(t, err, ErrNotDict) 60 | 61 | } 62 | 63 | // test .Json() .Keys() .Values() 64 | func TestDict_Json(t *testing.T) { 65 | d := NewMapDict() 66 | 67 | d.Set(1, 3) 68 | d.Set(`a`, 'b') 69 | d.Set(`a.b.c.d`, 'e') 70 | 71 | AssertEqual(t, d.Json(), `{"1":3,"a":{"b":{"c":{"d":101}}}}`) 72 | // AssertEqual(t, d.Keys(), []string{`1`, `a`}) 73 | // AssertEqual(t, d.Values(), []interface{}{ 74 | // 3, 75 | // map[string]interface{}{ 76 | // `b`: map[string]interface{}{ 77 | // `c`: map[string]interface{}{ 78 | // `d`: 'e', 79 | // }, 80 | // }, 81 | // }, 82 | // }) 83 | } 84 | 85 | func TestDict_Keys(t *testing.T) { 86 | dict := NewMapDict() 87 | dict.Set(`a`, 1) 88 | dict.Set(2, 1) 89 | dict.Set(`c`, 1) 90 | 91 | // AssertEqual(t, dict.Keys(), []interface{}{`a`, 2, `c`}) 92 | } 93 | 94 | func TestDict_Values(t *testing.T) { 95 | dict := NewMapDict() 96 | dict.Set(`a`, `t`) 97 | dict.Set(2, 3) 98 | dict.Set(`c`, `y`) 99 | 100 | // AssertEqual(t, dict.Values(), []interface{}{`t`, 3, `y`}) 101 | } 102 | -------------------------------------------------------------------------------- /digo/digo.go: -------------------------------------------------------------------------------- 1 | package digo 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrNotBound = errors.New(`build not-bound types`) 9 | ) 10 | 11 | type DIGo interface { 12 | // 注册单例 13 | // 14 | // s := new(T) 15 | // di.Singleton(s) 16 | Singleton(instancePtr interface{}) (err error) 17 | 18 | // 通过闭包方式注册 19 | // 闭包的第一个返回值的类型作为注册的 T 20 | // 闭包的参数,会根据类型从容器中取数据 21 | // 22 | // di.SingletonFunc(func(a *A) (T) { 23 | // // a will build first 24 | // return T{} 25 | // }) 26 | SingletonFunc(fn interface{}) (err error) 27 | 28 | // MapSingleton 表示从接口到实体的映射 29 | // 30 | // di.MapSingleton(op.Writer, os.Stdout) 31 | MapSingleton(T Type, V Value) (err error) 32 | 33 | // BindFunc register beans which need build repeat 34 | // 35 | // di.BindFunc(func(a *A) (*) { 36 | // return T{} 37 | // } 38 | //BindFunc(fn interface{}) 39 | 40 | // 利用容器提供的默认数据赋值 41 | // ptr := new(T) 42 | // di.Build(ptr) 43 | InvokePtr(ptr interface{}) (err error) 44 | 45 | // 利用容器提供的默认数据调用函数 46 | // di.Invoke(func(a *A, b *B) { 47 | // // a, b built 48 | // } 49 | InvokeFunc(fn interface{}) (fnOut []interface{}, err error) 50 | 51 | // 利用容器提供的默认数据填充对象的成员 52 | // 53 | // 54 | FillClass(classPtr interface{}) (err error) 55 | 56 | // 57 | // di.Listen(di.BeforeBuilding, func(a *A) { 58 | // }) 59 | //Listen(hook Hook, fn interface{}) 60 | } 61 | 62 | type Hook uint16 63 | 64 | // NewDIGo 65 | func NewDIGo() DIGo { 66 | return NewDiGoImpl() 67 | } 68 | -------------------------------------------------------------------------------- /digo/digo_impl.go: -------------------------------------------------------------------------------- 1 | package digo 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | type ( 10 | Type = reflect.Type 11 | Value = reflect.Value 12 | Kind = reflect.Kind 13 | ) 14 | 15 | var ( 16 | TypeOf = reflect.TypeOf 17 | ValueOf = reflect.ValueOf 18 | ) 19 | 20 | type DImpl struct { 21 | //stub 22 | 23 | // 被注入的原始值 24 | binding map[Type]Value 25 | 26 | // 解析后的值 27 | store map[Type]Value 28 | 29 | // 标记该类型是否是 singleton 30 | share map[Type]bool 31 | 32 | // resolved 表示该类型是否已被解析 33 | // 如果该类型被注入的是一个复杂类型,比如 func,resolved 表示该对象已被 invoked 34 | resolved map[Type]bool 35 | 36 | startBuilding map[Type]struct{} 37 | finishBuilt map[Type]struct{} 38 | } 39 | 40 | var _ DIGo = &DImpl{} 41 | 42 | func NewDiGoImpl() *DImpl { 43 | di := &DImpl{ 44 | binding: make(map[Type]Value), 45 | store: make(map[Type]reflect.Value), 46 | share: make(map[Type]bool), 47 | resolved: make(map[Type]bool), 48 | 49 | startBuilding: make(map[Type]struct{}), 50 | finishBuilt: make(map[Type]struct{}), 51 | } 52 | return di 53 | } 54 | 55 | func (di *DImpl) InvokePtr(ptr interface{}) (err error) { 56 | return di.build(reflect.ValueOf(ptr)) 57 | } 58 | 59 | func (di *DImpl) build(valuePtr Value) (err error) { 60 | v := valuePtr.Elem() 61 | T := v.Type() 62 | 63 | if di.share[T] { 64 | err = di.resolve(T) 65 | if err != nil { 66 | return err 67 | } 68 | v.Set(di.store[T]) 69 | } 70 | 71 | return 72 | } 73 | 74 | func (di *DImpl) SingletonFunc(fn interface{}) (err error) { 75 | fnT := TypeOf(fn) 76 | entryT := fnT.Out(0) 77 | 78 | fnV := ValueOf(fn) 79 | 80 | di.share[entryT] = true 81 | 82 | return di.mapTo(entryT, fnV) 83 | } 84 | 85 | func (di *DImpl) Singleton(instancePtr interface{}) (err error) { 86 | V := ValueOf(instancePtr).Elem() 87 | T := V.Type() 88 | 89 | di.share[T] = true 90 | 91 | return di.mapTo(T, V) 92 | } 93 | 94 | func (di *DImpl) FillClass(classPtr interface{}) (err error) { 95 | V := ValueOf(classPtr).Elem() 96 | for i := 0; i < V.NumField(); i++ { 97 | field := V.Field(i) 98 | if !field.CanSet() { 99 | continue 100 | } 101 | 102 | newValue := reflect.New(field.Type()) 103 | _ = di.build(newValue) 104 | field.Set(newValue.Elem()) 105 | } 106 | return 107 | } 108 | 109 | func (di *DImpl) MapSingleton(T Type, V Value) (err error) { 110 | T = T.Elem() 111 | 112 | di.share[T] = true 113 | 114 | return di.mapTo(T, V) 115 | } 116 | 117 | func (di *DImpl) mapTo(T Type, V Value) (err error) { 118 | if _, exist := di.binding[T]; exist { 119 | // 如果已存在,重置 resolved 120 | // 下次 build 时就重新 resolve 了 121 | di.resolved[T] = false 122 | } 123 | 124 | di.binding[T] = V 125 | 126 | return err 127 | } 128 | 129 | func (this *DImpl) BindFunc(fn interface{}) { 130 | 131 | } 132 | 133 | func (di *DImpl) InvokeFunc(fn interface{}) (fnOut []interface{}, err error) { 134 | fnV := ValueOf(fn) 135 | out, err := di.invokeFunc(fnV) 136 | for _, value := range out { 137 | fnOut = append(fnOut, value.Interface()) 138 | } 139 | return fnOut, err 140 | } 141 | 142 | func (di *DImpl) invokeFunc(fnV Value) (out []Value, err error) { 143 | fnT := fnV.Type() 144 | in := make([]Value, fnT.NumIn()) 145 | for i := 0; i < len(in); i++ { 146 | argT := fnT.In(i) 147 | argV := reflect.New(argT) 148 | _ = di.build(argV) 149 | in[i] = argV.Elem() 150 | } 151 | 152 | out = fnV.Call(in) 153 | return 154 | } 155 | 156 | func (di *DImpl) resolve(t Type) (err error) { 157 | if di.resolved[t] { 158 | return err 159 | } 160 | 161 | bound, ok := di.binding[t] 162 | if !ok { 163 | return errors.Wrap(ErrNotBound, t.Name()) 164 | } 165 | 166 | switch bound.Kind() { 167 | case reflect.Func: 168 | out, _ := di.invokeFunc(bound) 169 | di.store[t] = out[0] 170 | 171 | default: 172 | // 普通类型 173 | di.store[t] = bound 174 | } 175 | 176 | di.resolved[t] = true 177 | return err 178 | } 179 | -------------------------------------------------------------------------------- /digo/digo_impl_test.go: -------------------------------------------------------------------------------- 1 | package digo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestDiGoImpl_SingletonFunc(t *testing.T) { 10 | type fields struct { 11 | DiGoStub stub 12 | binding map[Type]interface{} 13 | store map[Type]reflect.Value 14 | share map[Type]bool 15 | startBuilding map[Type]struct{} 16 | finishBuilt map[Type]struct{} 17 | } 18 | type args struct { 19 | fn interface{} 20 | } 21 | tests := []struct { 22 | name string 23 | fields fields 24 | args args 25 | wantErr bool 26 | }{ 27 | // TODO: Add test cases. 28 | { 29 | args: args{func() int { return 316 }}, 30 | }, 31 | { 32 | args: args{func(i int) string { return fmt.Sprint(i) }}, 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | di := NewDiGoImpl() 38 | if err := di.SingletonFunc(tt.args.fn); (err != nil) != tt.wantErr { 39 | t.Errorf("DiGoImpl.SingletonFunc() error = %v, wantErr %v", err, tt.wantErr) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /digo/digo_stub.go: -------------------------------------------------------------------------------- 1 | package digo 2 | 3 | var _ DIGo = &stub{} 4 | 5 | type stub struct { 6 | } 7 | 8 | func (di *stub) FillClass(classPtr interface{}) (err error) { return } 9 | 10 | func (di *stub) MapSingleton(T Type, V Value) (err error) { return err } 11 | 12 | func (di *stub) Listen(hook Hook, fn interface{}) {} 13 | 14 | func (di *stub) Singleton(instancePtr interface{}) (err error) { return err } 15 | 16 | func (di *stub) SingletonFunc(fn interface{}) (err error) { return err } 17 | 18 | func (di *stub) BindFunc(fn interface{}) {} 19 | 20 | func (di *stub) InvokePtr(ptr interface{}) error { return nil } 21 | 22 | func (di *stub) InvokeFunc(fn interface{}) (fnOut []interface{}, err error) { return fnOut, err } 23 | -------------------------------------------------------------------------------- /digo/digo_test.go: -------------------------------------------------------------------------------- 1 | package digo_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/Kretech/xgo/digo" 11 | "github.com/Kretech/xgo/dump" 12 | ) 13 | 14 | func TestDemo_Singleton(t *testing.T) { 15 | di := digo.NewDIGo() 16 | 17 | // 注册两个基本类型 18 | _ = di.SingletonFunc(func() int { return 316 }) 19 | _ = di.SingletonFunc(func(i int) string { return fmt.Sprint(i) }) 20 | 21 | var age int 22 | var name string 23 | 24 | _ = di.InvokePtr(&age) 25 | _ = di.InvokePtr(&name) 26 | 27 | if age != 316 { 28 | t.Error(dump.Serialize(age)) 29 | } 30 | 31 | if name != `316` { 32 | t.Error(dump.Serialize(name)) 33 | } 34 | println(age, name) 35 | // 利用容器提供的默认数据调用函数 36 | t.Run(`InvokeFunc()`, func(t *testing.T) { 37 | invoked := `` 38 | _, _ = di.InvokeFunc(func(a int, s string) { 39 | invoked = fmt.Sprintf("a=%v s=%v", a, s) 40 | }) 41 | if invoked != `a=316 s=316` { 42 | t.Error(dump.Serialize(invoked)) 43 | } 44 | }) 45 | 46 | // 利用容器提供的默认数据构造对象 47 | type P struct { 48 | Name string 49 | Age int 50 | } 51 | _ = di.SingletonFunc(func(name string, age int) P { 52 | return P{Name: name, Age: age} 53 | }) 54 | 55 | t.Run(`InvokePtr()`, func(t *testing.T) { 56 | p1 := new(P) 57 | _ = di.InvokePtr(p1) 58 | if !(p1.Name == name && p1.Age == age) { 59 | t.Error(dump.Serialize(p1)) 60 | } 61 | 62 | // 测试单例生成的 struct 是同一个 63 | // 注意,p2 的类型是 *P,如果 p2 定义为 P,Build 时传 &p,虽然不会影响测试结果,但你自己已经维护了两个 struct 了 64 | p2 := new(P) 65 | _ = di.InvokePtr(p2) 66 | if *p2 != *p1 { 67 | t.Error(dump.Serialize(p2)) 68 | } 69 | 70 | // 用 Singleton() 注册对象 71 | _ = di.Singleton(p2) 72 | p3 := new(P) 73 | _ = di.InvokePtr(p3) 74 | if *p2 != *p3 { 75 | t.Error(dump.Serialize(p3)) 76 | } 77 | 78 | // FillClass 79 | t.Run(`FillClass()`, func(t *testing.T) { 80 | p4 := new(P) 81 | _ = di.FillClass(p4) 82 | if *p2 != *p4 { 83 | t.Error(dump.Serialize(p4)) 84 | } 85 | }) 86 | }) 87 | 88 | // 测试 MapSingleton 89 | t.Run(`MapSingleton()`, func(t *testing.T) { 90 | _ = di.MapSingleton(reflect.TypeOf(new(int)), reflect.ValueOf(7)) 91 | var num7 int 92 | _ = di.InvokePtr(&num7) 93 | if num7 != 7 { 94 | t.Error(dump.Serialize(num7)) 95 | } 96 | }) 97 | 98 | // 测试 MapSingleton 绑定 interface 和 implement 99 | t.Run(`MapSingleton()`, func(t *testing.T) { 100 | buf := bytes.Buffer{} 101 | _ = di.MapSingleton(reflect.TypeOf(new(io.Writer)), reflect.ValueOf(&buf)) 102 | _, _ = di.InvokeFunc(func(w io.Writer) { 103 | _, _ = w.Write([]byte(`hi`)) 104 | }) 105 | if buf.Len() != 2 { 106 | t.Error(dump.Serialize(buf.String())) 107 | } 108 | }) 109 | 110 | p := new(struct { 111 | Name string 112 | Age int 113 | }) 114 | _ = di.FillClass(p) 115 | dump.Dump(p) 116 | 117 | } 118 | -------------------------------------------------------------------------------- /digo/readme.md: -------------------------------------------------------------------------------- 1 | # digo 2 | 3 | A Denpdency-Injection Container for Golang 4 | 5 | ## Quick Start 6 | 7 | ### Singleton 8 | 9 | ```go 10 | di := digo.NewDIGo() 11 | 12 | _ = di.SingletonFunc(func() int { return 316 }) 13 | _ = di.SingletonFunc(func(i int) string { return fmt.Sprint(i) }) 14 | 15 | p := new(struct {Name string;Age int}) 16 | _ = di.FillClass(p) 17 | dump.Dump(p) 18 | // p => *struct{ Name string; Age int } ({ 19 | // "Name": "316", 20 | // "Age": 7 21 | // } 22 | 23 | _, _ = di.InvokeFunc(func(age int, name string) { 24 | println(age, name) 25 | // 316 316 26 | }) 27 | ``` 28 | 29 | ## Demo 30 | 31 | https://github.com/Kretech/xgo/blob/master/digo/digo_test.go 32 | 33 | -------------------------------------------------------------------------------- /dump/cli_dumper.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "runtime" 8 | "strings" 9 | 10 | "github.com/Kretech/xgo/dynamic" 11 | "github.com/fatih/color" 12 | ) 13 | 14 | type CliDumper struct { 15 | name dynamic.Name 16 | 17 | out io.Writer 18 | } 19 | 20 | var ( 21 | DefaultWriter io.Writer = os.Stderr 22 | 23 | // 显示对应代码位置 24 | ShowFileLine1 = true 25 | ) 26 | 27 | func NewCliDumper(X string) CliDumper { 28 | c := CliDumper{} 29 | c.name.X = X 30 | return c 31 | } 32 | 33 | func (c CliDumper) Dump(args ...interface{}) { 34 | c.setY(`Dump`) 35 | 36 | c.DepthDump(1, args...) 37 | } 38 | 39 | func (c CliDumper) DepthDump(depth int, args ...interface{}) { 40 | if Disable { 41 | return 42 | } 43 | 44 | if c.out == nil { 45 | c.out = DefaultWriter 46 | } 47 | 48 | c.setYFunc(func() string { 49 | return dynamic.CallerNameSkip(2+depth, true) 50 | }) 51 | 52 | names, compacted := c.name.DepthCompact(depth+1, args...) 53 | 54 | if ShowFileLine1 { 55 | _, _ = fmt.Fprintln(c.out, c.headerLine(depth+1, ``)) 56 | } 57 | 58 | for _, name := range names { 59 | txt := "" 60 | 61 | noUnaryName := name 62 | if strings.HasPrefix(name, "&") { 63 | txt += color.New(color.Italic, color.FgMagenta).Sprint("&") 64 | noUnaryName = name[1:] 65 | } 66 | 67 | txt += color.New(color.Italic, color.FgCyan).Sprint(noUnaryName) + SepKv 68 | 69 | txt += Serialize(compacted[name]) 70 | 71 | _, _ = fmt.Fprintln(c.out, txt) 72 | } 73 | } 74 | 75 | func (c *CliDumper) headerLine(depth int, t string) string { 76 | _, file, line, _ := runtime.Caller(depth + 1) 77 | return color.New().Sprintf("%s:%d", file, line) 78 | } 79 | 80 | func (c *CliDumper) setY(y string) { 81 | if c.name.Y == `` { 82 | c.name.Y = y 83 | } 84 | } 85 | 86 | func (c *CliDumper) setYFunc(y func() string) { 87 | if c.name.Y == `` { 88 | c.name.Y = y() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /dump/cli_dumper_test.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func ExampleNewCliDumper() { 8 | a := 1 9 | b := `2` 10 | 11 | ShowFileLine1 = false 12 | DefaultWriter = os.Stdout 13 | 14 | g1 := NewCliDumper(`g1`) 15 | g1.Dump(a, b) 16 | 17 | g2 := MyPackage{MyDump: MyDump} 18 | g2.MyDump(a, b) 19 | // Output: 20 | // a => 1 21 | // b => "2" 22 | // a => 1 23 | // b => "2" 24 | } 25 | 26 | // MyPackage mock the package name. so we can called it by `x.y()` 27 | type MyPackage struct { 28 | MyDump func(...interface{}) 29 | } 30 | 31 | func MyDump(args ...interface{}) { 32 | _g := NewCliDumper(`g2`) // means package name 33 | _g.DepthDump(1, args...) 34 | } 35 | -------------------------------------------------------------------------------- /dump/dump.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "github.com/Kretech/xgo/dynamic" 5 | ) 6 | 7 | // disable dump in global scope 8 | // use it in production 9 | var Disable = false 10 | 11 | func Dump(args ...interface{}) { 12 | d := &CliDumper{ 13 | name: dynamic.Name{X: `dump`, Y: dynamic.CallerName(true)}, 14 | } 15 | d.DepthDump(1, args...) 16 | } 17 | -------------------------------------------------------------------------------- /dump/dump_test.go: -------------------------------------------------------------------------------- 1 | package dump_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/Kretech/xgo/dump" 8 | "github.com/Kretech/xgo/encoding" 9 | ) 10 | 11 | func ExampleDump() { 12 | // just for testing 13 | dump.ShowFileLine1 = false 14 | dump.DefaultWriter = os.Stdout 15 | 16 | a := 1 17 | b := `2` 18 | c := map[string]interface{}{b: a} 19 | 20 | dump.Dump(a, b, c) 21 | // Output: 22 | // a => 1 23 | // b => "2" 24 | // c => map[string]interface{} (len=1) { 25 | // "2" => 1 26 | // } 27 | } 28 | 29 | func TestDump_Example(t *testing.T) { 30 | t.Run(`base`, func(t *testing.T) { 31 | dump.OptShowUint8sAsString = true 32 | dump.OptShowUint8AsChar = true 33 | aInt := 1 34 | bStr := `sf` 35 | cMap := map[string]interface{}{"name": "z", "age": 14, "bytes": []byte(string(`abc`)), `aByte`: byte('c')} 36 | dArray := []interface{}{&cMap, aInt, bStr} 37 | dump.Dump(aInt, &aInt, &bStr, bStr, cMap, dArray, cMap["name"], dArray[2], dArray[aInt]) 38 | }) 39 | 40 | t.Run(`operator`, func(t *testing.T) { 41 | a := 0.1 42 | b := 0.2 43 | cc := 0.3 44 | dump.Dump(a + b) 45 | dump.Dump(a+b == cc) 46 | dump.Dump(a+b > cc) 47 | dump.Dump(a+b < cc) 48 | }) 49 | 50 | t.Run(`interface`, func(t *testing.T) { 51 | var err error 52 | var emptyInterface interface{} 53 | var emptyMap map[string]interface{} 54 | var emptySlice []interface{} 55 | dump.Dump(err, emptyInterface, emptyMap, emptySlice, nil) 56 | }) 57 | 58 | t.Run(`struct`, func(t *testing.T) { 59 | dump.ShowFileLine1 = true 60 | defer func() { 61 | dump.ShowFileLine1 = false 62 | }() 63 | 64 | type String string 65 | 66 | type car struct { 67 | Speed int 68 | Owner interface{} 69 | } 70 | 71 | type Person struct { 72 | Name String 73 | age int 74 | Interests []string 75 | 76 | friends [4]*Person 77 | 78 | Cars []*car 79 | 80 | action []func() string 81 | } 82 | 83 | p1 := Person{ 84 | Name: "lisan", age: 19, Interests: []string{"a", "b"}, Cars: []*car{{Speed: 120}}, 85 | action: []func() string{ 86 | func() string { return "sayHi" }, 87 | func() string { return "sayBay" }, 88 | }, 89 | } 90 | p2 := &p1 91 | 92 | dump.Dump(p2) 93 | 94 | type Person2 Person 95 | p3 := Person2(p1) 96 | dump.Dump(p3) 97 | 98 | type Person3 = Person2 99 | p4 := Person3(p1) 100 | dump.Dump(p4) 101 | 102 | type Person4 struct { 103 | Person Person 104 | Person2 105 | } 106 | p5 := Person4{p1, p4} 107 | dump.Dump(p5) 108 | }) 109 | 110 | userId := func() int { return 4 } 111 | dump.Dump(userId()) 112 | 113 | dump.Dump(userId2()) 114 | 115 | _s := _S{} 116 | dump.Dump(_s.a()) 117 | dump.Dump(_s.b(`t`)) 118 | 119 | num3 := 3 120 | dump.Dump(`abc`) 121 | dump.Dump(encoding.JsonEncode(`abc`)) 122 | dump.Dump(encoding.JsonEncode(map[string]interface{}{"a": num3})) 123 | 124 | ch := make(chan bool) 125 | dump.Dump(ch) 126 | } 127 | 128 | type _S struct{} 129 | 130 | func (this *_S) a() string { 131 | return `_s.a` 132 | } 133 | 134 | func (this *_S) b(t string) string { 135 | return `_s.b(` + t + `)` 136 | } 137 | 138 | func userId2() int { 139 | return 8 140 | } 141 | -------------------------------------------------------------------------------- /dump/helper.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | // IsScalar 简单类型 9 | func IsScalar(v interface{}) bool { 10 | if v == nil { 11 | return true 12 | } 13 | 14 | t := reflect.TypeOf(v) 15 | if t.Kind() == reflect.Ptr { 16 | t = t.Elem() 17 | } 18 | 19 | switch t.Kind() { 20 | 21 | case reflect.Map, 22 | reflect.Struct, 23 | reflect.Slice, reflect.Array: 24 | return false 25 | 26 | case reflect.Chan: 27 | return false 28 | 29 | case reflect.Func: 30 | return false 31 | 32 | default: 33 | return true 34 | } 35 | } 36 | 37 | func hasLen(k reflect.Kind) bool { 38 | switch k { 39 | case reflect.Array, reflect.Slice, reflect.Map, reflect.Chan: 40 | return true 41 | default: 42 | return false 43 | } 44 | } 45 | 46 | func withTab(str string) string { 47 | return strings.Replace(str, "\n", "\n\t", -1) 48 | } 49 | -------------------------------------------------------------------------------- /dump/helper_test.go: -------------------------------------------------------------------------------- 1 | package dump_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/dump" 7 | "github.com/Kretech/xgo/encoding" 8 | ) 9 | 10 | func TestIsScalar(t *testing.T) { 11 | 12 | mustbe := map[interface{}]bool{ 13 | 3: true, 14 | true: true, 15 | 0.43: true, 16 | complex(2, 3): true, 17 | "hi": true, 18 | struct{}{}: false, 19 | new(map[int]string): false, 20 | new(chan int): false, 21 | new([]int): false, 22 | } 23 | 24 | for v, b := range mustbe { 25 | if dump.IsScalar(v) != b { 26 | t.Error("isScalar failed ", encoding.JsonEncode(v), b) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dump/readme.md: -------------------------------------------------------------------------------- 1 | # *XGo*/dump 2 | 3 | 4 | 5 | ## Quick Look 6 | 7 | ```go 8 | aInt := 1 9 | bStr := `sf` 10 | cMap := map[string]interface{}{"name": "z", "age": 14} 11 | dArray := []interface{}{&cMap, aInt, bStr} 12 | 13 | dump.Dump(aInt, &aInt, &bStr, bStr, cMap, dArray, cMap["name"], dArray[2], dArray[aInt]) 14 | ``` 15 | 16 | ![](https://i.loli.net/2020/06/03/ojTUsJAui5WKFMf.png) 17 | 18 | ## Option 19 | 20 | custom options 21 | 22 | ```go 23 | // disable dump in global, default false means enable 24 | // disable it in production to ignore the unnecessary output and risks 25 | dump.Disable = false 26 | 27 | // show variant line in head 28 | dump.ShowFileLine1 = true 29 | 30 | // change writer in global 31 | dump.DefaultWriter = os.Stdout 32 | 33 | // only show the first-n elements 34 | // others will show as '...' 35 | // * these options is design for debug clearly 36 | dump.MaxSliceLen = 32 37 | dump.MaxMapLen = 32 38 | 39 | // serialize options 40 | 41 | // render map with sorted keys 42 | dump.OptSortMapKeys = true 43 | 44 | // render []uint8(`ab`) as string("ab") 45 | OptShowUint8sAsString = true 46 | 47 | // render uint8(97) as char('a') 48 | OptShowUint8AsChar 49 | ``` 50 | 51 | ## More TestCases 52 | 53 | https://github.com/Kretech/xgo/blob/master/dump/dumper_test.go 54 | 55 | https://github.com/Kretech/xgo/blob/master/dump/serialize_test.go 56 | -------------------------------------------------------------------------------- /dump/serialize.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "strings" 9 | "time" 10 | "unsafe" 11 | 12 | "github.com/fatih/color" 13 | 14 | "github.com/Kretech/xgo/encoding" 15 | ) 16 | 17 | var ( 18 | // uint8(97) => 'a' 19 | OptShowUint8AsChar = true 20 | 21 | // []uint8{'a','b'} => "ab" 22 | OptShowUint8sAsString = true 23 | 24 | // 按字典序显示map.keys 25 | OptSortMapKeys = true 26 | ) 27 | 28 | var ( 29 | MaxSliceLen = 32 30 | MaxMapLen = 32 31 | 32 | SepKv = " => " 33 | 34 | StringQuota = `"` 35 | ) 36 | 37 | const ( 38 | Zero = `` 39 | Nil = "" 40 | ) 41 | 42 | func serializeScalar(V reflect.Value) (result string) { 43 | 44 | switch V.Type().Kind() { 45 | case reflect.String: 46 | quota := StringQuota 47 | s := fmt.Sprint(V.Interface()) 48 | 49 | if strings.Contains(s, StringQuota) { 50 | quota = "`" 51 | } 52 | result = fmt.Sprintf(`%s%v%s`, quota, s, quota) 53 | 54 | case reflect.Uint8: 55 | if OptShowUint8AsChar { 56 | result = fmt.Sprintf("%c", V.Uint()) 57 | } else { 58 | result = fmt.Sprint(V.Interface()) 59 | } 60 | 61 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 62 | reflect.Uint /*reflect.Uint8,*/, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 63 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 64 | 65 | result = fmt.Sprint(V.Interface()) 66 | 67 | default: 68 | result = fmt.Sprint(V.Interface()) 69 | } 70 | 71 | return 72 | } 73 | 74 | func Serialize(originValue interface{}) (serialized string) { 75 | if originValue == nil { 76 | return Nil 77 | } 78 | 79 | result := originValue 80 | 81 | var V reflect.Value 82 | 83 | switch v := originValue.(type) { 84 | case reflect.Value: 85 | V = v 86 | default: 87 | V = reflect.ValueOf(originValue) 88 | } 89 | 90 | T := V.Type() 91 | isPtr := false 92 | 93 | if T.Kind() == reflect.Ptr { 94 | isPtr = true 95 | T = T.Elem() 96 | V = V.Elem() 97 | } 98 | 99 | if !V.IsValid() { 100 | return Zero 101 | } 102 | 103 | if isPtr { 104 | serialized += color.New(color.FgMagenta).Sprint("*") 105 | } 106 | 107 | // 基础类型 108 | if IsScalar(originValue) { 109 | serialized = serializeScalar(V) 110 | return 111 | } 112 | 113 | //复合类型 114 | // 1. 先打 head,标明类型 115 | // 2. reflect 116 | 117 | rTName := strings.Replace(T.String(), " ", "", 1) 118 | head := color.New(color.FgGreen).Sprint(rTName) + " " 119 | 120 | func() { 121 | if hasLen(T.Kind()) { 122 | head += "(" 123 | head += fmt.Sprintf("len=%v", color.New(color.FgYellow).Sprint(V.Len())) 124 | //txt += fmt.Sprintf("cap=%v ", color.New(color.FgGreen).Sprint(reflect.ValueOf(originValue).Cap())) 125 | head += ") " 126 | } 127 | }() 128 | 129 | // 恶心。。 130 | serialized += head 131 | 132 | // special complex 133 | switch v := originValue.(type) { 134 | case []byte: 135 | if !OptShowUint8sAsString { 136 | break 137 | } 138 | 139 | serialized += Serialize(string(v)) 140 | return 141 | 142 | case time.Time: 143 | serialized += fmt.Sprintf(`"%s"`, v.String()) 144 | return 145 | } 146 | 147 | // ... 148 | 149 | switch T.Kind() { 150 | case reflect.Array, reflect.Slice: 151 | 152 | buf := bytes.Buffer{} 153 | buf.WriteString("[") 154 | notEmpty := false 155 | for i := 0; i < V.Len(); i++ { 156 | v := V.Index(i) 157 | vi := v.Interface() 158 | vs := Serialize(vi) 159 | if vs != Zero { 160 | buf.WriteByte('\n') 161 | buf.WriteString(fmt.Sprintf("%d%s", i, SepKv)) 162 | buf.WriteString(vs) 163 | 164 | notEmpty = true 165 | } 166 | 167 | if i+1 >= MaxSliceLen { 168 | buf.WriteString(fmt.Sprintf("\n...\nother %d items...\n", V.Len()-MaxSliceLen)) 169 | break 170 | } 171 | } 172 | 173 | body := buf.String() 174 | if notEmpty { 175 | body = withTab(body) + "\n" 176 | } 177 | body += "]" 178 | 179 | result = body 180 | 181 | case reflect.Map: 182 | 183 | type item struct { 184 | key string 185 | value string 186 | } 187 | items := make([]item, 0, V.Len()) 188 | 189 | buf := bytes.Buffer{} 190 | for i, key := range V.MapKeys() { 191 | v := V.MapIndex(key).Interface() 192 | items = append(items, item{Serialize(key.Interface()), Serialize(v)}) 193 | 194 | if i+1 >= MaxMapLen { 195 | break 196 | } 197 | } 198 | 199 | if OptSortMapKeys { 200 | sort.Slice(items, func(i, j int) bool { 201 | return items[i].key < items[j].key 202 | }) 203 | } 204 | 205 | buf.WriteString("{") 206 | for _, item := range items { 207 | buf.WriteByte('\n') 208 | buf.WriteString(item.key) 209 | buf.WriteString(SepKv) 210 | buf.WriteString(item.value) 211 | } 212 | 213 | body := withTab(buf.String()) 214 | 215 | body += "\n}" 216 | 217 | result = body 218 | 219 | case reflect.Struct: 220 | buf := bytes.Buffer{} 221 | buf.WriteString("{") 222 | for i := 0; i < V.NumField(); i++ { 223 | field := V.Field(i) 224 | fieldT := V.Type().Field(i) 225 | buf.WriteByte('\n') 226 | buf.WriteString(fieldT.Name) 227 | buf.WriteString(": ") 228 | 229 | if field.CanInterface() { 230 | buf.WriteString(Serialize(field.Interface())) 231 | } else if field.CanAddr() { 232 | newValue := reflect.NewAt(fieldT.Type, unsafe.Pointer(field.UnsafeAddr())).Elem() 233 | buf.WriteString(Serialize(newValue.Interface())) 234 | } else { 235 | buf.WriteString("unaddressable: " + field.String()) 236 | } 237 | 238 | if i+1 >= MaxMapLen { 239 | break 240 | } 241 | } 242 | 243 | body := withTab(buf.String()) 244 | 245 | body += "\n}" 246 | 247 | result = body 248 | 249 | case reflect.Func: 250 | result = fmt.Sprintf("{ &%v }", originValue) 251 | 252 | case reflect.Chan: 253 | result = fmt.Sprintf("{...}") 254 | 255 | default: 256 | result = fmt.Sprintf("(%T)", originValue) + encoding.JsonEncode(originValue, encoding.OptIndentTab) 257 | } 258 | 259 | serialized += fmt.Sprint(result) 260 | 261 | return 262 | } 263 | -------------------------------------------------------------------------------- /dump/serialize_test.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type ( 10 | Integer int 11 | 12 | String string 13 | StringPtr *string 14 | StringAlias = string 15 | 16 | car struct { 17 | Speed int 18 | Owner interface{} 19 | } 20 | 21 | Person struct { 22 | Name String 23 | age int 24 | Interests []string 25 | 26 | friends [4]*Person 27 | 28 | Cars []*car 29 | 30 | action []func() string 31 | } 32 | ) 33 | 34 | func TestSerialize(t *testing.T) { 35 | time.Local = time.UTC 36 | 37 | type args struct { 38 | originValue interface{} 39 | } 40 | tests := []struct { 41 | name string 42 | originValue interface{} 43 | wantSerialized string 44 | }{ 45 | // TODO: Add test cases. 46 | {"byte", byte('a'), "a"}, 47 | {"uint8", uint8('a'), "a"}, 48 | {"int", 3, "3"}, 49 | // {"*int", new(int), "*0"}, 50 | {"Integer", Integer(3), "3"}, 51 | {"float", 0.3, "0.3"}, 52 | {"string", "abc", `"abc"`}, 53 | {"*string", ptrString("abc"), `"abc"`}, 54 | {"String", String("abc"), `"abc"`}, 55 | {"[]byte", []byte("abc"), `[]uint8 (len=3) "abc"`}, 56 | {"map", map[string]int{"a": 1, "b": 2, "c": 3}, `map[string]int(len=3){"a"=>1"b"=>2"c"=>3}`}, 57 | {"slice", []int{1, 3, 2}, "[]int(len=3)[0=>1\n1=>3\n2=>2]"}, 58 | {"time", time.Unix(1500000000, 0), `time.Time "2017-07-14 02:40:00 +0000 UTC"`}, 59 | } 60 | for _, tt := range tests { 61 | t.Run(tt.name, func(t *testing.T) { 62 | if gotSerialized := Serialize(tt.originValue); !equalNoSpace(gotSerialized, tt.wantSerialized) { 63 | t.Errorf("Serialize() = %v want %v", gotSerialized, tt.wantSerialized) 64 | // t.Errorf("Serialize() = %v escape = %v want %v", gotSerialized, escapeSpace(gotSerialized, " \t\n\r"), tt.wantSerialized) 65 | } 66 | }) 67 | } 68 | } 69 | 70 | func ptrString(s string) *string { 71 | return &s 72 | } 73 | 74 | // 忽略空格比较 75 | func equalNoSpace(a, b string) bool { 76 | charset := " \t\n\r" 77 | return escapeSpace(a, charset) == escapeSpace(b, charset) 78 | } 79 | 80 | func escapeSpace(a string, charset string) string { 81 | for _, char := range charset { 82 | a = strings.Replace(a, string(char), "", -1) 83 | } 84 | return a 85 | } 86 | -------------------------------------------------------------------------------- /dump/stub.s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kretech/xgo/d4d0736e5fd8b780db031a280045747eb62754bb/dump/stub.s -------------------------------------------------------------------------------- /dynamic/caller.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | import ( 4 | "runtime" 5 | "strings" 6 | ) 7 | 8 | func CallerName(short bool) string { 9 | return CallerNameSkip(1, short) 10 | } 11 | 12 | func CallerNameSkip(skip int, short bool) string { 13 | pc, _, _, _ := runtime.Caller(skip + 1) 14 | fn := runtime.FuncForPC(pc) 15 | name := fn.Name() 16 | if short { 17 | idx := strings.LastIndex(name, `.`) 18 | if idx+1 < len(name) { 19 | return name[idx+1:] 20 | } 21 | } 22 | 23 | return name 24 | } 25 | -------------------------------------------------------------------------------- /dynamic/caller_test.go: -------------------------------------------------------------------------------- 1 | package dynamic_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/dynamic" 7 | ) 8 | 9 | func a() string { 10 | return b() 11 | } 12 | 13 | func b() string { 14 | return dynamic.CallerName(true) 15 | } 16 | 17 | func TestCallerName(t *testing.T) { 18 | name := a() 19 | t.Log(name) 20 | 21 | name = b() 22 | t.Log(name) 23 | } 24 | -------------------------------------------------------------------------------- /dynamic/compact.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | func (name Name) Compact(args ...interface{}) (paramNames []string, paramAndValues map[string]interface{}) { 4 | return name.DepthCompact(1, args...) 5 | } 6 | 7 | func (name Name) DepthCompact(depth int, args ...interface{}) (paramNames []string, paramAndValues map[string]interface{}) { 8 | paramNames = name.VarNameDepth(depth+1, args...) 9 | 10 | // because of the variable depth 11 | // len(paramNames) would large than len(args) 12 | // so we put each args to paramNames by reversed order 13 | length := len(args) 14 | paramAndValues = make(map[string]interface{}, length) 15 | for i := 1; i <= length; i++ { 16 | paramAndValues[paramNames[len(paramNames)-i]] = args[len(args)-i] 17 | } 18 | 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /dynamic/function.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | import ( 4 | "encoding/json" 5 | "go/ast" 6 | "os" 7 | "path" 8 | "reflect" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "unsafe" 13 | 14 | "github.com/Kretech/xgo/astutil" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | // Parameter contains input and output info of function 19 | type Parameter struct { 20 | Name string 21 | RType reflect.Type 22 | } 23 | 24 | //FuncHeader contains function info 25 | type FuncHeader struct { 26 | Doc string // docs above func 27 | Name string 28 | In []*Parameter 29 | Out []*Parameter 30 | } 31 | 32 | // Equals return two header is equivalent for testing 33 | func (fh *FuncHeader) Equals(other *FuncHeader) bool { 34 | if !(fh.Name == other.Name && fh.Doc == other.Doc) { 35 | return false 36 | } 37 | 38 | if !(len(fh.In) == len(other.In) && len(fh.Out) == len(other.Out)) { 39 | return false 40 | } 41 | 42 | a := append(fh.In, fh.Out...) 43 | b := append(other.In, other.Out...) 44 | for i := range a { 45 | if !(a[i].Name == a[i].Name && b[i].RType == b[i].RType) { 46 | return false 47 | } 48 | } 49 | 50 | return true 51 | } 52 | 53 | // Encode is convenient for json marshal 54 | func (fh *FuncHeader) Encode() string { 55 | bytes, _ := json.Marshal(fh) 56 | return string(bytes) 57 | } 58 | 59 | var fhCache sync.Map 60 | 61 | //GetFuncHeader return function header cached in funcPC 62 | // @fixme 63 | // according the comment of reflect/Value.Pointer 64 | // pc is not unique for simple function 65 | func GetFuncHeader(originFunc interface{}) (fh FuncHeader, err error) { 66 | pc := funcPC(originFunc) 67 | cacheKey := uint(pc) 68 | value, ok := fhCache.Load(cacheKey) 69 | if ok { 70 | fh = value.(FuncHeader) 71 | return 72 | } 73 | 74 | fh, err = GetFuncHeaderNoCache(originFunc) 75 | fhCache.Store(cacheKey, fh) 76 | 77 | return 78 | } 79 | 80 | // GetFuncHeaderNoCache return function header in runtime without cache 81 | func GetFuncHeaderNoCache(originFunc interface{}) (fh FuncHeader, err error) { 82 | pc := funcPC(originFunc) 83 | runtimeFunc := runtime.FuncForPC(pc) 84 | funcNameFull := runtimeFunc.Name() 85 | funcName := funcNameFull[strings.LastIndexByte(funcNameFull, '.')+1:] 86 | 87 | fh.Name = funcName 88 | 89 | fileLong, _ := runtimeFunc.FileLine(pc) 90 | 91 | pkgPath := path.Dir(fileLong) 92 | pkgShort := path.Base(pkgPath) 93 | fileShort := path.Base(fileLong) 94 | 95 | astPkg, err := astutil.ReadPackageWithName(pkgPath, pkgShort, fileShort, func(info os.FileInfo) bool { 96 | return strings.Contains(info.Name(), fileShort) 97 | }) 98 | if err != nil { 99 | err = errors.WithStack(err) 100 | return 101 | } 102 | 103 | astFunc := getAstFunc(astPkg.Files[fileLong], funcNameFull) 104 | if astFunc == nil { 105 | err = errors.Wrap(err, `unsupport function`) 106 | return 107 | } 108 | 109 | fh.addDoc(astFunc) 110 | fh.addParams(astFunc) 111 | 112 | T := reflect.TypeOf(originFunc) 113 | for i, p := range append(fh.In) { 114 | p.RType = T.In(i) 115 | } 116 | for i, p := range append(fh.Out) { 117 | p.RType = T.Out(i) 118 | } 119 | 120 | return 121 | } 122 | 123 | func getAstFunc(file *ast.File, funcNameFull string) *ast.FuncDecl { 124 | base := path.Base(funcNameFull) 125 | base = strings.TrimPrefix(base, file.Name.Name+".") 126 | if !strings.Contains(base, `*`) && strings.HasPrefix(runtime.Version(), `go1.10`) { 127 | base = strings.Replace(base, "(", "", 1) 128 | base = strings.Replace(base, ")", "", 1) 129 | } 130 | 131 | for _, d := range file.Decls { 132 | if fn, ok := d.(*ast.FuncDecl); ok { 133 | fnName := fn.Name.Name 134 | if fn.Recv != nil { 135 | recv := fn.Recv.List[0].Type 136 | recvName := `` 137 | switch e := recv.(type) { 138 | case *ast.Ident: 139 | recvName = e.Name 140 | case *ast.StarExpr: 141 | recvName = `(` + `*` + e.X.(*ast.Ident).Name + `)` 142 | default: 143 | recvName = astutil.ExprString(recv) 144 | } 145 | fnName = recvName + `.` + fnName + `-fm` 146 | } 147 | if fnName == base { 148 | return fn 149 | } 150 | } 151 | } 152 | 153 | return nil 154 | } 155 | 156 | func (fh *FuncHeader) addDoc(astFunc *ast.FuncDecl) { 157 | if astFunc.Doc == nil { 158 | return 159 | } 160 | 161 | for _, c := range astFunc.Doc.List { 162 | if fh.Doc != `` { 163 | fh.Doc += "\n" 164 | } 165 | fh.Doc += c.Text 166 | } 167 | } 168 | 169 | func (fh *FuncHeader) addParams(astFunc *ast.FuncDecl) { 170 | 171 | for _, field := range astFunc.Type.Params.List { 172 | pa := Parameter{} 173 | for _, name := range field.Names { 174 | if pa.Name != `` { 175 | pa.Name += `,` 176 | } 177 | pa.Name += name.Name 178 | } 179 | 180 | //typeStr := p.Type.(*ast.Ident).Name // string 181 | 182 | fh.In = append(fh.In, &pa) 183 | } 184 | 185 | if astFunc.Type.Results != nil { 186 | for _, field := range astFunc.Type.Results.List { 187 | pa := Parameter{} 188 | for _, name := range field.Names { 189 | if pa.Name != `` { 190 | pa.Name += `,` 191 | } 192 | pa.Name += name.Name 193 | } 194 | 195 | fh.Out = append(fh.Out, &pa) 196 | } 197 | } 198 | } 199 | 200 | func funcPC(f interface{}) uintptr { 201 | return reflect.ValueOf(f).Pointer() 202 | // see reflect/Value.Pointer and syscall/funcPC 203 | // return **(**uintptr)(unsafe.Pointer(&f)) 204 | } 205 | 206 | func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { 207 | return unsafe.Pointer(uintptr(p) + x) 208 | } 209 | -------------------------------------------------------------------------------- /dynamic/function_test.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/Kretech/xgo/astutil" 9 | ) 10 | 11 | var ( 12 | TString = reflect.TypeOf(``) 13 | ) 14 | 15 | func tEmptyFunc() {} 16 | func tOnlyInFunc(s string) {} 17 | func tFunc(s1 string) (s2 string) { return } 18 | 19 | // Person ... 20 | type Person struct{} 21 | 22 | // comment 23 | func (this Person) Name() string { 24 | return `name` 25 | } 26 | 27 | func (this *Person) PtrName() string { 28 | return `ptrName` 29 | } 30 | 31 | // Say can say something 32 | func (this Person) Say(c string) string { 33 | return this.Name() + ` : ` + c 34 | } 35 | 36 | func TestGetFuncHeader(t *testing.T) { 37 | type args struct { 38 | fn interface{} 39 | } 40 | tests := []struct { 41 | args args 42 | wantF FuncHeader 43 | wantErr bool 44 | }{ 45 | // TODO: Add test cases. 46 | {args{func(string) {}}, FuncHeader{Name: `func1`}, false}, 47 | {args{tEmptyFunc}, FuncHeader{Name: `tEmptyFunc`}, false}, 48 | {args{tOnlyInFunc}, FuncHeader{Name: `tOnlyInFunc`, In: []*Parameter{{`s`, TString}}}, false}, 49 | {args{tFunc}, FuncHeader{Name: `tFunc`, In: []*Parameter{{`s1`, TString}}, Out: []*Parameter{{`s2`, TString}}}, false}, 50 | {args{Person{}.Name}, FuncHeader{Name: `Name-fm`, Doc: `// comment`, Out: []*Parameter{{``, TString}}}, false}, 51 | {args{Person{}.Say}, FuncHeader{Name: `Say-fm`, Doc: `// Say can say something`, In: []*Parameter{{`c`, TString}}, Out: []*Parameter{{``, TString}}}, false}, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.wantF.Name, func(t *testing.T) { 55 | gotF, err := GetFuncHeader(tt.args.fn) 56 | if (err != nil) != tt.wantErr { 57 | t.Errorf("FunctionSign() error = %v, wantErr %v", err, tt.wantErr) 58 | return 59 | } 60 | if !tt.wantF.Equals(&gotF) { 61 | t.Errorf("FunctionSign() gotF = %v, want %v", gotF.Encode(), tt.wantF.Encode()) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | func BenchmarkGetFuncHeader(b *testing.B) { 68 | astutil.OptPackageCache = false 69 | b.Run(`NoCache_i0_o0`, func(b *testing.B) { 70 | for i := 0; i < b.N; i++ { 71 | _, _ = GetFuncHeaderNoCache(tEmptyFunc) 72 | } 73 | }) 74 | b.Run(`NoCache_i1_o1`, func(b *testing.B) { 75 | for i := 0; i < b.N; i++ { 76 | _, _ = GetFuncHeaderNoCache(tFunc) 77 | } 78 | }) 79 | 80 | astutil.OptPackageCache = true 81 | b.Run(`CacheAST`, func(b *testing.B) { 82 | for i := 0; i < b.N; i++ { 83 | _, _ = GetFuncHeaderNoCache(tFunc) 84 | } 85 | }) 86 | 87 | b.Run(`CacheFH`, func(b *testing.B) { 88 | for i := 0; i < b.N; i++ { 89 | _, _ = GetFuncHeader(tFunc) 90 | } 91 | }) 92 | } 93 | 94 | func TestGetFuncHeaderExample(t *testing.T) { 95 | h, _ := GetFuncHeader(func() {}) 96 | h, _ = GetFuncHeader((&Person{}).PtrName) 97 | //h, _ = GetFuncHeader(Person{}.Say) 98 | t.Log(h.Name) 99 | t.Log(h.Doc) 100 | for _, param := range append(h.In, h.Out...) { 101 | t.Log(param.Name, param.RType) 102 | } 103 | } 104 | 105 | func ExampleGetFuncHeader() { 106 | 107 | // // Person ... 108 | // type Person struct{} 109 | // 110 | // // comment 111 | // func (this Person) Name() string { 112 | // return `noname` 113 | // } 114 | // 115 | // // Say can say something 116 | // func (this Person) Say(c string) string { 117 | // return this.Name() + ` : ` + c 118 | // } 119 | 120 | h, _ := GetFuncHeader(Person{}.Say) 121 | log.Println(h.Name) 122 | //: Say-fm 123 | 124 | log.Println(h.Doc) 125 | //: // Say can say something 126 | 127 | for _, param := range append(h.In, h.Out...) { 128 | log.Println(param.Name, param.RType) 129 | //: c string 130 | //: string 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /dynamic/init_test.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | import "log" 4 | 5 | func init() { 6 | log.SetFlags(log.LstdFlags | log.Lshortfile) 7 | } 8 | -------------------------------------------------------------------------------- /dynamic/log.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | func init() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /dynamic/readme.md: -------------------------------------------------------------------------------- 1 | # *XGo*/Dynamic 2 | 3 | this package provides some dynamic features using `reflect` and `go/ast`. 4 | 5 | ## Features 6 | 7 | ### Function Headers 8 | 9 | Examples: https://gowalker.org/github.com/Kretech/xgo/dynamic#_ex_btn_GetFuncHeader 10 | 11 | ### Variable Names 12 | 13 | https://gowalker.org/github.com/Kretech/xgo/p#VarName 14 | 15 | ### Compact function from Php 16 | 17 | https://gowalker.org/github.com/Kretech/xgo/p#Compact 18 | 19 | ### Variable scopes in goroutine 20 | 21 | https://gowalker.org/github.com/Kretech/xgo/p#G 22 | -------------------------------------------------------------------------------- /dynamic/variant_name.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "runtime" 9 | "sync" 10 | 11 | "github.com/Kretech/xgo/astutil" 12 | ) 13 | 14 | type Name struct { 15 | 16 | // X.Y means how user call this function 17 | // if user called dynamic.VarName(a), X=dynamic Y=VarName 18 | // if user called dump.Dump(a) X=dump Y=Dump 19 | // set this variant used to avoid to scanner import alias 20 | X string 21 | Y string 22 | } 23 | 24 | func NameOf(x string) Name { 25 | return Name{X: x} 26 | } 27 | 28 | // VarName return the variable names 29 | // VarName(a, b) => []string{"a", "b"} 30 | func (n Name) VarName(args ...interface{}) []string { 31 | n = n.setY(func() string { return CallerNameSkip(2, true) }) 32 | return n.VarNameDepth(1, args...) 33 | } 34 | 35 | // VarNameDepth return the variable names of args... 36 | // args is a stub for call this method 37 | // 38 | // how can we do this ? 39 | // first, get function info by runtime.Caller 40 | // second, get astFile with ast/parser 41 | // last, match function parameters in ast and get the code 42 | func (n Name) VarNameDepth(skip int, args ...interface{}) (names []string) { 43 | n = n.setY(func() string { return CallerNameSkip(2, true) }) 44 | 45 | _, calledFile, calledLine, _ := runtime.Caller(skip + 1) 46 | 47 | shouldCalledExpr := n.X + `.` + n.Y 48 | 49 | // todo 一行多次调用时,还需根据 runtime 找到 column 一起定位 50 | cacheKey := fmt.Sprintf("%s:%d@%s", calledFile, calledLine, shouldCalledExpr) 51 | return cacheGet(cacheKey, func() interface{} { 52 | 53 | r := []string{} 54 | found := false 55 | 56 | fset := token.NewFileSet() 57 | f, _ := parser.ParseFile(fset, calledFile, nil, 0) 58 | 59 | ast.Inspect(f, func(node ast.Node) (goon bool) { 60 | if found { 61 | return false 62 | } 63 | 64 | if node == nil { 65 | return false 66 | } 67 | 68 | call, ok := node.(*ast.CallExpr) 69 | if !ok { 70 | return true 71 | } 72 | 73 | if fset.Position(call.End()).Line != calledLine { 74 | return true 75 | } 76 | 77 | exprStr := astutil.SrcOf(call.Fun) 78 | if exprStr != shouldCalledExpr { 79 | return true 80 | } 81 | 82 | // 拼装每个参数的名字 83 | for _, arg := range call.Args { 84 | name := astutil.SrcOf(arg) 85 | r = append(r, name) 86 | } 87 | 88 | found = true 89 | return false 90 | }) 91 | 92 | return r 93 | }).([]string) 94 | } 95 | 96 | func VarName(args ...interface{}) []string { 97 | name := Name{X: `dynamic`, Y: CallerName(true)} 98 | return name.VarNameDepth(1, args...) 99 | } 100 | 101 | func VarNameDepth(skip int, args ...interface{}) []string { 102 | name := Name{X: `dynamic`, Y: CallerName(true)} 103 | return name.VarNameDepth(skip+1, args...) 104 | } 105 | 106 | var m sync.Map 107 | 108 | func cacheGet(key string, backup func() interface{}) interface{} { 109 | v, found := m.Load(key) 110 | if !found { 111 | v = backup() 112 | m.Store(key, v) 113 | } 114 | 115 | return v 116 | } 117 | 118 | func (name Name) setY(y func() string) Name { 119 | if name.Y == `` { 120 | name.Y = y() 121 | } 122 | 123 | return name 124 | } 125 | -------------------------------------------------------------------------------- /dynamic/variant_name_test.go: -------------------------------------------------------------------------------- 1 | package dynamic_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/dynamic" 7 | ) 8 | 9 | func TestVarName4Debug(t *testing.T) { 10 | a := 1 11 | t.Run(`pkg.VarName`, func(t *testing.T) { 12 | v := dynamic.VarName(a) 13 | if v[0] != `a` { 14 | t.Error(v) 15 | } 16 | }) 17 | 18 | t.Run(`newName.VarName`, func(t *testing.T) { 19 | name := dynamic.NameOf(`name`) 20 | v := name.VarName(a) 21 | if len(v) == 0 || v[0] != `a` { 22 | t.Error(v) 23 | } 24 | }) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /encoding/base64.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import "encoding/base64" 4 | 5 | func Base64Decode(s string) string { 6 | b, _ := base64.StdEncoding.DecodeString(s) 7 | return string(b) 8 | } 9 | 10 | func Base64EncodeString(s string) string { 11 | return Base64Encode([]byte(s)) 12 | } 13 | 14 | func Base64Encode(bs []byte) string { 15 | return base64.StdEncoding.EncodeToString(bs) 16 | } 17 | -------------------------------------------------------------------------------- /encoding/encoding_test.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/Kretech/xgo/test" 11 | ) 12 | 13 | func TestBase64EncodeString(t *testing.T) { 14 | test.AssertEqual(t, Base64EncodeString(`hello`), `aGVsbG8=`) 15 | } 16 | 17 | func TestBase64Decode(t *testing.T) { 18 | test.AssertEqual(t, Base64Decode(`aGVsbG8=`), `hello`) 19 | } 20 | 21 | func TestJsonEncodeMap(t *testing.T) { 22 | m := make(map[string]interface{}) 23 | m[`a`] = 4.0 24 | m[`tt`] = `hello` 25 | b, _ := json.Marshal(m) 26 | 27 | expect := make(map[string]interface{}) 28 | JsonDecode(b, &expect) 29 | test.AssertEqual(t, m, expect) 30 | 31 | m[`escape`] = `&` 32 | test.AssertTrue(t, strings.Contains(JsonEncode(m), `"escape":"&"`)) 33 | test.AssertTrue(t, strings.Contains(JsonEncode(m, OptEscapeHtml), `"escape":"\u0026"`)) 34 | } 35 | 36 | func TestJsonDecodeObject(t *testing.T) { 37 | 38 | type User struct { 39 | Id int `json:"id"` 40 | Name string `json:"name"` 41 | } 42 | 43 | m1 := User{ 44 | Id: 4, 45 | Name: "hi", 46 | } 47 | 48 | m2 := User{} 49 | JsonDecode(`{"id":4,"name":"hi"}`, &m2) 50 | 51 | test.AssertEqual(t, m1, m2) 52 | } 53 | 54 | func TestJsonMarshalTime(t *testing.T) { 55 | m := map[string]interface{}{ 56 | "t": time.Date(2001, 1, 1, 1, 1, 0, 0, time.Local), 57 | "X": time.Date(1002, 1, 1, 1, 1, 0, 0, time.Local), 58 | } 59 | s := JsonEncode(m) 60 | fmt.Println(s) 61 | 62 | type A struct { 63 | X time.Time 64 | } 65 | 66 | m2 := A{} 67 | err := JsonDecode(s, &m2) 68 | fmt.Println(m2, err) 69 | } 70 | -------------------------------------------------------------------------------- /encoding/json.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os" 9 | ) 10 | 11 | type Opt uint8 12 | 13 | const ( 14 | OptEscapeHtml Opt = 1 << 1 15 | 16 | OptIndentTab Opt = 1 << 2 17 | ) 18 | 19 | var ( 20 | logger = log.New(os.Stderr, `xgo.encoding`, log.Ldate|log.Ltime|log.Lshortfile) 21 | ) 22 | 23 | func (opt Opt) escapeHtml() bool { 24 | return opt&OptEscapeHtml > 0 25 | } 26 | 27 | func (opt Opt) indentTab() bool { 28 | return opt&OptIndentTab > 0 29 | } 30 | 31 | func (opt Opt) Apply(enc *json.Encoder) { 32 | 33 | enc.SetEscapeHTML(opt.escapeHtml()) 34 | 35 | if opt.indentTab() { 36 | enc.SetIndent("", "\t") 37 | } 38 | 39 | } 40 | 41 | func JsonEncode(s interface{}, opts ...Opt) string { 42 | var opt Opt 43 | if len(opts) > 0 { 44 | opt = opts[0] 45 | } 46 | 47 | buffer := &bytes.Buffer{} 48 | encoder := json.NewEncoder(buffer) 49 | 50 | opt.Apply(encoder) 51 | 52 | err := encoder.Encode(s) 53 | if err != nil { 54 | logger.Output(2, fmt.Sprintf("JsonEncode error: %v", err)) 55 | } 56 | 57 | b := buffer.Bytes() 58 | 59 | if len(b) > 0 && b[len(b)-1] == '\n' { 60 | // 去掉 go std 给加的 \n 61 | // 正常的 json 末尾是不会有 \n 的 62 | // @see https://github.com/golang/go/issues/7767 63 | b = b[:len(b)-1] 64 | } 65 | 66 | return string(b) 67 | } 68 | 69 | func JsonDecode(str interface{}, ele interface{}) error { 70 | if ss, ok := str.(string); ok { 71 | str = []byte(ss) 72 | } 73 | return json.Unmarshal(str.([]byte), &ele) 74 | } 75 | -------------------------------------------------------------------------------- /firewall/firewall.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | -------------------------------------------------------------------------------- /firewall/limiter.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | type Limiter interface { 4 | Acquire() 5 | } 6 | -------------------------------------------------------------------------------- /firewall/mutexlimiter.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type MutexLimiter struct { 10 | ttl time.Duration 11 | 12 | limit int 13 | current int 14 | flushed int 15 | 16 | lock sync.Mutex 17 | } 18 | 19 | func (l *MutexLimiter) Acquire() { 20 | l.lock.Lock() 21 | 22 | if l.current < l.limit { 23 | l.current++ 24 | l.lock.Unlock() 25 | return 26 | } 27 | 28 | // if overflow 29 | // release lock and acquire later 30 | l.lock.Unlock() 31 | runtime.Gosched() 32 | l.Acquire() 33 | } 34 | 35 | func NewMutexLimiter(ttl time.Duration, limit int) *MutexLimiter { 36 | obj := &MutexLimiter{ttl: ttl, limit: limit} 37 | 38 | start := make(chan struct{}) 39 | go func() { 40 | start <- struct{}{} 41 | for range time.Tick(ttl) { 42 | obj.lock.Lock() 43 | obj.current = 0 44 | obj.flushed++ 45 | obj.lock.Unlock() 46 | } 47 | }() 48 | <-start 49 | 50 | return obj 51 | } 52 | -------------------------------------------------------------------------------- /firewall/mutexlimiter_test.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestMutexLimiter_Acquire(t *testing.T) { 11 | ttl := time.Second / 123 12 | limit := 300 13 | total := 1000 14 | 15 | s := NewMutexLimiter(ttl, limit) 16 | start := time.Now() 17 | for i := 0; i < total; i++ { 18 | s.Acquire() 19 | } 20 | end := time.Now() 21 | 22 | fmt.Println(end.Sub(start)) 23 | fmt.Println(int(end.Sub(start)/ttl)*limit <= total) 24 | } 25 | 26 | func TestNewMutexLimiterDemo(t *testing.T) { 27 | return 28 | log.Println(`hi`, 0, 0, time.Now().Unix()) 29 | l := NewMutexLimiter(1*time.Second, 2) 30 | time.Sleep(2 * time.Second) 31 | for { 32 | l.Acquire() 33 | log.Println(`hi`, time.Now().UnixNano()) 34 | } 35 | } 36 | 37 | func BenchmarkMutexLimiter_Acquire(b *testing.B) { 38 | s := NewMutexLimiter(time.Second, 1<<30) 39 | for i := 0; i < b.N; i++ { 40 | s.Acquire() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /firewall/readme.md: -------------------------------------------------------------------------------- 1 | # Firewall 2 | 3 | ### Intro 4 | 5 | | files | how it works | Benchmark / 2.6 GHz Intel Core i7 | 6 | | ------------ | ------------------------------------- | ---- | 7 | | Limiter | limiter interface | | 8 | | SleepLimiter | sleep process when tokens are run out | 20M / qps 81.9 ns/op | 9 | | MutexLimiter | use mutex counter and ticker to rate | 100M / qps 14.2 ns/op | 10 | | | | | 11 | | Semaphore | wraped chan struct{} | 30M / qps 40.4 ns/op | 12 | | | | | 13 | | | | | 14 | | | | | 15 | 16 | ### How to use 17 | 18 | ```go 19 | // RateLimiter 20 | limiter := New{XXX}Limiter(1*time.Second, 300) // 300 tokens in one second 21 | limiter.Acquire() // return immediately or waiting useable token 22 | 23 | // Semaphore 24 | sema := NewChanSemaphore(cap) 25 | sema.Acquire() 26 | sema.Release() 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /firewall/resource_limiter.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | type ResourceLimiter interface { 4 | Acquire() interface{} 5 | Release(resource interface{}) 6 | } 7 | 8 | type ChanResourceLimiter struct { 9 | pool chan interface{} 10 | } 11 | 12 | // NewChanResourcePool ... 13 | func NewChanResourcePool(cap int) *ChanResourceLimiter { 14 | obj := &ChanResourceLimiter{pool: make(chan interface{}, cap)} 15 | return obj 16 | } 17 | 18 | func (this *ChanResourceLimiter) Acquire() interface{} { 19 | return <-this.pool 20 | } 21 | 22 | func (this *ChanResourceLimiter) Release(resource interface{}) { 23 | this.pool <- resource 24 | } 25 | -------------------------------------------------------------------------------- /firewall/semaphore.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | type Semaphore interface { 4 | // P wait() 5 | Acquire() 6 | 7 | // V signal() 8 | Release() 9 | } 10 | 11 | // NewSemaphore 12 | func NewSemaphore(cap int) Semaphore { 13 | return NewChanSemaphore(cap) 14 | } 15 | 16 | // Semaphore 限制同一时刻,最多可以发生的次数 17 | type ChanSemaphore struct { 18 | pool chan struct{} 19 | } 20 | 21 | // NewChanSemaphore 22 | func NewChanSemaphore(cap int) *ChanSemaphore { 23 | obj := &ChanSemaphore{pool: make(chan struct{}, cap)} 24 | return obj 25 | } 26 | 27 | func (this *ChanSemaphore) Release() { 28 | <-this.pool 29 | } 30 | 31 | func (this *ChanSemaphore) Acquire() { 32 | this.pool <- struct{}{} 33 | } 34 | -------------------------------------------------------------------------------- /firewall/semaphore_test.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestChanSemaphore(t *testing.T) { 11 | cost := time.Second / 130 12 | limit := 6 13 | total := 20 14 | 15 | wg := sync.WaitGroup{} 16 | wg.Add(total) 17 | 18 | s := NewChanSemaphore(limit) 19 | start := time.Now() 20 | for i := 0; i < total; i++ { 21 | go func(i int) { 22 | s.Acquire() 23 | time.Sleep(cost) 24 | //fmt.Println(i) 25 | wg.Done() 26 | s.Release() 27 | }(i) 28 | } 29 | 30 | wg.Wait() 31 | 32 | end := time.Now() 33 | 34 | fmt.Println(end.Sub(start)) 35 | // 单位时间实际执行了 总任务/总时间 total/T 36 | // 单位时间理论最多执行 limit/cost 37 | // total/T <= limit/cost 38 | fmt.Println(total*int(cost) <= limit*int(end.Sub(start))) 39 | } 40 | 41 | func BenchmarkNewChanSemaphore(b *testing.B) { 42 | s := NewChanSemaphore(1<<30 - 1) 43 | for i := 0; i < b.N; i++ { 44 | s.Acquire() 45 | s.Release() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /firewall/sleeplimiter.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // SleepLimiter 限制单位时间内的可执行次数 9 | type SleepLimiter struct { 10 | ttl time.Duration 11 | limit int 12 | 13 | current int 14 | expireAt time.Time 15 | 16 | sync.Mutex 17 | } 18 | 19 | func NewSleepLimiter(ttl time.Duration, limit int) *SleepLimiter { 20 | obj := &SleepLimiter{ttl: ttl, limit: limit} 21 | return obj 22 | } 23 | 24 | func (this *SleepLimiter) Acquire() { 25 | this.Lock() 26 | 27 | now := time.Now() 28 | 29 | // expired 30 | if this.expireAt.Before(now) { 31 | this.expireAt = now.Add(this.ttl) 32 | this.current = 0 33 | } 34 | 35 | this.current++ 36 | if this.current <= this.limit { 37 | this.Unlock() 38 | return 39 | } 40 | 41 | time.Sleep(this.expireAt.Sub(now)) 42 | this.Unlock() 43 | 44 | // 直接尾递归 45 | this.Acquire() 46 | } 47 | -------------------------------------------------------------------------------- /firewall/sleeplimiter_test.go: -------------------------------------------------------------------------------- 1 | package firewall 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "testing" 8 | "time" 9 | 10 | "golang.org/x/time/rate" 11 | ) 12 | 13 | func TestSleepLimiter_Acquire(t *testing.T) { 14 | ttl := time.Second / 123 15 | limit := 300 16 | total := 1000 17 | 18 | s := NewSleepLimiter(ttl, limit) 19 | start := time.Now() 20 | for i := 0; i < total; i++ { 21 | s.Acquire() 22 | } 23 | end := time.Now() 24 | 25 | fmt.Println(end.Sub(start)) 26 | fmt.Println(int(end.Sub(start)/ttl)*limit <= total) 27 | } 28 | 29 | func TestNewSleepLimiterDemo(t *testing.T) { 30 | return 31 | l := NewSleepLimiter(1*time.Second, 2) 32 | time.Sleep(2 * time.Second) 33 | for { 34 | l.Acquire() 35 | log.Println(`hi`) 36 | } 37 | } 38 | 39 | func BenchmarkSleepLimiter_Acquire(b *testing.B) { 40 | s := NewSleepLimiter(time.Second, 1<<30) 41 | for i := 0; i < b.N; i++ { 42 | s.Acquire() 43 | } 44 | } 45 | 46 | func BenchmarkRateLimiter(b *testing.B) { 47 | var limiter = rate.NewLimiter(1<<30, 1) 48 | for i := 0; i < b.N; i++ { 49 | limiter.Wait(context.TODO()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Kretech/xgo 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/fatih/color v1.7.0 7 | github.com/mattn/go-colorable v0.1.1 // indirect 8 | github.com/mattn/go-isatty v0.0.7 // indirect 9 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 10 | github.com/pkg/errors v0.8.1 11 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 2 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 3 | github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= 4 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 5 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 6 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 7 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 8 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= 9 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= 10 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 11 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 12 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 13 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 14 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 15 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 16 | -------------------------------------------------------------------------------- /gotemplate/function.go: -------------------------------------------------------------------------------- 1 | package gotemplate 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Kretech/xgo/word" 7 | ) 8 | 9 | func UseFuncAll() map[string]interface{} { 10 | return UseFuncSets( 11 | StringSet, 12 | ) 13 | } 14 | 15 | func UseFuncSets(funcSets ...map[string]interface{}) map[string]interface{} { 16 | all := make(map[string]interface{}, 32) 17 | for _, funcSet := range funcSets { 18 | for name, fun := range funcSet { 19 | all[name] = fun 20 | } 21 | } 22 | return all 23 | } 24 | 25 | var StringSet = map[string]interface{}{ 26 | "ToUpper": strings.ToUpper, 27 | "ToLower": strings.ToLower, 28 | "LowerFirst": word.LowerFirst, 29 | "UpperFirst": word.UpperFirst, 30 | } 31 | -------------------------------------------------------------------------------- /http/form/form.go: -------------------------------------------------------------------------------- 1 | package form 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | type Form struct { 9 | url.Values 10 | } 11 | 12 | func New(values url.Values) *Form { 13 | return &Form{Values: values} 14 | } 15 | 16 | func (this *Form) GetString(key string) string { 17 | return this.Values.Get(key) 18 | } 19 | 20 | func (this *Form) GetInt(key string, defaultValue int) int { 21 | i, err := strconv.Atoi(this.Values.Get(key)) 22 | if err == nil { 23 | return i 24 | } 25 | 26 | return defaultValue 27 | } 28 | 29 | func (this *Form) ToStringMap() map[string]string { 30 | m := make(map[string]string) 31 | for key := range this.Values { 32 | m[key] = this.Values.Get(key) 33 | } 34 | return m 35 | } 36 | -------------------------------------------------------------------------------- /httpclient/client.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | type HostGetter func() string 17 | 18 | type Client struct { 19 | host HostGetter 20 | 21 | *http.Client 22 | } 23 | 24 | func NewClient() *Client { 25 | c := &Client{ 26 | Client: DefaultHttpClient, 27 | } 28 | return c 29 | } 30 | 31 | func (c *Client) SetHost(host string) *Client { 32 | c.host = func() string { return host } 33 | return c 34 | } 35 | 36 | func (c *Client) SetHostGetter(hg HostGetter) *Client { 37 | c.host = hg 38 | return c 39 | } 40 | 41 | func (c *Client) Get(ctx context.Context, uri string, query ...map[string]string) (resp *Response, err error) { 42 | uri = c.mergeHost(uri) 43 | if len(query) > 0 { 44 | u, _ := url.Parse(uri) 45 | q := u.Query() 46 | for k, v := range query[0] { 47 | q.Set(k, v) 48 | } 49 | u.RawQuery = q.Encode() 50 | uri = u.String() 51 | } 52 | 53 | req := NewRequest(`GET`, uri) 54 | return c.DoRequest(ctx, req) 55 | } 56 | 57 | func (c *Client) PostForm(ctx context.Context, url string, form url.Values) (*Response, error) { 58 | return c.Post(ctx, url, "application/x-www-form-urlencoded", strings.NewReader(form.Encode())) 59 | } 60 | 61 | func (c *Client) PostJSON(ctx context.Context, url string, body interface{}) (*Response, error) { 62 | b, err := json.Marshal(body) 63 | if err != nil { 64 | return nil, errors.Wrapf(err, `json.Marshal error`) 65 | } 66 | 67 | return c.Post(ctx, url, "application/json", bytes.NewReader(b)) 68 | } 69 | 70 | func (c *Client) Post(ctx context.Context, url, contentType string, body io.Reader) (resp *Response, err error) { 71 | url = c.mergeHost(url) 72 | 73 | req := NewRequest(`POST`, url).Body(contentType, body) 74 | 75 | return c.DoRequest(ctx, req) 76 | } 77 | 78 | func (c *Client) DoRequest(ctx context.Context, req *Request) (resp *Response, err error) { 79 | return req.Do(ctx, c.Client) 80 | } 81 | 82 | func (c *Client) mergeHost(rawurl string) string { 83 | hasSchema := strings.HasPrefix(rawurl, `http://`) || strings.HasPrefix(rawurl, "https://") 84 | if !hasSchema && c.host != nil { 85 | host := c.host() 86 | rawurl = fmt.Sprintf("%s/%s", strings.TrimSuffix(host, `/`), strings.TrimPrefix(rawurl, `/`)) 87 | } 88 | return rawurl 89 | } 90 | 91 | var Default = NewClient() 92 | -------------------------------------------------------------------------------- /httpclient/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Kretech/xgo/httpclient 2 | 3 | go 1.12 4 | 5 | require github.com/pkg/errors v0.9.1 6 | -------------------------------------------------------------------------------- /httpclient/go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 2 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 3 | -------------------------------------------------------------------------------- /httpclient/readme.md: -------------------------------------------------------------------------------- 1 | # *XGo*/httpClient 2 | 3 | ## Usage 4 | 5 | ### Basic 6 | 7 | ```go 8 | resp, _ := httpclient.Default.Get(context.Background(), "https://www.baidu.com/sugrec") 9 | 10 | // 打印返回值 11 | log.Println(resp.String()) 12 | 13 | // 反序列化到对象 14 | resp.To(&obj) 15 | 16 | // 转为 interface{},复合对象为 map/array 17 | resp.ToInterface() 18 | 19 | // POST 20 | httpclient.Default.PostJSON(ctx, "http://foo.com/bar", struct{}) 21 | httpclient.Default.PostForm(ctx, "http://foo.com/bar", map[string][]string{}) 22 | ``` 23 | 24 | ### Custom Header 25 | 26 | ```go 27 | resp, _ := httpclient.PostRequest("http://foo.com/bar"). 28 | Header("key", "value"). 29 | Form(map[string][]string{}). // set form-urlencode body 30 | JSON(struct{}). // set json body 31 | Body("content-type", io.Reader). // set other body 32 | Do(ctx) 33 | ``` 34 | 35 | ### Custom Response Wrapper 36 | 37 | ```go 38 | resp, _ := ... 39 | // { 40 | // "code": -1, 41 | // "err_msg": "some error", 42 | // "data": { 43 | // "id": 1 44 | // } 45 | // } 46 | 47 | err := resp.UnwrapTo(FooWrapper{}, &v) 48 | /* or */ 49 | v, err := resp.UnwrapToInterface(FooWrapper{}) 50 | 51 | println(err) // "some error" 52 | println(v) // {"id":1} 53 | ``` 54 | 55 | ### Dynamic Request Host 56 | 57 | ```go 58 | c := httpclient.NewClient().SetHost(`http://foo.com`) 59 | resp, _ := c.Get("/abc") 60 | 61 | c := httpclient.NewClient().SetHostGetter(func() string { 62 | hosts := []string{"http://192.168.0.1", "http://192.168.0.2"} 63 | return hosts[rand.Int()] 64 | }}) 65 | resp, _ := c.Get("/abc") 66 | ``` 67 | 68 | ### Retries 69 | 70 | todo 71 | 72 | -------------------------------------------------------------------------------- /httpclient/request.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | // Request is builder for http.Request 17 | type Request struct { 18 | method string 19 | url string 20 | header http.Header 21 | body io.Reader 22 | } 23 | 24 | // NewRequest 25 | func NewRequest(method string, url string) *Request { 26 | return &Request{ 27 | method: method, 28 | url: url, 29 | } 30 | } 31 | 32 | // Header set header 33 | func (r *Request) Header(key, value string) *Request { 34 | r.header.Set(key, value) 35 | return r 36 | } 37 | 38 | // Body set body with content-type 39 | func (r *Request) Body(contentType string, body io.Reader) *Request { 40 | r.Header("Content-Type", contentType) 41 | r.body = body 42 | return r 43 | } 44 | 45 | // Form set body with application/x-www-form-urlencoded 46 | func (r *Request) Form(form url.Values) *Request { 47 | return r.Body("application/x-www-form-urlencoded", strings.NewReader(form.Encode())) 48 | } 49 | 50 | // JSON set body with application/json 51 | func (r *Request) JSON(body interface{}) *Request { 52 | r, err := r.JSONOrError(body) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | return r 58 | } 59 | 60 | // JSONOrError set body with application/json 61 | // return error when marshal failed 62 | func (r *Request) JSONOrError(body interface{}) (*Request, error) { 63 | r.Header("Content-Type", "application/json") 64 | b, err := json.Marshal(body) 65 | if err != nil { 66 | return r, errors.Wrapf(err, `json.Marshal error`) 67 | } 68 | 69 | return r.Body("application/json", bytes.NewReader(b)), nil 70 | } 71 | 72 | // Build to http.Request 73 | func (r *Request) Build(ctx context.Context) (*http.Request, error) { 74 | req, err := http.NewRequestWithContext(ctx, strings.ToUpper(r.method), r.url, r.body) 75 | if err != nil { 76 | return nil, errors.Wrapf(err, `NewRequestWithContext`) 77 | } 78 | 79 | for k, v := range r.header { 80 | req.Header[k] = v 81 | } 82 | 83 | return req, nil 84 | } 85 | 86 | var ( 87 | DefaultHttpClient = &http.Client{Timeout: 15 * time.Second} 88 | ) 89 | 90 | // Do client.do 91 | func (r *Request) Do(ctx context.Context, clients ...*http.Client) (resp *Response, err error) { 92 | var c *http.Client 93 | if len(clients) > 0 { 94 | c = clients[0] 95 | } else { 96 | c = DefaultHttpClient 97 | } 98 | 99 | req, err := r.Build(ctx) 100 | if err != nil { 101 | return 102 | } 103 | 104 | httpResp, err := c.Do(req) 105 | if err != nil { 106 | return nil, errors.Wrapf(err, `Do`) 107 | } 108 | 109 | resp = &Response{} 110 | resp.Response = httpResp 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /httpclient/request_test.go: -------------------------------------------------------------------------------- 1 | package httpclient_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/Kretech/xgo/httpclient" 9 | ) 10 | 11 | func TestRequest(t *testing.T) { 12 | c := httpclient.Default 13 | c.Timeout = time.Second * 2 14 | resp, err := c.Get(context.Background(), "https://www.baidu.com/sugrec") 15 | if err != nil { 16 | t.Errorf("%+v", err) 17 | } 18 | 19 | tos, err := resp.String() 20 | if err != nil { 21 | t.Errorf("%+v", err) 22 | } 23 | if len(tos) < 1 { 24 | t.Error("toString failed") 25 | } 26 | 27 | m := map[string]interface{}{} 28 | err = resp.To(&m) 29 | if err != nil { 30 | t.Errorf("%+v", err) 31 | } 32 | if len(m) < 1 { 33 | t.Error("toMap failed") 34 | } 35 | 36 | iface, err := resp.ToInterface() 37 | if err != nil { 38 | t.Errorf("%+v", err) 39 | } 40 | if iface == nil { 41 | t.Error("ToInteface failed") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /httpclient/response.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | type Response struct { 14 | *http.Response 15 | 16 | bytes []byte 17 | } 18 | 19 | func (r *Response) Bytes() (bytes []byte, err error) { 20 | if len(r.bytes) > 0 { 21 | return r.bytes, nil 22 | } 23 | 24 | r.bytes, err = ioutil.ReadAll(r.Response.Body) 25 | if err != nil { 26 | err = errors.Wrapf(err, "Response.ReadAll") 27 | return []byte{}, err 28 | } 29 | defer r.Body.Close() 30 | 31 | return r.bytes, nil 32 | } 33 | 34 | func (r *Response) String() (string, error) { 35 | bytes, err := r.Bytes() 36 | if err != nil { 37 | return ``, err 38 | } 39 | return string(bytes), nil 40 | } 41 | 42 | var ( 43 | Unmarshalers = map[string]func([]byte, interface{}) error{ 44 | `application/json`: JsonUnmarshaler, 45 | `application/xml`: XmlUnmarshaler, 46 | } 47 | 48 | DefaultUnmarshaler = JsonUnmarshaler 49 | 50 | JsonUnmarshaler = func(data []byte, v interface{}) error { return json.Unmarshal(data, v) } 51 | XmlUnmarshaler = func(data []byte, v interface{}) error { return xml.Unmarshal(data, v) } 52 | ) 53 | 54 | func (r *Response) To(v interface{}) error { 55 | bytes, err := r.Bytes() 56 | if err != nil { 57 | return err 58 | } 59 | 60 | var found bool 61 | contentType := r.Response.Header.Get("Content-Type") 62 | for key, unmarshaler := range Unmarshalers { 63 | if strings.Contains(contentType, key) { 64 | err = unmarshaler(bytes, v) 65 | if err == nil { 66 | found = true 67 | break 68 | } 69 | 70 | err = errors.Wrapf(err, `Unmarshal error with: %s`, bytes) 71 | return err 72 | } 73 | } 74 | if !found { 75 | if DefaultUnmarshaler == nil { 76 | return errors.Errorf(`nonsupport Content-Type: ` + contentType) 77 | } 78 | 79 | err = DefaultUnmarshaler(bytes, v) 80 | if err != nil { 81 | return errors.Wrapf(err, `Unmarshal error with: %s`, bytes) 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | func (r *Response) ToInterface() (interface{}, error) { 89 | var i interface{} 90 | err := r.To(&i) 91 | return i, err 92 | } 93 | 94 | func (r *Response) UnwrapTo(w ResponseWrapper, v interface{}) error { 95 | w.SetData(v) 96 | err := r.To(w) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | err = w.Error() 102 | if err != nil { 103 | return errors.Wrapf(err, "response.user.error") 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func (r *Response) UnwrapToInterface(w ResponseWrapper) (interface{}, error) { 110 | var i interface{} 111 | err := r.UnwrapTo(w, &i) 112 | return i, err 113 | } 114 | -------------------------------------------------------------------------------- /httpclient/wrapper.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import "errors" 4 | 5 | // see wrapper_test.go 6 | type ResponseWrapper interface { 7 | Error() error 8 | SetData(interface{}) 9 | GetData() interface{} 10 | } 11 | 12 | type CEDResponseWrapper struct { 13 | Code int `json:"code,omitempty"` 14 | ErrMsg string `json:"err_msg,omitempty"` 15 | HasData 16 | } 17 | 18 | func (ced *CEDResponseWrapper) Error() error { 19 | if ced.Code == 0 { 20 | return nil 21 | } 22 | 23 | return errors.New(ced.ErrMsg) 24 | } 25 | 26 | type HasData struct { 27 | Data interface{} `json:"data,omitempty"` 28 | } 29 | 30 | func (w *HasData) SetData(data interface{}) { 31 | w.Data = data 32 | } 33 | 34 | func (w *HasData) GetData() interface{} { 35 | return w.Data 36 | } 37 | -------------------------------------------------------------------------------- /httpclient/wrapper_test.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestCEDWrapper(t *testing.T) { 10 | resp := &Response{} 11 | resp.Response = &http.Response{} 12 | resp.bytes = []byte(`{"code":-1,"err_msg":"someerror","data":{"id":1}}`) 13 | 14 | v, err := resp.UnwrapToInterface(new(CEDResponseWrapper)) 15 | if v.(map[string]interface{})[`id`] != float64(1) { 16 | t.Errorf(`failed %v %T`, v, v) 17 | } 18 | 19 | if !strings.Contains(err.Error(), `someerror`) { 20 | t.Errorf("%+v", err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mapx/slice_map.go: -------------------------------------------------------------------------------- 1 | package mapx 2 | -------------------------------------------------------------------------------- /p/compact.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "regexp" 9 | "runtime" 10 | "strings" 11 | 12 | _ "unsafe" 13 | 14 | "github.com/Kretech/xgo/astutil" 15 | ) 16 | 17 | // VarName 用来获取变量的名字 18 | // VarName(a, b) => []string{"a", "b"} 19 | func VarName(args ...interface{}) []string { 20 | return varNameDepth(1, args...) 21 | } 22 | 23 | func varNameDepth(skip int, args ...interface{}) (c []string) { 24 | pc, _, _, _ := runtime.Caller(skip) 25 | userCalledFunc := runtime.FuncForPC(pc) // 用户调用 varName 的函数名 26 | 27 | // 用户通过这个方法来获取变量名。 28 | // 直接通过 package 调用可能有几种写法:p.F() alias.F() .F(),我们需要解析 import 来确定 29 | shouldCalledSel := userCalledFunc.Name()[:strings.LastIndex(userCalledFunc.Name(), `.`)] 30 | 31 | splitName := strings.Split(userCalledFunc.Name(), "/") 32 | shouldCalledExpr := splitName[len(splitName)-1] 33 | 34 | // 粗匹配 dump.(*CliDumper).Dump 35 | // 针对 d:=dumper(); d.Dump() 的情况 36 | if strings.Contains(shouldCalledExpr, ".(") { 37 | // 简单的正则来估算是不是套了一层 struct{} 38 | matched, _ := regexp.MatchString(`\w+\.(.+)\.\w+`, shouldCalledExpr) 39 | if matched { 40 | // 暂时不好判断前缀 d 是不是 dumper 类型,先略过 41 | // 用特殊的 . 前缀表示这个 sel 不处理 42 | shouldCalledSel = "" 43 | shouldCalledExpr = shouldCalledExpr[strings.LastIndex(shouldCalledExpr, "."):] 44 | } 45 | } 46 | 47 | //fmt.Println("userCalledFunc =", userCalledFunc.Name()) 48 | //fmt.Println("shouldCalledSel =", shouldCalledSel) 49 | //fmt.Println("shouldCalledExpr =", shouldCalledExpr) 50 | 51 | _, file, line, _ := runtime.Caller(skip + 1) 52 | //fmt.Printf("%v:%v\n", file, line) 53 | 54 | // todo 一行多次调用时,还需根据 runtime 找到 column 一起定位 55 | cacheKey := fmt.Sprintf("%s:%d@%s", file, line, shouldCalledExpr) 56 | return cacheGet(cacheKey, func() interface{} { 57 | 58 | r := []string{} 59 | found := false 60 | 61 | fset := token.NewFileSet() 62 | f, _ := parser.ParseFile(fset, file, nil, 0) 63 | 64 | // import alias 65 | aliasImport := make(map[string]string) 66 | for _, decl := range f.Decls { 67 | decl, ok := decl.(*ast.GenDecl) 68 | if !ok { 69 | continue 70 | } 71 | 72 | for _, spec := range decl.Specs { 73 | is, ok := spec.(*ast.ImportSpec) 74 | if !ok { 75 | continue 76 | } 77 | 78 | if is.Name != nil && strings.Trim(is.Path.Value, `""`) == shouldCalledSel { 79 | aliasImport[is.Name.Name] = shouldCalledSel 80 | shouldCalledExpr = is.Name.Name + "." + strings.Split(shouldCalledExpr, ".")[1] 81 | 82 | shouldCalledExpr = strings.TrimLeft(shouldCalledExpr, `.`) 83 | } 84 | } 85 | } 86 | 87 | ast.Inspect(f, func(node ast.Node) (goon bool) { 88 | if found { 89 | return false 90 | } 91 | 92 | if node == nil { 93 | return false 94 | } 95 | 96 | call, ok := node.(*ast.CallExpr) 97 | if !ok { 98 | return true 99 | } 100 | 101 | // 检查是不是调用 argsName 的方法 102 | isArgsNameFunc := func(expr *ast.CallExpr, shouldCallName string) bool { 103 | 104 | var equalCall = func(shouldCallName, currentName string) bool { 105 | if shouldCallName[0] == '.' { 106 | return strings.HasSuffix(currentName, shouldCallName) 107 | } 108 | 109 | return shouldCallName == currentName 110 | } 111 | 112 | if strings.Contains(shouldCallName, ".") { 113 | fn, ok := call.Fun.(*ast.SelectorExpr) 114 | if !ok { 115 | return false 116 | } 117 | 118 | // 对于多级访问比如 a.b.c(),fn.X 还是个 SelectorExpr 119 | lf, ok := fn.X.(*ast.Ident) 120 | if !ok { 121 | return false 122 | } 123 | 124 | currentName := lf.Name + "." + fn.Sel.Name 125 | 126 | return equalCall(shouldCallName, currentName) 127 | } else { 128 | fn, ok := call.Fun.(*ast.Ident) 129 | if !ok { 130 | return false 131 | } 132 | 133 | return fn.Name == shouldCallName 134 | } 135 | } 136 | 137 | if fset.Position(call.End()).Line != line { 138 | return true 139 | } 140 | 141 | if !isArgsNameFunc(call, shouldCalledExpr) { 142 | return true 143 | } 144 | 145 | // 拼装每个参数的名字 146 | for _, arg := range call.Args { 147 | name := astutil.ExprString(arg) 148 | r = append(r, name) 149 | } 150 | 151 | found = true 152 | return false 153 | }) 154 | 155 | return r 156 | }).([]string) 157 | } 158 | 159 | // Compact 将多个变量打包到一个字典里 160 | // a,b:=1,2 Comapct(a, b) => {"a":1,"b":2} 161 | // 参考自 http://php.net/manual/zh/function.compact.php 162 | func Compact(args ...interface{}) (paramNames []string, paramAndValues map[string]interface{}) { 163 | return DepthCompact(1, args...) 164 | } 165 | 166 | //go:linkname DepthCompact github.com/Kretech/xgo/dump.DepthCompact 167 | func DepthCompact(depth int, args ...interface{}) (paramNames []string, paramAndValues map[string]interface{}) { 168 | paramNames = varNameDepth(depth+1, args...) 169 | 170 | // because of the variable depth 171 | // len(paramNames) would large than len(args) 172 | // so we put each args to paramNames by reversed order 173 | length := len(args) 174 | paramAndValues = make(map[string]interface{}, length) 175 | for i := 1; i <= length; i++ { 176 | paramAndValues[paramNames[len(paramNames)-i]] = args[len(args)-i] 177 | } 178 | 179 | return 180 | } 181 | 182 | var m = newRWMap() 183 | 184 | func cacheGet(key string, backup func() interface{}) interface{} { 185 | 186 | v := m.Get(key) 187 | 188 | if v == nil { 189 | v = backup() 190 | m.Set(key, v) 191 | } 192 | 193 | return v 194 | } 195 | -------------------------------------------------------------------------------- /p/compact_alias_internal_test.go: -------------------------------------------------------------------------------- 1 | package p_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/Kretech/xgo/p" 7 | "github.com/Kretech/xgo/test" 8 | ) 9 | 10 | func TestArgsNameWithInternalAlias(t *testing.T) { 11 | as := test.A(t) 12 | 13 | a := 3 14 | b := 4 15 | a1 := VarName(a, b) 16 | as.Equal(a1, []string{`a`, `b`}) 17 | } 18 | 19 | func TestInternalCompact(t *testing.T) { 20 | as := test.A(t) 21 | 22 | age := 3 23 | name := `zhang` 24 | 25 | _, result := Compact(age, name) 26 | expect := map[string]interface{}{`age`: 3, `name`: `zhang`} 27 | 28 | as.Equal(result, expect) 29 | } 30 | -------------------------------------------------------------------------------- /p/compact_alias_test.go: -------------------------------------------------------------------------------- 1 | package p_test 2 | 3 | import ( 4 | "testing" 5 | 6 | pp "github.com/Kretech/xgo/p" 7 | "github.com/Kretech/xgo/test" 8 | ) 9 | 10 | func TestArgsNameWithAlias(t *testing.T) { 11 | as := test.A(t) 12 | 13 | a := 3 14 | b := 4 15 | a1 := pp.VarName(a, b) 16 | as.Equal(a1, []string{`a`, `b`}) 17 | } 18 | -------------------------------------------------------------------------------- /p/compact_test.go: -------------------------------------------------------------------------------- 1 | // package p_test is testing for p 2 | // which p is a php adapter 3 | 4 | package p_test 5 | 6 | import ( 7 | "fmt" 8 | "sync" 9 | "testing" 10 | 11 | "github.com/Kretech/xgo/p" 12 | "github.com/Kretech/xgo/test" 13 | ) 14 | 15 | func TestAll(t *testing.T) { 16 | cas := test.TR(t) 17 | 18 | // cas.Add(testArgsName) 19 | // cas.Add(testConcurrenceArgsName) 20 | // cas.Add(testCompact) 21 | 22 | // todo 这是一个bug,同一行调用多次 varName 时,无法识别是第几个,现在都认为是第一个 23 | cas.Add(func(t *test.Assert) { 24 | a := 3 25 | b := 4 26 | c := 4 27 | fmt.Println(p.VarName(a, b), 28 | p.VarName(b, a), p.VarName(c)) 29 | }) 30 | } 31 | 32 | func TestArgsName(t *testing.T) { 33 | as := test.A(t) 34 | 35 | a := 3 36 | b := 4 37 | a1 := p.VarName(a, b) 38 | as.Equal(a1, []string{`a`, `b`}) 39 | } 40 | 41 | func TestConcurrenceArgsName(t *testing.T) { 42 | as := test.A(t) 43 | 44 | a := 3 45 | c := 4 46 | wg := sync.WaitGroup{} 47 | for i := 1; i < 10; i++ { 48 | wg.Add(1) 49 | go func() { 50 | for i := 1; i < 20; i++ { 51 | if i%2 == 1 { 52 | as.Equal(p.VarName(a, c), []string{`a`, `c`}) 53 | } else { 54 | as.Equal(p.VarName(c), []string{`c`}) 55 | } 56 | } 57 | wg.Done() 58 | }() 59 | } 60 | wg.Wait() 61 | } 62 | 63 | func TestCompact(t *testing.T) { 64 | as := test.A(t) 65 | 66 | age := 3 67 | name := `zhang` 68 | 69 | _, result := p.Compact(age, name) 70 | expect := map[string]interface{}{`age`: 3, `name`: `zhang`} 71 | 72 | as.Equal(result, expect) 73 | } 74 | 75 | func BenchmarkVarName(b *testing.B) { 76 | a := 3 77 | for i := 0; i < b.N; i++ { 78 | p.VarName(a) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /p/dump.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "reflect" 9 | "strings" 10 | 11 | "github.com/Kretech/xgo/encoding" 12 | "github.com/fatih/color" 13 | ) 14 | 15 | var ( 16 | MaxSliceLen = 32 17 | MaxMapLen = 32 18 | 19 | SepKv = " => " 20 | 21 | Out io.Writer = os.Stdout 22 | 23 | // 24 | StringQuota = `"` 25 | ) 26 | 27 | // Deprecated 28 | // use dump.Dump 29 | func Dump(args ...interface{}) { 30 | DepthDump(1, args...) 31 | } 32 | 33 | // DD means Dump and Die 34 | func DD(args ...interface{}) { 35 | DepthDump(1, args...) 36 | 37 | panic(`DD`) 38 | } 39 | 40 | func DepthDump(depth int, args ...interface{}) { 41 | names, compacted := DepthCompact(depth+1, args...) 42 | 43 | for _, name := range names { 44 | txt := "" 45 | 46 | if strings.HasPrefix(name, "&") { 47 | txt += color.New(color.Italic, color.FgMagenta).Sprint("&") 48 | name = name[1:] 49 | } 50 | 51 | txt += color.New(color.Italic, color.FgCyan).Sprint(name) + SepKv 52 | 53 | txt += serialize(compacted[name]) 54 | 55 | _, _ = fmt.Fprintln(Out, txt) 56 | } 57 | } 58 | 59 | func serialize(originValue interface{}) (txt string) { 60 | result := originValue 61 | 62 | rT := reflect.TypeOf(originValue) 63 | rV := reflect.ValueOf(originValue) 64 | isPtr := false 65 | 66 | if rT.Kind() == reflect.Ptr { 67 | isPtr = true 68 | rT = rT.Elem() 69 | rV = rV.Elem() 70 | } 71 | 72 | // 基础类型 73 | switch rT.Kind() { 74 | case reflect.String: 75 | result = fmt.Sprintf(`%s%v%s`, StringQuota, rV.Interface(), StringQuota) 76 | 77 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 78 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 79 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 80 | result = fmt.Sprint(rV.Interface()) 81 | } 82 | 83 | if isPtr { 84 | txt += color.New(color.FgMagenta).Sprint("*") 85 | } 86 | 87 | if !IsScala(originValue) { 88 | // txt += fmt.Sprint(reflect.TypeOf(originValue)) 89 | 90 | rTName := strings.Replace(rT.String(), " ", "", 1) 91 | head := color.New(color.FgGreen).Sprint(rTName) + " " 92 | 93 | func() { 94 | defer func() { 95 | recover() 96 | }() 97 | 98 | head += "(" 99 | head += fmt.Sprintf("len=%v", color.New(color.FgYellow).Sprint(rV.Len())) 100 | //txt += fmt.Sprintf("cap=%v ", color.New(color.FgGreen).Sprint(reflect.ValueOf(originValue).Cap())) 101 | head += ") " 102 | }() 103 | 104 | // 恶心。。 105 | txt += head 106 | 107 | // ... 108 | 109 | switch rT.Kind() { 110 | case reflect.Slice: 111 | 112 | buf := bytes.Buffer{} 113 | buf.WriteString("[") 114 | for i := 0; i < rV.Len(); i++ { 115 | v := rV.Index(i).Interface() 116 | buf.WriteByte('\n') 117 | buf.WriteString(fmt.Sprintf("%d%s", i, SepKv)) 118 | buf.WriteString(serialize(v)) 119 | 120 | if i+1 >= MaxSliceLen { 121 | buf.WriteString(fmt.Sprintf("\n...\nother %d items...\n", rV.Len()-MaxSliceLen)) 122 | break 123 | } 124 | } 125 | 126 | body := withTab(buf.String()) 127 | 128 | body += "\n]" 129 | 130 | result = body 131 | 132 | case reflect.Map: 133 | 134 | buf := bytes.Buffer{} 135 | buf.WriteString("{") 136 | for i, key := range rV.MapKeys() { 137 | v := rV.MapIndex(key).Interface() 138 | buf.WriteByte('\n') 139 | buf.WriteString(serialize(key.Interface())) 140 | buf.WriteString(SepKv) 141 | buf.WriteString(serialize(v)) 142 | 143 | if i+1 >= MaxMapLen { 144 | break 145 | } 146 | } 147 | 148 | body := withTab(buf.String()) 149 | 150 | body += "\n}" 151 | 152 | result = body 153 | 154 | default: 155 | result = encoding.JsonEncode(originValue, encoding.OptIndentTab) 156 | } 157 | } 158 | 159 | txt += fmt.Sprint(result) 160 | 161 | return 162 | } 163 | 164 | // 简单类型 165 | func IsScala(v interface{}) bool { 166 | if v == nil { 167 | return true 168 | } 169 | 170 | t := reflect.TypeOf(v) 171 | if t.Kind() == reflect.Ptr { 172 | t = t.Elem() 173 | } 174 | 175 | switch t.Kind() { 176 | 177 | case reflect.Map, 178 | reflect.Struct, 179 | reflect.Slice, reflect.Array: 180 | return false 181 | 182 | default: 183 | return true 184 | } 185 | } 186 | 187 | func withTab(str string) string { 188 | return strings.Replace(str, "\n", "\n\t", -1) 189 | } 190 | -------------------------------------------------------------------------------- /p/dump_test.go: -------------------------------------------------------------------------------- 1 | package p_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/encoding" 7 | "github.com/Kretech/xgo/p" 8 | ) 9 | 10 | type _S struct { 11 | } 12 | 13 | func (this *_S) a() string { 14 | return `_s.a` 15 | } 16 | 17 | func (this *_S) b(t string) string { 18 | return `_s.b(` + t + `)` 19 | } 20 | 21 | func TestDump(t *testing.T) { 22 | 23 | aInt := 1 24 | bStr := `sf` 25 | cMap := map[string]interface{}{"name": "z", "age": 14} 26 | dArray := []interface{}{&cMap, aInt, bStr} 27 | c := cMap 28 | p.Dump(aInt, &aInt, &bStr, bStr, cMap, dArray, c, cMap["name"], dArray[2], dArray[aInt]) 29 | 30 | userId := func() int { return 4 } 31 | p.Dump(userId()) 32 | 33 | p.Dump(userId2()) 34 | 35 | _s := _S{} 36 | p.Dump(_s.a()) 37 | p.Dump(_s.b(`t`)) 38 | 39 | p.Dump(encoding.JsonEncode(`abc`)) 40 | p.Dump(encoding.JsonEncode(map[string]interface{}{"a": aInt})) 41 | } 42 | 43 | func userId2() int { 44 | return 8 45 | } 46 | -------------------------------------------------------------------------------- /p/goroutine.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/petermattis/goid" 7 | ) 8 | 9 | // GoID 获取当前 Goroutine 的 ID 10 | func GoID() int { 11 | return int(goid.Get()) 12 | } 13 | 14 | var _globals struct { 15 | ms map[int]rwmap 16 | sync.RWMutex 17 | } 18 | 19 | // G 获取当前协程内的全局变量 20 | // 参考自 http://php.net/manual/zh/reserved.variables.globals.php 21 | func G() *rwmap { 22 | _globals.Lock() 23 | 24 | if _globals.ms == nil { 25 | _globals.ms = make(map[int]rwmap, 32) 26 | } 27 | 28 | m, ok := _globals.ms[GoID()] 29 | if !ok { 30 | _globals.ms[GoID()] = *newRWMap() 31 | m = _globals.ms[GoID()] 32 | } 33 | 34 | _globals.Unlock() 35 | 36 | return &m 37 | } 38 | 39 | // _global alias to $_GLOBAL in current goroutine 40 | type rwmap struct { 41 | data map[interface{}]interface{} 42 | rw sync.RWMutex 43 | } 44 | 45 | func newRWMap() *rwmap { 46 | return &rwmap{data: make(map[interface{}]interface{}, 4)} 47 | } 48 | 49 | func (m *rwmap) Set(key interface{}, value interface{}) { 50 | m.rw.Lock() 51 | m.data[key] = value 52 | m.rw.Unlock() 53 | } 54 | 55 | func (m *rwmap) Get(key interface{}) interface{} { 56 | m.rw.Lock() 57 | defer m.rw.Unlock() 58 | return m.data[key] 59 | } 60 | -------------------------------------------------------------------------------- /p/goroutine_test.go: -------------------------------------------------------------------------------- 1 | package p_test 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/Kretech/xgo/p" 9 | "github.com/Kretech/xgo/test" 10 | ) 11 | 12 | func TestG(t *testing.T) { 13 | cas := test.TR(t) 14 | 15 | cas.Add(func(t *test.Assert) { 16 | wg := sync.WaitGroup{} 17 | for i := 0; i < 10; i++ { 18 | wg.Add(1) 19 | go func() { 20 | id1 := p.GoID() 21 | runtime.Gosched() 22 | id2 := p.GoID() 23 | t.Equal(id1, id2) 24 | 25 | p.G().Set(`gid`, id1) 26 | runtime.Gosched() 27 | t.Equal(p.G().Get(`gid`), id2) 28 | 29 | wg.Done() 30 | }() 31 | } 32 | wg.Wait() 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /p/readme.md: -------------------------------------------------------------------------------- 1 | # *XGo*/p 2 | 3 | Tricking implements for php runtime functions 4 | 5 | 6 | [TOC] 7 | 8 | [所有函数列表 => GoWalker](https://gowalker.org/github.com/Kretech/xgo/p) 9 | 10 | ## Sample 11 | 12 | ### Dumper 13 | 14 | ```go 15 | aInt := 1 16 | bStr := `sf` 17 | cMap := map[string]interface{}{"name": "z", "age": 14} 18 | dArray := []interface{}{&cMap, aInt, bStr} 19 | c := cMap 20 | 21 | p.Dump(aInt, &aInt, &bStr, bStr, cMap, dArray, c, cMap["name"], dArray[2], dArray[aInt]) 22 | ``` 23 | 24 | ![](https://i.loli.net/2019/03/14/5c8a541bd8497.png) 25 | 26 | ## 已知问题 27 | 28 | - compact 目前只支持一行调一次 29 | - 目前定位调用代码是通过行号和函数名,所以只支持一行一个 30 | 31 | ## todo 32 | 33 | - [ ] 获取函数签名 34 | - [ ] ClassLoader 35 | - [ ] Functional http API 36 | - [ ] JSON-RPC 37 | -------------------------------------------------------------------------------- /pipe/examples/cpipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define n 702410 9 | int a[n]; 10 | 11 | double dosth() 12 | { 13 | struct timeval begin; 14 | struct timeval end; 15 | gettimeofday(&begin, NULL); 16 | for (int i = 0; i < n; i++) 17 | { 18 | a[i] = a[i] * i + i; 19 | } 20 | gettimeofday(&end, NULL); 21 | return end.tv_sec - begin.tv_sec + (end.tv_usec - begin.tv_usec) *1.0/ 1000000; 22 | } 23 | 24 | int main() 25 | { 26 | int id = getpid(); 27 | for (int i = 0; i < n; i++) 28 | { 29 | a[i] = i; 30 | } 31 | 32 | char a[32]; 33 | int c = 0; 34 | while (~scanf("%s", a)) 35 | { 36 | double t = dosth(); 37 | printf("p%d:\tread:(len=%lu) %s\tusetime:%f\n", id, strlen(a), a, t); 38 | fflush(stdout); 39 | } 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /pipe/exec_pipe.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os/exec" 7 | "sync" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type ExecPipe struct { 13 | cmd *exec.Cmd 14 | 15 | stdinPipe io.WriteCloser 16 | stdoutPipe io.ReadCloser 17 | 18 | bufRW *bufio.ReadWriter 19 | 20 | lock sync.Mutex 21 | } 22 | 23 | func NewExecPipe(cmd *exec.Cmd) *ExecPipe { 24 | return &ExecPipe{cmd: cmd} 25 | } 26 | 27 | func (this *ExecPipe) Start() (err error) { 28 | this.stdinPipe, err = this.cmd.StdinPipe() 29 | if err != nil { 30 | return errors.Wrap(err, `pipe start`) 31 | } 32 | 33 | this.stdoutPipe, err = this.cmd.StdoutPipe() 34 | if err != nil { 35 | return errors.Wrap(err, `pipe start`) 36 | } 37 | 38 | err = this.cmd.Start() 39 | if err != nil { 40 | return errors.Wrap(err, `pipe start`) 41 | } 42 | 43 | this.bufRW = bufio.NewReadWriter(bufio.NewReader(this.stdoutPipe), bufio.NewWriter(this.stdinPipe)) 44 | 45 | return 46 | } 47 | 48 | func (this *ExecPipe) Stop() error { 49 | return this.cmd.Process.Kill() 50 | } 51 | 52 | func (this *ExecPipe) WriteAndRead(b []byte) (resp []byte, err error) { 53 | this.lock.Lock() 54 | defer this.lock.Unlock() 55 | 56 | _, err = this.bufRW.Write(b) 57 | if err != nil { 58 | err = errors.Wrap(err, `pipe write`) 59 | return 60 | } 61 | 62 | err = this.bufRW.Flush() 63 | if err != nil { 64 | err = errors.Wrap(err, `pipe flush`) 65 | return 66 | } 67 | 68 | resp, _, err = this.bufRW.ReadLine() 69 | if err != nil { 70 | err = errors.Wrap(err, `pipe readLine`) 71 | } 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /pipe/exec_pipe_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "os/exec" 9 | "strings" 10 | "sync" 11 | "testing" 12 | ) 13 | 14 | func init() { 15 | log.SetFlags(log.LstdFlags | log.Lshortfile) 16 | } 17 | 18 | func TestPipe(t *testing.T) { 19 | //cmd := exec.Command("awk '{print $0}'") 20 | cmd := exec.Command("./a.out") 21 | //cmd := exec.Command("top") 22 | 23 | stdinPipe, err := cmd.StdinPipe() 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | 29 | stdoutPipe, err := cmd.StdoutPipe() 30 | if err != nil { 31 | t.Error(err) 32 | return 33 | } 34 | 35 | err = cmd.Start() 36 | if err != nil { 37 | t.Error(err) 38 | return 39 | } 40 | 41 | rn := bufio.NewReadWriter(bufio.NewReader(stdoutPipe), bufio.NewWriter(stdinPipe)) 42 | for i := 0; i < 10; i++ { 43 | _, err := rn.WriteString("abc" + fmt.Sprint(rand.Int31()) + "\n") 44 | _ = rn.Writer.Flush() 45 | 46 | line, prefix, err := rn.ReadLine() 47 | t.Log(string(line), prefix, err) 48 | } 49 | } 50 | 51 | func TestPipe_Call(t *testing.T) { 52 | //p := NewPipe(exec.Command("awk", "'{print $0}'")) 53 | p := NewExecPipe(exec.Command("./a.out")) 54 | p.Start() 55 | 56 | wg := sync.WaitGroup{} 57 | wg.Add(1) 58 | 59 | go func() { 60 | wg.Wait() 61 | err := p.Stop() 62 | if err != nil { 63 | t.Error(err) 64 | } 65 | }() 66 | 67 | resp, err := p.WriteAndRead([]byte("hello_pipe\n")) 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | 72 | if !strings.Contains(string(resp), "hello_pipe") { 73 | t.Error(string(resp)) 74 | } 75 | 76 | wg.Done() 77 | } 78 | 79 | func BenchmarkPipe_WriteAndRead(b *testing.B) { 80 | p := NewExecPipe(exec.Command("./a.out")) 81 | p.Start() 82 | defer p.Stop() 83 | 84 | for i := 0; i < b.N; i++ { 85 | _, _ = p.WriteAndRead([]byte("hello_pipe\n")) 86 | } 87 | } 88 | 89 | func BenchmarkPipe_WriteAndReadParallel(b *testing.B) { 90 | p := NewExecPipe(exec.Command("./a.out")) 91 | p.Start() 92 | defer p.Stop() 93 | 94 | b.RunParallel(func(pb *testing.PB) { 95 | for pb.Next() { 96 | _, _ = p.WriteAndRead([]byte("hello_pipe\n")) 97 | } 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /pipe/pipe.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | type Pipe interface { 4 | Start() error 5 | Stop() error 6 | WriteAndRead([]byte) ([]byte, error) 7 | } 8 | -------------------------------------------------------------------------------- /pipe/pipes.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | type Pipes struct { 8 | pipes []Pipe 9 | pipesChan chan Pipe 10 | } 11 | 12 | // 13 | func NewPipes(pipes []Pipe) *Pipes { 14 | p := &Pipes{ 15 | pipes: pipes, 16 | pipesChan: make(chan Pipe, len(pipes)), 17 | } 18 | for _, pipe := range pipes { 19 | p.pipesChan <- pipe 20 | } 21 | return p 22 | } 23 | 24 | func (this *Pipes) Start() (err error) { 25 | for i := 0; i < len(this.pipes); i++ { 26 | err = this.pipes[i].Start() 27 | if err != nil { 28 | return errors.Wrapf(err, "pipes[%d]", i) 29 | } 30 | } 31 | return 32 | } 33 | 34 | func (this *Pipes) Stop() (err error) { 35 | for i := 0; i < len(this.pipes); i++ { 36 | stop := this.pipes[i].Stop() 37 | if stop != nil { 38 | err = errors.Wrapf(stop, "pipes[%d]", i) 39 | } 40 | } 41 | return 42 | } 43 | 44 | func (this *Pipes) AcquirePipe() Pipe { 45 | return <-this.pipesChan 46 | } 47 | 48 | func (this *Pipes) ReleasePipe(p Pipe) { 49 | this.pipesChan <- p 50 | } 51 | 52 | func (this *Pipes) WriteAndRead(b []byte) (resp []byte, err error) { 53 | p := this.AcquirePipe() 54 | defer this.ReleasePipe(p) 55 | 56 | return p.WriteAndRead(b) 57 | } 58 | -------------------------------------------------------------------------------- /pipe/pipes_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestPipes_WriteAndRead(t *testing.T) { 11 | pipes := []Pipe{} 12 | for i := 0; i < 8; i++ { 13 | pipes = append(pipes, NewExecPipe(exec.Command("./a.out"))) 14 | } 15 | p := NewPipes(pipes) 16 | 17 | err := p.Start() 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | defer p.Stop() 22 | 23 | for i := 0; i < 10; i++ { 24 | s := fmt.Sprintf("hello_pipe%d\n", i) 25 | resp, err := p.WriteAndRead([]byte(s)) 26 | if strings.Contains(string(resp), s) { 27 | t.Error("expect", s, "got", string(resp), err) 28 | } 29 | } 30 | } 31 | 32 | func BenchmarkPipes_WriteAndReadParallel(b *testing.B) { 33 | pipes := []Pipe{} 34 | for i := 0; i < 8; i++ { 35 | pipes = append(pipes, NewExecPipe(exec.Command("./a.out"))) 36 | } 37 | p := NewPipes(pipes) 38 | 39 | err := p.Start() 40 | if err != nil { 41 | b.Error(err) 42 | } 43 | defer p.Stop() 44 | 45 | b.RunParallel(func(pb *testing.PB) { 46 | for pb.Next() { 47 | _, err = p.WriteAndRead([]byte("hello_pipe3\n")) 48 | if err != nil { 49 | b.Error(err) 50 | } 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /pipe/readme.md: -------------------------------------------------------------------------------- 1 | # XGo/Pipe 2 | 3 | 在 Go 里通过 pipe 调 C 的一个 demo 4 | 5 | ### Run Examples 6 | 7 | ```bash 8 | gcc ./examples/cpipe.c -o a.out 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /string/string.go: -------------------------------------------------------------------------------- 1 | package string 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type String []rune 9 | 10 | //New 11 | func New(s interface{}) String { 12 | switch s.(type) { 13 | case *string: 14 | return New([]byte(*s.(*string))) 15 | case string: 16 | return New([]byte(s.(string))) 17 | case []byte: 18 | return String([]rune(string(s.([]byte)))) 19 | case []rune: 20 | return String(s.([]rune)) 21 | case String: 22 | return s.(String) 23 | default: 24 | return New(fmt.Sprint(s)) 25 | } 26 | } 27 | 28 | func (s String) String() string { 29 | return string(s) 30 | } 31 | 32 | func (s String) bytes() []byte { 33 | return []byte(s.String()) 34 | } 35 | 36 | func (s String) runes() []rune { 37 | return []rune(s) 38 | } 39 | 40 | // Equal determined equal with other strings 41 | func (s String) Equal(sub interface{}) bool { 42 | return s.String() == New(sub).String() 43 | } 44 | 45 | // Contains call strings.Contains in chains 46 | func (s String) Contains(sub interface{}) bool { 47 | return strings.Contains(s.String(), New(sub).String()) 48 | } 49 | 50 | // Index call strings.Index in chains 51 | func (s String) Index(sub interface{}) int { 52 | return strings.Index(s.String(), New(sub).String()) 53 | } 54 | 55 | // Split call strings.Split in chains 56 | func (s String) Split(seq interface{}) []string { 57 | return strings.Split(s.String(), New(seq).String()) 58 | } 59 | 60 | // HasPrefix call strings.HasPrefix in chains 61 | func (s String) HasPrefix(seq interface{}) bool { 62 | return strings.HasPrefix(s.String(), New(seq).String()) 63 | } 64 | 65 | // Replace call strings.Replace in chains 66 | func (s String) Replace(old interface{}, new interface{}, counts ...int) String { 67 | n := -1 68 | if len(counts) > 0 { 69 | n = counts[0] 70 | } 71 | return New(strings.Replace(s.String(), New(old).String(), New(new).String(), n)) 72 | } 73 | 74 | // HasSuffix call strings.HasSuffix in chains 75 | func (s String) HasSuffix(seq interface{}) bool { 76 | return strings.HasSuffix(s.String(), New(seq).String()) 77 | } 78 | 79 | // Trim call strings.Trim in chains 80 | func (s String) Trim(seq interface{}) String { 81 | return s.Ltrim(seq).Rtrim(seq) 82 | } 83 | 84 | // Ltrim call strings.Ltrim in chains 85 | func (s String) Ltrim(seq interface{}) String { 86 | return New(strings.TrimLeft(s.String(), New(seq).String())) 87 | } 88 | 89 | // Rtrim call strings.Rtrim in chains 90 | func (s String) Rtrim(seq interface{}) String { 91 | return New(strings.TrimRight(s.String(), New(seq).String())) 92 | } 93 | -------------------------------------------------------------------------------- /string/string_test.go: -------------------------------------------------------------------------------- 1 | package string 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/test" 7 | ) 8 | 9 | func TestString_Equal(t *testing.T) { 10 | test.AssertEqual(t, New(`abc`).Equal([]byte{'a', 'b', 'c'}), true) 11 | } 12 | 13 | func TestString_Contains(t *testing.T) { 14 | test.AssertEqual(t, New(`abc`).Contains(`b`), true) 15 | test.AssertEqual(t, New(`abc`).Contains(`d`), false) 16 | 17 | test.AssertEqual(t, New([]byte{'a', 'b', 'c'}).Contains(`b`), true) 18 | test.AssertEqual(t, New([]rune{'李', '二', '狗'}).Contains(`二`), true) 19 | } 20 | 21 | func TestString_Trim(t *testing.T) { 22 | test.AssertEqual(t, New(` abc `).Trim(` `).Equal(`abc`), true) 23 | test.AssertEqual(t, New(`李二狗`).Trim(`狗`).Equal(`李二`), true) 24 | } 25 | 26 | func TestString_Replace(t *testing.T) { 27 | test.AssertEqual(t, New(`aabc`).Replace(`a`, `b`).Equal(`bbbc`), true) 28 | test.AssertEqual(t, New(`aabc`).Replace(`a`, `b`, 1).Equal(`babc`), true) 29 | test.AssertEqual(t, New(`李二狗`).Replace(`二`, `三`).Equal(`李三狗`), true) 30 | } 31 | -------------------------------------------------------------------------------- /test/assert.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "testing" 4 | 5 | type Assert struct { 6 | T *testing.T 7 | } 8 | 9 | func A(t *testing.T) *Assert { 10 | return &Assert{t} 11 | } 12 | 13 | func (a *Assert) Equal(actualValue interface{}, expectValue interface{}) { 14 | assertEqualSkip(a.T, 1, actualValue, expectValue) 15 | } 16 | 17 | func (a *Assert) True(actualValue interface{}) { 18 | a.Equal(actualValue, true) 19 | } 20 | 21 | func (a *Assert) Must(condition interface{}) { 22 | a.Equal(condition, true) 23 | } 24 | -------------------------------------------------------------------------------- /test/assert_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "testing" 4 | 5 | func TestAssert_Equal(t *testing.T) { 6 | a := A(t) 7 | 8 | a.Equal(true, true) 9 | a.Equal(true, 1 == 1) 10 | 11 | a.Equal(2, 3-1) 12 | a.Equal(0, 0) 13 | a.Equal(int(0), int64(0)) 14 | 15 | a.Equal("hello", "h"+"ello") 16 | } 17 | -------------------------------------------------------------------------------- /test/assert_util.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | // Alias 12 | BeTrue = AssertTrue 13 | BeNil = AssertNil 14 | BeEqual = AssertEqual 15 | ) 16 | 17 | func AssertTrue(t *testing.T, resultValue interface{}) { 18 | AssertEqual(t, resultValue, true) 19 | } 20 | 21 | func AssertNil(t *testing.T, resultValue interface{}) { 22 | AssertEqual(t, resultValue, nil) 23 | } 24 | 25 | // assert a equals b, or show code where error 26 | func AssertEqual(t *testing.T, resultValue interface{}, expectValue interface{}) { 27 | assertEqualSkip(t, 1, resultValue, expectValue) 28 | } 29 | 30 | func assertEqualSkip(t *testing.T, skip int, resultValue interface{}, expectValue interface{}) { 31 | if isEqual(resultValue, expectValue) { 32 | return 33 | } 34 | 35 | resultValue = fmt.Sprintf("%+v", resultValue) 36 | expectValue = fmt.Sprintf("%+v", expectValue) 37 | 38 | file, line := calledBy(skip) 39 | t.Errorf( 40 | "Failure in %s:%d\nresult:(%d)\t%+v\nexpect:(%d)\t%+v\n----\n%s\n", 41 | file, line, 42 | len(resultValue.(string)), resultValue, 43 | len(expectValue.(string)), expectValue, 44 | showFile(file, line), 45 | ) 46 | } 47 | 48 | func isEqual(actualValue interface{}, expectValue interface{}) bool { 49 | if actualValue == nil || expectValue == nil { 50 | return actualValue == expectValue 51 | } 52 | 53 | switch reflect.TypeOf(expectValue).Kind() { 54 | 55 | case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array: 56 | return reflect.DeepEqual(actualValue, expectValue) 57 | 58 | default: 59 | actual := fmt.Sprintf("%v", actualValue) 60 | expect := fmt.Sprintf("%v", expectValue) 61 | return actual == expect 62 | } 63 | } 64 | 65 | func calledBy(skip int) (string, int) { 66 | _, file, line, _ := runtime.Caller(2 + skip) 67 | return file, line 68 | } 69 | -------------------------------------------------------------------------------- /test/assert_util_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAssertEqual(t *testing.T) { 8 | AssertEqual(t, true, true) 9 | AssertEqual(t, true, 1 == 1) 10 | 11 | AssertEqual(t, 2, 3-1) 12 | AssertEqual(t, 0, 0) 13 | AssertEqual(t, int(0), int64(0)) 14 | 15 | AssertEqual(t, "hello", "h"+"ello") 16 | } 17 | -------------------------------------------------------------------------------- /test/file.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | ) 8 | 9 | var fileCache map[string][]byte 10 | 11 | func init() { 12 | fileCache = make(map[string][]byte) 13 | } 14 | 15 | func readFile(filename string) ([]byte, error) { 16 | file := fileCache[filename] 17 | if file != nil && len(file) > 0 { 18 | return file, nil 19 | } 20 | 21 | file, err := ioutil.ReadFile(filename) 22 | 23 | fileCache[filename] = file 24 | 25 | return file, err 26 | } 27 | 28 | func showFile(filename string, line int) string { 29 | file, _ := readFile(filename) 30 | 31 | buf := bytes.NewBufferString(``) 32 | 33 | size := len(file) 34 | begin := line - 2 35 | end := line + 3 36 | 37 | // @todo line超过文件时 38 | found := 1 39 | for idx := 0; idx < size; idx++ { 40 | if found >= begin && found < end { 41 | buf.WriteByte(file[idx]) 42 | } 43 | 44 | if file[idx] == '\n' { 45 | found++ 46 | 47 | if found >= begin && found < end { 48 | tag := ' ' 49 | if found == line { 50 | tag = '→' 51 | } 52 | buf.WriteString(fmt.Sprintf("%2c%4d |\t", tag, found)) 53 | } 54 | 55 | if found >= end { 56 | break 57 | } 58 | } 59 | } 60 | 61 | return buf.String() 62 | } 63 | -------------------------------------------------------------------------------- /test/runner.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "testing" 4 | 5 | type TestRunner struct { 6 | *testing.T 7 | } 8 | 9 | func TR(t *testing.T) *TestRunner { 10 | return &TestRunner{ 11 | t, 12 | } 13 | } 14 | 15 | func (this *TestRunner) Add(fn func(t *Assert)) { 16 | fn(A(this.T)) 17 | } 18 | -------------------------------------------------------------------------------- /version/compare.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | var ErrOverflowSection = errors.New("Too long version sections") 11 | var ErrOverSize = errors.New("Too large version number") 12 | 13 | const ( 14 | maxSection = 4 15 | perSectionBit = 16 16 | maxPerSection = 1< 0 { 41 | return 42 | } 43 | 44 | if strings.Count(sVersion, ".") > maxSection { 45 | return v, ErrOverflowSection 46 | } 47 | 48 | svs := strings.Split(sVersion, ".") 49 | vs := make([]int, 0, len(svs)) 50 | for _, s := range svs { 51 | i, _ := strconv.Atoi(s) 52 | if i > maxPerSection { 53 | return 0, ErrOverSize 54 | } 55 | 56 | vs = append(vs, i) 57 | } 58 | v = uint64(0) 59 | 60 | for i := 0; i < len(vs); i++ { 61 | v += uint64(vs[i] << (perSectionBit * uint(maxSection-1-i))) 62 | } 63 | 64 | // fmt.Printf("%-30s%016x\n", sVersion, v) 65 | vCache[sVersion] = v 66 | 67 | return 68 | } 69 | 70 | func Compare(v1, v2 string) (result T, err error) { 71 | hash1, err := version2Int(v1) 72 | if err != nil { 73 | return 74 | } 75 | 76 | hash2, err := version2Int(v2) 77 | if err != nil { 78 | return 79 | } 80 | 81 | if hash1 > hash2 { 82 | return resultGreater, nil 83 | } 84 | 85 | if hash1 < hash2 { 86 | return resultLess, nil 87 | } 88 | 89 | if hash1 == hash2 { 90 | return resultEqual, nil 91 | } 92 | 93 | return 0, nil 94 | } 95 | 96 | func LessThan(v1, v2 string) bool { 97 | r, err := Compare(v1, v2) 98 | if err != nil { 99 | log.Println(`Error In version/compare.go#LessThan`, err) 100 | } 101 | 102 | return r == resultLess 103 | } 104 | 105 | func GreaterThan(v1, v2 string) bool { 106 | r, err := Compare(v1, v2) 107 | if err != nil { 108 | log.Println(`Error In version/compare.go#GreaterThan`, err) 109 | } 110 | 111 | return r == resultGreater 112 | } 113 | 114 | func Equal(v1, v2 string) bool { 115 | r, err := Compare(v1, v2) 116 | if err != nil { 117 | log.Println(`Error In version/compare.go#Equal`, err) 118 | } 119 | 120 | return r == resultEqual 121 | } 122 | -------------------------------------------------------------------------------- /version/compare_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/test" 7 | ) 8 | 9 | func TestVersionCompare(t *testing.T) { 10 | 11 | cases := [][]interface{}{ 12 | {"1.20.3", "2.99.99", resultLess, nil}, 13 | {"1.20.3", "2.99.99", resultLess, nil}, 14 | {"2.99.99", "1.20.3", resultGreater, nil}, 15 | {"1.20.3", "1.20.3", resultEqual, nil}, 16 | {"1.20.3", "2.99.99", resultLess, nil}, 17 | {"1.20.3", "1.20.3333", resultLess, nil}, 18 | {"1.1.1.1", "9999.9999.9999.9999", resultLess, nil}, 19 | } 20 | 21 | for _, cas := range cases { 22 | r, err := Compare(cas[0].(string), cas[1].(string)) 23 | must := r == cas[2].(T) && err == cas[3] 24 | // fmt.Println(must, r, err) 25 | test.AssertEqual(t, must, true) 26 | test.AssertEqual(t, r, cas[2].(T)) 27 | test.AssertEqual(t, err, cas[3]) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /version/helper.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func escape(s string) string { 8 | s = strings.TrimLeft(s, "Vv") 9 | s = strings.Replace(s, "-", ".", -1) 10 | 11 | return s 12 | } 13 | -------------------------------------------------------------------------------- /version/semver.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const dot = `.` 10 | 11 | type SemVer struct { 12 | Major int 13 | Minor int 14 | Patch int 15 | } 16 | 17 | //Parse 18 | func Parse(s string) (v *SemVer) { 19 | v = new(SemVer) 20 | 21 | s = escape(s) 22 | vs := strings.Split(s, dot) 23 | if len(vs) > 0 { 24 | v.Major, _ = strconv.Atoi(vs[0]) 25 | } 26 | if len(vs) > 1 { 27 | v.Minor, _ = strconv.Atoi(vs[1]) 28 | } 29 | if len(vs) > 2 { 30 | v.Patch, _ = strconv.Atoi(vs[2]) 31 | } 32 | 33 | return 34 | } 35 | 36 | func (v *SemVer) String() string { 37 | return fmt.Sprintf("v%v.%v.%v", v.Major, v.Minor, v.Patch) 38 | } 39 | 40 | func (v *SemVer) NumberString() string { 41 | return fmt.Sprintf("%v.%v.%v", v.Major, v.Minor, v.Patch) 42 | } 43 | 44 | func (v SemVer) NextMajor() *SemVer { 45 | return &SemVer{Major: v.Major + 1} 46 | } 47 | 48 | func (v SemVer) NextMinor() *SemVer { 49 | return &SemVer{Major: v.Major, Minor: v.Minor + 1} 50 | } 51 | 52 | func (v SemVer) NextPatch() *SemVer { 53 | v.Patch += 1 54 | return &v 55 | } 56 | -------------------------------------------------------------------------------- /version/semver_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/test" 7 | ) 8 | 9 | func TestSemVer_String(t *testing.T) { 10 | type fields struct { 11 | s string 12 | } 13 | tests := []struct { 14 | name string 15 | fields fields 16 | want string 17 | }{ 18 | // TODO: Add test cases. 19 | { 20 | name: `test`, 21 | fields: fields{`v1.2.3`}, 22 | want: `v1.2.3`, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | v := Parse(tt.fields.s) 28 | if got := v.String(); got != tt.want { 29 | t.Errorf("SemVer.String() = %v, want %v", got, tt.want) 30 | } 31 | }) 32 | } 33 | } 34 | 35 | func TestSemVer_Next(t *testing.T) { 36 | as := test.A(t) 37 | 38 | v := Parse(`1.2.3`) 39 | 40 | as.Equal(v.NextMajor().NumberString(), `2.0.0`) 41 | as.Equal(v.NextMinor().NumberString(), `1.3.0`) 42 | as.Equal(v.NextPatch().NumberString(), `1.2.4`) 43 | } 44 | -------------------------------------------------------------------------------- /word/case_convert.go: -------------------------------------------------------------------------------- 1 | package word 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | // CamelCase convert `a_b_c` to `aBC` 9 | func CamelCase(v string) string { 10 | buf := bytes.NewBuffer([]byte{}) 11 | length := len(v) 12 | for i := 0; i < length; i++ { 13 | if v[i] != '_' { 14 | buf.WriteByte(v[i]) 15 | } else { 16 | i++ 17 | if i > length { 18 | continue 19 | } 20 | if v[i] >= 'a' && v[i] <= 'z' { 21 | buf.WriteByte(v[i] + 'A' - 'a') 22 | } else { 23 | buf.WriteByte(v[i]) 24 | } 25 | } 26 | } 27 | 28 | return buf.String() 29 | } 30 | 31 | // UnderlineCase convert `ABC` to `a_b_c` 32 | func UnderlineCase(v string) string { 33 | buf := bytes.NewBuffer([]byte{}) 34 | length := len(v) 35 | for i := 0; i < length; i++ { 36 | if i > 0 && v[i] >= 'A' && v[i] <= 'Z' { 37 | buf.WriteByte('_') 38 | buf.WriteByte(v[i] + 'a' - 'A') 39 | } else { 40 | buf.WriteByte(v[i]) 41 | } 42 | } 43 | return buf.String() 44 | } 45 | 46 | func UpperFirst(v string) string { 47 | return strings.ToUpper(string(v[0])) + v[1:] 48 | } 49 | 50 | func LowerFirst(v string) string { 51 | return strings.ToLower(string(v[0])) + v[1:] 52 | } 53 | -------------------------------------------------------------------------------- /word/case_convert_test.go: -------------------------------------------------------------------------------- 1 | package word 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Kretech/xgo/test" 7 | ) 8 | 9 | func TestCamelCase(t *testing.T) { 10 | test.AssertEqual(t, CamelCase(`a_bc_d`), `aBcD`) 11 | } 12 | 13 | func TestUnderlineCase(t *testing.T) { 14 | test.AssertEqual(t, UnderlineCase(`helloWorld`), `hello_world`) 15 | } 16 | 17 | func TestUpperFirst(t *testing.T) { 18 | test.BeEqual(t, UpperFirst(`a`), `A`) 19 | test.BeEqual(t, UpperFirst(`ab`), `Ab`) 20 | test.BeEqual(t, UpperFirst(`_a`), `_a`) 21 | 22 | test.BeEqual(t, LowerFirst(`_a`), `_a`) 23 | test.BeEqual(t, LowerFirst(`A`), `a`) 24 | test.BeEqual(t, LowerFirst(`Ac`), `ac`) 25 | test.BeEqual(t, LowerFirst(`ac`), `ac`) 26 | } 27 | --------------------------------------------------------------------------------