reactor rpc (#179)
* reactor rpc generation * update flag * update command * update command * update unit test * delete test file * optimize code * update doc * update gen pb * rename target dir * update mysql data type convert rule * add done flag * optimize req/reply parameter * optimize req/reply parameter * remove waste code * remove duplicate parameter * format code * format code * optimize naming * reactor rpcv2 to rpc * remove new line * format code * rename underline to snake * reactor getParentPackage * remove debug log * reactor background
This commit is contained in:
@@ -1,46 +1,170 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/emicklei/proto"
|
||||
)
|
||||
|
||||
func Transfer(proto, target string, externalImport []*Import, console console.Console) (*PbAst, error) {
|
||||
messageM := make(map[string]lang.PlaceholderType)
|
||||
enumM := make(map[string]*Enum)
|
||||
protoAst, err := parseProto(proto, messageM, enumM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range externalImport {
|
||||
err = checkImport(item.OriginalProtoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
innerAst, err := parseProto(item.OriginalProtoPath, protoAst.Message, protoAst.Enum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range innerAst.Message {
|
||||
protoAst.Message[k] = v
|
||||
}
|
||||
for k, v := range innerAst.Enum {
|
||||
protoAst.Enum[k] = v
|
||||
}
|
||||
}
|
||||
protoAst.Import = externalImport
|
||||
protoAst.PbSrc = filepath.Join(target, strings.TrimSuffix(filepath.Base(proto), ".proto")+".pb.go")
|
||||
return transfer(protoAst, console)
|
||||
type (
|
||||
defaultProtoParser struct{}
|
||||
)
|
||||
|
||||
func NewDefaultProtoParser() *defaultProtoParser {
|
||||
return &defaultProtoParser{}
|
||||
}
|
||||
|
||||
func transfer(proto *Proto, console console.Console) (*PbAst, error) {
|
||||
parser := MustNewAstParser(proto, console)
|
||||
parse, err := parser.Parse()
|
||||
func (p *defaultProtoParser) Parse(src string) (Proto, error) {
|
||||
var ret Proto
|
||||
|
||||
abs, err := filepath.Abs(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Proto{}, err
|
||||
}
|
||||
return parse, nil
|
||||
|
||||
r, err := os.Open(abs)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
parser := proto.NewParser(r)
|
||||
set, err := parser.Parse()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var serviceList []Service
|
||||
proto.Walk(
|
||||
set,
|
||||
proto.WithImport(func(i *proto.Import) {
|
||||
ret.Import = append(ret.Import, Import{Import: i})
|
||||
}),
|
||||
proto.WithMessage(func(message *proto.Message) {
|
||||
ret.Message = append(ret.Message, Message{Message: message})
|
||||
}),
|
||||
proto.WithPackage(func(p *proto.Package) {
|
||||
ret.Package = Package{Package: p}
|
||||
}),
|
||||
proto.WithService(func(service *proto.Service) {
|
||||
serv := Service{Service: service}
|
||||
elements := service.Elements
|
||||
for _, el := range elements {
|
||||
v, _ := el.(*proto.RPC)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
serv.RPC = append(serv.RPC, &RPC{RPC: v})
|
||||
}
|
||||
|
||||
serviceList = append(serviceList, serv)
|
||||
}),
|
||||
proto.WithOption(func(option *proto.Option) {
|
||||
if option.Name == "go_package" {
|
||||
ret.GoPackage = option.Constant.Source
|
||||
}
|
||||
}),
|
||||
)
|
||||
if len(serviceList) == 0 {
|
||||
return ret, errors.New("rpc service not found")
|
||||
}
|
||||
|
||||
if len(serviceList) > 1 {
|
||||
return ret, errors.New("only one service expected")
|
||||
}
|
||||
service := serviceList[0]
|
||||
name := filepath.Base(abs)
|
||||
|
||||
for _, rpc := range service.RPC {
|
||||
if strings.Contains(rpc.RequestType, ".") {
|
||||
return ret, fmt.Errorf("line %v:%v, request type must defined in %s", rpc.Position.Line, rpc.Position.Column, name)
|
||||
}
|
||||
if strings.Contains(rpc.ReturnsType, ".") {
|
||||
return ret, fmt.Errorf("line %v:%v, returns type must defined in %s", rpc.Position.Line, rpc.Position.Column, name)
|
||||
}
|
||||
}
|
||||
if len(ret.GoPackage) == 0 {
|
||||
ret.GoPackage = ret.Package.Name
|
||||
}
|
||||
ret.PbPackage = GoSanitized(filepath.Base(ret.GoPackage))
|
||||
ret.Src = abs
|
||||
ret.Name = name
|
||||
ret.Service = service
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// see google.golang.org/protobuf@v1.25.0/internal/strs/strings.go:71
|
||||
func GoSanitized(s string) string {
|
||||
// Sanitize the input to the set of valid characters,
|
||||
// which must be '_' or be in the Unicode L or N categories.
|
||||
s = strings.Map(func(r rune) rune {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
return r
|
||||
}
|
||||
return '_'
|
||||
}, s)
|
||||
|
||||
// Prepend '_' in the event of a Go keyword conflict or if
|
||||
// the identifier is invalid (does not start in the Unicode L category).
|
||||
r, _ := utf8.DecodeRuneInString(s)
|
||||
if token.Lookup(s).IsKeyword() || !unicode.IsLetter(r) {
|
||||
return "_" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// copy from github.com/golang/protobuf@v1.4.2/protoc-gen-go/generator/generator.go:2648
|
||||
func CamelCase(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
t := make([]byte, 0, 32)
|
||||
i := 0
|
||||
if s[0] == '_' {
|
||||
// Need a capital letter; drop the '_'.
|
||||
t = append(t, 'X')
|
||||
i++
|
||||
}
|
||||
// Invariant: if the next letter is lower case, it must be converted
|
||||
// to upper case.
|
||||
// That is, we process a word at a time, where words are marked by _ or
|
||||
// upper case letter. Digits are treated as words.
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
|
||||
continue // Skip the underscore in s.
|
||||
}
|
||||
if isASCIIDigit(c) {
|
||||
t = append(t, c)
|
||||
continue
|
||||
}
|
||||
// Assume we have a letter now - if not, it's a bogus identifier.
|
||||
// The next word is a sequence of characters that must start upper case.
|
||||
if isASCIILower(c) {
|
||||
c ^= ' ' // Make it a capital letter.
|
||||
}
|
||||
t = append(t, c) // Guaranteed not lower case.
|
||||
// Accept lower case sequence that follows.
|
||||
for i+1 < len(s) && isASCIILower(s[i+1]) {
|
||||
i++
|
||||
t = append(t, s[i])
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
func isASCIILower(c byte) bool {
|
||||
return 'a' <= c && c <= 'z'
|
||||
}
|
||||
|
||||
// Is c an ASCII digit?
|
||||
func isASCIIDigit(c byte) bool {
|
||||
return '0' <= c && c <= '9'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user