Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

43
app/tool/gorpc/BUILD Normal file
View File

@@ -0,0 +1,43 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_binary",
)
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/tool/gorpc/goparser:all-srcs",
"//app/tool/gorpc/input:all-srcs",
"//app/tool/gorpc/model:all-srcs",
],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/app/tool/gorpc",
tags = ["automanaged"],
deps = [
"//app/tool/gorpc/goparser:go_default_library",
"//app/tool/gorpc/input:go_default_library",
"//app/tool/gorpc/model:go_default_library",
"//vendor/golang.org/x/tools/imports:go_default_library",
],
)
go_binary(
name = "gorpc",
embed = [":go_default_library"],
)

45
app/tool/gorpc/README.MD Normal file
View File

@@ -0,0 +1,45 @@
### gorpc
#### 说明
1. 根据service 方法生成rpc client 以及rpc model层 代码
2. service方法格式暂限定为以下格式
```
func (s *Receiver) FuncName(c context.Contex,args ...interface{}) (err error) {
}
func (s *Receiver) FuncName(c context.Contex,args ...interface{}) (resp interface{},err error) {
}
```
3. args 参数应为基础类型,如 intstring,slice 等
#### 使用
1. 进入到对应目录下执行gorpc 即可生成rpc client代码生成的代码默认放在 project/rpc/client/ 目录下
2. 使用-model 参数生成rpc 参数 model ,生成的代码默认放在 project/model/ 目录下
#### exmaple
```
func (s *Service) Example(c context.Contex,i int,b string)(res *Resp,err error) {
return
}
```
以上代码片段将自动生成如下代码
```model
type ArgExample struct {
I int
B string
}
```
``` client
func (s *Service) Example(c context.Context, arg *model.ArgExample) (res *Resp, err error) {
res = new(Resp)
err = s.client.Call(c, _jury, arg, res)
return
}
```

View File

@@ -0,0 +1,27 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["goparser.go"],
importpath = "go-common/app/tool/gorpc/goparser",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/tool/gorpc/model:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,294 @@
// Package goparse contains logic for parsing Go files. Specifically it parses
// source and test files into domain model for generating tests.
package goparser
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"strings"
"go-common/app/tool/gorpc/model"
)
// ErrEmptyFile represents an empty file error.
var ErrEmptyFile = errors.New("file is empty")
// Result representats a parsed Go file.
type Result struct {
// The package name and imports of a Go file.
Header *model.Header
// All the functions and methods in a Go file.
Funcs []*model.Function
}
// Parser can parse Go files.
type Parser struct {
// The importer to resolve packages from import paths.
Importer types.Importer
}
// Parse parses a given Go file at srcPath, along any files that share the same
// package, into a domain model for generating tests.
func (p *Parser) Parse(srcPath string, files []model.Path) (*Result, error) {
b, err := p.readFile(srcPath)
if err != nil {
return nil, err
}
fset := token.NewFileSet()
f, err := p.parseFile(fset, srcPath)
if err != nil {
return nil, err
}
fs, err := p.parseFiles(fset, f, files)
if err != nil {
return nil, err
}
return &Result{
Header: &model.Header{
Comments: parseComment(f, f.Package),
Package: f.Name.String(),
Imports: parseImports(f.Imports),
Code: goCode(b, f),
},
Funcs: p.parseFunctions(fset, f, fs),
}, nil
}
func (p *Parser) readFile(srcPath string) ([]byte, error) {
b, err := ioutil.ReadFile(srcPath)
if err != nil {
return nil, fmt.Errorf("ioutil.ReadFile: %v", err)
}
if len(b) == 0 {
return nil, ErrEmptyFile
}
return b, nil
}
func (p *Parser) parseFile(fset *token.FileSet, srcPath string) (*ast.File, error) {
f, err := parser.ParseFile(fset, srcPath, nil, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("target parser.ParseFile(): %v", err)
}
return f, nil
}
func (p *Parser) parseFiles(fset *token.FileSet, f *ast.File, files []model.Path) ([]*ast.File, error) {
pkg := f.Name.String()
var fs []*ast.File
for _, file := range files {
ff, err := parser.ParseFile(fset, string(file), nil, 0)
if err != nil {
return nil, fmt.Errorf("other file parser.ParseFile: %v", err)
}
if name := ff.Name.String(); name != pkg {
continue
}
fs = append(fs, ff)
}
return fs, nil
}
func (p *Parser) parseFunctions(fset *token.FileSet, f *ast.File, fs []*ast.File) []*model.Function {
ul, el := p.parseTypes(fset, fs)
var funcs []*model.Function
for _, d := range f.Decls {
fDecl, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
funcs = append(funcs, parseFunc(fDecl, ul, el))
}
return funcs
}
func (p *Parser) parseTypes(fset *token.FileSet, fs []*ast.File) (map[string]types.Type, map[*types.Struct]ast.Expr) {
conf := &types.Config{
Importer: p.Importer,
// Adding a NO-OP error function ignores errors and performs best-effort
// type checking. https://godoc.org/golang.org/x/tools/go/types#Config
Error: func(error) {},
}
ti := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
}
// Note: conf.Check can fail, but since Info is not required data, it's ok.
conf.Check("", fset, fs, ti)
ul := make(map[string]types.Type)
el := make(map[*types.Struct]ast.Expr)
for e, t := range ti.Types {
// Collect the underlying types.
ul[t.Type.String()] = t.Type.Underlying()
// Collect structs to determine the fields of a receiver.
if v, ok := t.Type.(*types.Struct); ok {
el[v] = e
}
}
return ul, el
}
func parseComment(f *ast.File, pkgPos token.Pos) []string {
var comments []string
var count int
for _, comment := range f.Comments {
if comment.End() < pkgPos && comment != f.Doc {
for _, c := range comment.List {
count += len(c.Text) + 1 // +1 for '\n'
if count < int(c.End()) {
n := int(c.End()) - count
comments = append(comments, strings.Repeat("\n", n))
count++ // for last of '\n'
}
comments = append(comments, c.Text)
}
}
}
return comments
}
// Returns the Go code below the imports block.
func goCode(b []byte, f *ast.File) []byte {
furthestPos := f.Name.End()
for _, node := range f.Imports {
if pos := node.End(); pos > furthestPos {
furthestPos = pos
}
}
if furthestPos < token.Pos(len(b)) {
furthestPos++
}
return b[furthestPos:]
}
func parseFunc(fDecl *ast.FuncDecl, ul map[string]types.Type, el map[*types.Struct]ast.Expr) *model.Function {
f := &model.Function{
Name: fDecl.Name.String(),
IsExported: fDecl.Name.IsExported(),
Receiver: parseReceiver(fDecl.Recv, ul, el),
Parameters: parseFieldList(fDecl.Type.Params, ul),
}
fs := parseFieldList(fDecl.Type.Results, ul)
i := 0
for _, fi := range fs {
if fi.Type.String() == "error" {
f.ReturnsError = true
continue
}
fi.Index = i
f.Results = append(f.Results, fi)
i++
}
return f
}
func parseImports(imps []*ast.ImportSpec) []*model.Import {
var is []*model.Import
for _, imp := range imps {
var n string
if imp.Name != nil {
n = imp.Name.String()
}
is = append(is, &model.Import{
Name: n,
Path: imp.Path.Value,
})
}
return is
}
func parseReceiver(fl *ast.FieldList, ul map[string]types.Type, el map[*types.Struct]ast.Expr) *model.Receiver {
if fl == nil {
return nil
}
r := &model.Receiver{
Field: parseFieldList(fl, ul)[0],
}
t, ok := ul[r.Type.Value]
if !ok {
return r
}
s, ok := t.(*types.Struct)
if !ok {
return r
}
st := el[s].(*ast.StructType)
r.Fields = append(r.Fields, parseFieldList(st.Fields, ul)...)
for i, f := range r.Fields {
f.Name = s.Field(i).Name()
}
return r
}
func parseFieldList(fl *ast.FieldList, ul map[string]types.Type) []*model.Field {
if fl == nil {
return nil
}
i := 0
var fs []*model.Field
for _, f := range fl.List {
for _, pf := range parseFields(f, ul) {
pf.Index = i
fs = append(fs, pf)
i++
}
}
return fs
}
func parseFields(f *ast.Field, ul map[string]types.Type) []*model.Field {
t := parseExpr(f.Type, ul)
if len(f.Names) == 0 {
return []*model.Field{{
Type: t,
}}
}
var fs []*model.Field
for _, n := range f.Names {
fs = append(fs, &model.Field{
Name: n.Name,
Type: t,
})
}
return fs
}
func parseExpr(e ast.Expr, ul map[string]types.Type) *model.Expression {
switch v := e.(type) {
case *ast.StarExpr:
val := types.ExprString(v.X)
return &model.Expression{
Value: val,
IsStar: true,
Underlying: underlying(val, ul),
}
case *ast.Ellipsis:
exp := parseExpr(v.Elt, ul)
return &model.Expression{
Value: exp.Value,
IsStar: exp.IsStar,
IsVariadic: true,
Underlying: underlying(exp.Value, ul),
}
default:
val := types.ExprString(e)
return &model.Expression{
Value: val,
Underlying: underlying(val, ul),
IsWriter: val == "io.Writer",
}
}
}
func underlying(val string, ul map[string]types.Type) string {
if ul[val] != nil {
return ul[val].String()
}
return ""
}

View File

@@ -0,0 +1,27 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["input.go"],
importpath = "go-common/app/tool/gorpc/input",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/tool/gorpc/model:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,49 @@
package input
import (
"fmt"
"path"
"path/filepath"
"go-common/app/tool/gorpc/model"
)
// Returns all the Golang files for the given path. Ignores hidden files.
func Files(srcPath string) ([]model.Path, error) {
srcPath, err := filepath.Abs(srcPath)
if err != nil {
return nil, fmt.Errorf("filepath.Abs: %v\n", err)
}
if filepath.Ext(srcPath) == "" {
return dirFiles(srcPath)
}
return file(srcPath)
}
func dirFiles(srcPath string) ([]model.Path, error) {
ps, err := filepath.Glob(path.Join(srcPath, "*.go"))
if err != nil {
return nil, fmt.Errorf("filepath.Glob: %v\n", err)
}
var srcPaths []model.Path
for _, p := range ps {
src := model.Path(p)
if isHiddenFile(p) || src.IsTestPath() {
continue
}
srcPaths = append(srcPaths, src)
}
return srcPaths, nil
}
func file(srcPath string) ([]model.Path, error) {
src := model.Path(srcPath)
if filepath.Ext(srcPath) != ".go" || isHiddenFile(srcPath) || src.IsTestPath() {
return nil, fmt.Errorf("no Go source files found at %v", srcPath)
}
return []model.Path{src}, nil
}
func isHiddenFile(path string) bool {
return []rune(filepath.Base(path))[0] == '.'
}

192
app/tool/gorpc/main.go Normal file
View File

@@ -0,0 +1,192 @@
// A commandline tool for generating rpc code by service methods.
//
// This tool can generate rpc client code ,rpc arg model for specific Go project dir.
// Usage :
// $gorpc [options]
// Available options:
//
// -client generate rpc client code.
//
// -d specific go project service dir (default ./service/)
//
// -model generate rpc arg model code.
//
// -o specific rpc client code output file. (default ./rpc/client/client.go)
//
// -m specific rpc arg model output file. (default ./model/rpc.go)
//
// -s print code to stdout.
// Example:
// $ cd $GOPATH/relation
// $ gorpc
// such command will generate rpc client code by functions of ./service/* and write code to $GOPATH/relation/rpc/client/client.go
package main
import (
"bytes"
"flag"
"fmt"
"go/importer"
"io"
"io/ioutil"
"log"
"path"
"go-common/app/tool/gorpc/goparser"
"go-common/app/tool/gorpc/input"
"go-common/app/tool/gorpc/model"
"golang.org/x/tools/imports"
)
const (
tpl = `var (
_noRes = &struct{}{}
)
type Service struct {
client *rpc.Client2
}
func New(c *rpc.ClientConfig) (s *Service) {
s = &Service{}
s.client = rpc.NewDiscoveryCli(c)
return
}`
)
type output struct {
rpcClient *bytes.Buffer
methodStr *bytes.Buffer
methods *bytes.Buffer
model *bytes.Buffer
}
var out = &output{
rpcClient: new(bytes.Buffer),
methodStr: new(bytes.Buffer),
methods: new(bytes.Buffer),
model: new(bytes.Buffer),
}
var (
dir = flag.String("d", "./service/", "source code dir")
argModel = flag.Bool("model", false, "use -model to generate rpc arg model")
client = flag.Bool("client", true, "use -client to generate rpc client code")
clientfile = flag.String("o", "./rpc/client/client.go", "out file name")
modelfile = flag.String("m", "./model/rpc.go", "generate rpc arg model")
std = flag.Bool("s", false, "use -s to print code to stdout")
)
func main() {
flag.Parse()
p := &goparser.Parser{Importer: importer.Default()}
files, err := input.Files(path.Dir(*dir))
if err != nil {
panic(err)
}
if *argModel {
out.model.WriteString("package model\n")
}
for _, f := range files {
rs, err := p.Parse(string(f), files)
if err != nil {
panic(err)
}
for _, f := range rs.Funcs {
if f.IsExported && len(f.Results) <= 1 && f.Receiver != nil && f.ReturnsError {
if *client {
out.generateMeStr(f)
out.generateMethod(f)
}
if *argModel {
out.generateModel(f)
}
}
}
}
if *argModel {
m, err := imports.Process("model.go", out.model.Bytes(), &imports.Options{TabWidth: 4})
if err != nil {
log.Printf("gopimorts err %v", err)
return
}
if *std {
fmt.Printf("%s", m)
} else {
ioutil.WriteFile(*modelfile, m, 0666)
}
}
if *client {
out.rpcClient.WriteString("package client \n")
out.rpcClient.WriteString(tpl)
out.rpcClient.WriteString("\nconst ( \n")
io.Copy(out.rpcClient, out.methodStr)
out.rpcClient.WriteString("\n)\n")
io.Copy(out.rpcClient, out.methods)
c, err := imports.Process("client.go", out.rpcClient.Bytes(), &imports.Options{TabWidth: 4})
if err != nil {
log.Printf("gopimorts err %v", err)
return
}
if *std {
fmt.Printf("%s", c)
} else {
ioutil.WriteFile(*clientfile, c, 0666)
}
}
}
func (o *output) generateMeStr(f *model.Function) {
b := o.methodStr
fmt.Fprintf(b, "_%s = \"RPC.%s\"\n", f.Name, f.Name)
}
func (o *output) generateModel(f *model.Function) {
b := o.model
for _, p := range f.Parameters {
if !p.IsBasicType() {
if p.Type.String() == "context.Context" {
continue
}
return
}
}
fmt.Fprintf(b, fmt.Sprintf("type Arg%s struct{\n", f.Name))
for _, p := range f.Parameters {
fmt.Fprintln(b, string(bytes.ToUpper([]byte(p.Name))), p.Type)
}
fmt.Fprintln(b, "}")
}
func (o *output) generateMethod(f *model.Function) {
b := o.methods
name := f.Name
for _, p := range f.Parameters {
if !p.IsBasicType() {
if p.Type.String() == "context.Context" {
continue
}
return
}
}
fmt.Fprintf(b, "func(s %s)%s(", f.Receiver.Type, f.Name)
fmt.Fprintf(b, "c context.Context, arg *model.Arg%s)(", f.Name)
if f.ReturnsError {
if len(f.Results) > 0 {
fmt.Fprintf(b, "res %s", f.Results[0].Type)
fmt.Fprint(b, ",")
}
fmt.Fprintf(b, "err error)")
} else {
fmt.Fprint(b, ")")
}
fmt.Fprint(b, " {\n")
if len(f.Results) == 0 {
fmt.Fprintf(b, "err = s.client.Call(c, _%s, arg, &_noRes)", name)
} else if f.Results[0].Type.IsStar {
fmt.Fprintf(b, "res = new(%s)\n", f.Results[0].Type.String()[1:])
fmt.Fprintf(b, "err = s.client.Call(c, _%s, arg, res)", name)
} else {
fmt.Fprintf(b, "err = s.client.Call(c, _%s, arg, &res)", name)
}
fmt.Fprintf(b, "\nreturn\n}\n")
}

View File

@@ -0,0 +1,26 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["models.go"],
importpath = "go-common/app/tool/gorpc/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,166 @@
package model
import (
"strings"
"unicode"
)
type Expression struct {
Value string
IsStar bool
IsVariadic bool
IsWriter bool
Underlying string
}
func (e *Expression) String() string {
value := e.Value
if e.IsStar {
value = "*" + value
}
if e.IsVariadic {
return "[]" + value
}
return value
}
type Field struct {
Name string
Type *Expression
Index int
}
func (f *Field) IsWriter() bool {
return f.Type.IsWriter
}
func (f *Field) IsStruct() bool {
return strings.HasPrefix(f.Type.Underlying, "struct")
}
func (f *Field) IsBasicType() bool {
return isBasicType(f.Type.String()) || isBasicType(f.Type.Underlying)
}
func isBasicType(t string) bool {
switch t {
case "bool", "string", "int", "int8", "int16", "int32", "int64", "uint",
"uint8", "uint16", "uint32", "uint64", "uintptr", "byte", "rune",
"float32", "float64", "complex64", "complex128":
return true
default:
return false
}
}
func (f *Field) IsNamed() bool {
return f.Name != "" && f.Name != "_"
}
func (f *Field) ShortName() string {
return strings.ToLower(string([]rune(f.Type.Value)[0]))
}
type Receiver struct {
*Field
Fields []*Field
}
type Function struct {
Name string
IsExported bool
Receiver *Receiver
Parameters []*Field
Results []*Field
ReturnsError bool
}
func (f *Function) TestParameters() []*Field {
var ps []*Field
for _, p := range f.Parameters {
if p.IsWriter() {
continue
}
ps = append(ps, p)
}
return ps
}
func (f *Function) TestResults() []*Field {
var ps []*Field
ps = append(ps, f.Results...)
for _, p := range f.Parameters {
if !p.IsWriter() {
continue
}
ps = append(ps, &Field{
Name: p.Name,
Type: &Expression{
Value: "string",
IsWriter: true,
Underlying: "string",
},
Index: len(ps),
})
}
return ps
}
func (f *Function) ReturnsMultiple() bool {
return len(f.Results) > 1
}
func (f *Function) OnlyReturnsOneValue() bool {
return len(f.Results) == 1 && !f.ReturnsError
}
func (f *Function) OnlyReturnsError() bool {
return len(f.Results) == 0 && f.ReturnsError
}
func (f *Function) FullName() string {
var r string
if f.Receiver != nil {
r = f.Receiver.Type.Value
}
return strings.Title(r) + strings.Title(f.Name)
}
func (f *Function) TestName() string {
if f.Receiver != nil {
receiverType := f.Receiver.Type.Value
if unicode.IsLower([]rune(receiverType)[0]) {
receiverType = "_" + receiverType
}
return "Test" + receiverType + "_" + f.Name
}
if unicode.IsLower([]rune(f.Name)[0]) {
return "Test_" + f.Name
}
return "Test" + f.Name
}
func (f *Function) IsNaked() bool {
return f.Receiver == nil && len(f.Parameters) == 0 && len(f.Results) == 0
}
type Import struct {
Name, Path string
}
type Header struct {
Comments []string
Package string
Imports []*Import
Code []byte
}
type Path string
func (p Path) TestPath() string {
return strings.TrimSuffix(string(p), ".go") + "_test.go"
}
func (p Path) IsTestPath() bool {
return strings.HasSuffix(string(p), "_test.go")
}