feat: finish

This commit is contained in:
Young Xu 2023-03-12 23:56:26 +08:00
parent 91a5eb75b8
commit a51a9fe3bf
Signed by: xuthus5
GPG Key ID: A23CF9620CBB55F9
7 changed files with 272 additions and 5 deletions

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# protoc-gen-coco
## install
```shell
go install gitter.top/coco/protoc-gen-coco@latest
```
## usage
```shell
protoc example.proto --coco_out=. --go_out=.
```

View File

@ -130,3 +130,23 @@ func GetCommentApiURL(method *protogen.Method) string {
}
return "/" + CamelCaseToJavascriptCase(method.GoName)
}
func GetRPCInfoList(services *protogen.Service) []*rpcInfo {
if len(services.Methods) == 0 {
return nil
}
var list []*rpcInfo
for _, method := range services.Methods {
node := &rpcInfo{
FuncName: method.GoName,
RouterPath: GetCommentApiURL(method),
Method: GetCommentHttpMethod(method.Comments),
Author: GetCommentAuthor(method.Comments),
Describe: GetCommentDescribe(method.Comments),
ReqName: method.Input.GoIdent.GoName,
RespName: method.Output.GoIdent.GoName,
}
list = append(list, node)
}
return list
}

View File

@ -6,7 +6,11 @@ option go_package = "./;main";
// @route_group: true
// @base_url: /api/v1
// @gen_to: ./example_controller.go
service ExampleService { // tail
// @author: xuthus
// @desc: test rpc
// @method: GET
rpc ExampleCall1(ExampleMessage1) returns(ReturnType) {}
rpc ExampleCall2(ExampleMessage2) returns(ReturnType) {}
}

1
go.mod
View File

@ -25,6 +25,7 @@ require (
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
gitter.top/coco/goast v0.0.0-20230318151709-875a73a463aa // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect

6
go.sum
View File

@ -59,6 +59,12 @@ github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
gitter.top/coco/coco v0.0.0-20230312134419-0dd59eb0e955 h1:dLnS34ByD6KgTiUHBPQSqhvh1yk9pR10P3So/Gz9Zo0=
gitter.top/coco/coco v0.0.0-20230312134419-0dd59eb0e955/go.mod h1:IptVJiCXf87Xywfw26zrvXQjTjF8ZsIHUTJ8yPWSpWE=
gitter.top/coco/goast v0.0.0-20230303150326-1f614fadb952 h1:6nsHIjVKo9vHEG3sIIZcVv/1o9VZOxkgZvsGDB2zsag=
gitter.top/coco/goast v0.0.0-20230303150326-1f614fadb952/go.mod h1:irSK6CvEKqxRIu45LbT1GTXvwu9D94h51hK9bR6Zcoo=
gitter.top/coco/goast v0.0.0-20230318150308-b74a5040b338 h1:JWoLRyJRupqsbb/HEqPuhhoFrfwVThZD5trIgU4EMeQ=
gitter.top/coco/goast v0.0.0-20230318150308-b74a5040b338/go.mod h1:irSK6CvEKqxRIu45LbT1GTXvwu9D94h51hK9bR6Zcoo=
gitter.top/coco/goast v0.0.0-20230318151709-875a73a463aa h1:qSlQttmvI3fRWJUdZ5Ra5Prg+5OCfzrn/BIN8q+lItE=
gitter.top/coco/goast v0.0.0-20230318151709-875a73a463aa/go.mod h1:irSK6CvEKqxRIu45LbT1GTXvwu9D94h51hK9bR6Zcoo=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=

40
main.go
View File

@ -13,12 +13,18 @@ func (c *Coco) Generate(plugin *protogen.Plugin) error {
if len(plugin.Files) == 0 {
return nil
}
c.generateRouterMap(plugin)
c.generateRouterImpl(plugin)
return nil
}
func (c *Coco) generateRouterMap(plugin *protogen.Plugin) {
for _, pbFile := range plugin.Files {
// service empty
if len(pbFile.Services) == 0 {
continue
}
// write file header
filename := fmt.Sprintf("autogen_router_%s.go", pbFile.GeneratedFilenamePrefix)
g := plugin.NewGeneratedFile(filename, pbFile.GoImportPath)
@ -32,7 +38,7 @@ func (c *Coco) Generate(plugin *protogen.Plugin) error {
g.P()
for _, service := range pbFile.Services {
// service router group
// is service router group
if !IsCommentRouterGroup(pbFile.Services[0].Comments) {
continue
}
@ -47,9 +53,35 @@ func (c *Coco) Generate(plugin *protogen.Plugin) error {
g.P(values)
g.P()
}
}
return nil
}
func (c *Coco) generateRouterImpl(plugin *protogen.Plugin) {
for _, pbFile := range plugin.Files {
// service empty
if len(pbFile.Services) == 0 {
continue
}
for _, service := range pbFile.Services {
// is service router group
if !IsCommentRouterGroup(pbFile.Services[0].Comments) {
continue
}
genTo := GetCommentGenerateTo(service.Comments)
generator := newRouterImpl(genTo, string(pbFile.GoPackageName), service.GoName, service)
if !generator.IsFileExist() {
if err := generator.generateNewFile(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "generate all router failed: %v\n", err)
}
return
}
if err := generator.generateNewRouters(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "generate router impl failed: %v\n", err)
}
}
}
}
func main() {

View File

@ -2,8 +2,13 @@ package main
import (
"bytes"
"errors"
"fmt"
"gitter.top/coco/goast"
"google.golang.org/protobuf/compiler/protogen"
"os"
"path/filepath"
"strings"
"text/template"
)
@ -28,7 +33,7 @@ const routerGenerateTpl = `type AutoGen{{.StructName}}Impl interface { ` + `{{ra
{{.MethodName}}(ctx *core.Context, req *{{.InputName}}) (resp *{{.OutputName}}, err error){{end}}
}
var (
AutoGen{{.StructName}} = &core.Routers{
AutoGen{{.StructName}}RouterMap = &core.Routers{
BaseURL: "{{.BaseURL}}",
Apis: core.RouterMap{ {{range .Methods}}
"{{.MethodName}}": {
@ -73,3 +78,189 @@ func GenerateRouterMap(srv *protogen.Service) (string, error) {
return buf.String(), nil
}
type rpcInfo struct {
FuncName string // 函数名
RouterPath string // 路由路径
Method string // 请求类型 POST/GET...
Author string // 接口作者
Describe string // 描述
ReqName string // request name
RespName string // response name
}
type routerImpl struct {
Filename string
PkgName string
SrvName string
ModelName string // protobuf go_out package name
Routers []*rpcInfo
}
func newRouterImpl(filename, modelName, srvName string, service *protogen.Service) *routerImpl {
if filename == "" {
filename = "./"
}
pkgName := filepath.Base(filepath.Dir(filename))
if pkgName == "" {
realName, _ := os.Getwd()
pkgName = fixPkgName(filepath.Base(realName))
}
return &routerImpl{
ModelName: modelName,
Filename: filename,
SrvName: srvName,
PkgName: pkgName,
Routers: GetRPCInfoList(service),
}
}
// fixPkgName 修正不规范的包名
func fixPkgName(name string) string {
name = strings.ReplaceAll(name, "-", "_")
name = strings.ReplaceAll(name, ".", "_")
return name
}
func (ri *routerImpl) IsFileExist() bool {
_, err := os.Stat(ri.Filename)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return false
} else {
_, _ = fmt.Fprintf(os.Stderr, "get file stat failed: %v", err)
}
return false
}
return true
}
func (ri *routerImpl) GetFileInfo(filename string) (os.FileInfo, error) {
return os.Stat(filename)
}
const CompleteRouteGenerateAndPackageTpl = `package {{.PkgName}}
import "gitter.top/coco/coco/core"
type {{.SrvName}} struct {}
// IDE: {{.SrvName}} implemented {{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl interface
var _ AutoGen{{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl = (*{{.SrvName}})(nil)
{{range $route := .Routers}}
// {{$route.FuncName}} {{$route.Describe}}
func (receiver *{{$.SrvName}}) {{$route.FuncName}}(ctx *core.Context, req *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.ReqName}}) (resp *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}}, err error) {
resp = new({{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}})
// TODO impl...
return resp, nil
}
{{end}}
`
func (ri *routerImpl) generateNewFile() error {
t, err := template.New("generate_all_file").Parse(CompleteRouteGenerateAndPackageTpl)
if err != nil {
return err
}
t.DefinedTemplates()
var buf bytes.Buffer
if err = t.Execute(&buf, ri); err != nil {
return err
}
f, err := os.OpenFile(ri.Filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
if _, err = f.Write(buf.Bytes()); err != nil {
return err
}
return nil
}
func (ri *routerImpl) generateNewRouters() error {
info, err := ri.GetFileInfo(ri.Filename)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
parser, err := goast.NewParser(ri.Filename)
if err != nil {
return err
}
definer, err := parser.Parse()
if err != nil {
return err
}
if !definer.ExistType(ri.SrvName) {
tpl := `
type {{.SrvName}} struct {}
// IDE: {{.SrvName}} implemented {{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl interface
var _ AutoGen{{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl = (*{{.SrvName}})(nil)
`
t, err := template.New("generate_type").Parse(tpl)
if err != nil {
return err
}
t.DefinedTemplates()
var buf bytes.Buffer
if err = t.Execute(&buf, ri); err != nil {
return err
}
f, err := os.OpenFile(ri.Filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
if _, err = f.Write(buf.Bytes()); err != nil {
return err
}
}
var needImpls []*rpcInfo
for _, router := range ri.Routers {
if !definer.ExistMethod(fmt.Sprintf("%s.%s", ri.SrvName, router.FuncName)) {
needImpls = append(needImpls, router)
}
}
if len(needImpls) != 0 {
tpl := `{{range $route := .Routers}}
// {{$route.FuncName}} {{$route.Describe}}
func (receiver *{{$.SrvName}}) {{$route.FuncName}}(ctx *core.Context, req *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.ReqName}}) (resp *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}}, err error) {
resp = new({{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}})
// TODO impl...
return resp, nil
}
{{end}}`
ri.Routers = needImpls
t, err := template.New("generate_impls").Parse(tpl)
if err != nil {
return err
}
t.DefinedTemplates()
var buf bytes.Buffer
if err = t.Execute(&buf, ri); err != nil {
return err
}
f, err := os.OpenFile(ri.Filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
if _, err = f.Write(buf.Bytes()); err != nil {
return err
}
}
return nil
}