package parser import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/emicklei/proto" "github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util/stringx" ) const ( AnyImport = "google/protobuf/any.proto" ) var ( enumTypeTemplate = `{{.name}} int32` enumTemplate = `const ( {{.element}} )` enumFiledTemplate = `{{.key}} {{.name}} = {{.value}}` ) type ( MessageField struct { Type string Name stringx.String } Message struct { Name stringx.String Element []*MessageField *proto.Message } Enum struct { Name stringx.String Element []*EnumField *proto.Enum } EnumField struct { Key string Value int } Proto struct { Package string Import []*Import PbSrc string ContainsAny bool Message map[string]lang.PlaceholderType Enum map[string]*Enum } Import struct { ProtoImportName string PbImportName string OriginalDir string OriginalProtoPath string OriginalPbPath string BridgeImport string exists bool //xx.proto protoName string // xx.pb.go pbName string } ) func checkImport(src string) error { r, err := os.Open(src) if err != nil { return err } defer r.Close() parser := proto.NewParser(r) parseRet, err := parser.Parse() if err != nil { return err } var base = filepath.Base(src) proto.Walk(parseRet, proto.WithImport(func(i *proto.Import) { if err != nil { return } err = fmt.Errorf("%v:%v the external proto cannot import other proto files", base, i.Position.Line) })) if err != nil { return err } return nil } func ParseImport(src string) ([]*Import, bool, error) { bridgeImportM := make(map[string]string) r, err := os.Open(src) if err != nil { return nil, false, err } defer r.Close() workDir := filepath.Dir(src) parser := proto.NewParser(r) parseRet, err := parser.Parse() if err != nil { return nil, false, err } protoImportSet := collection.NewSet() var containsAny bool proto.Walk(parseRet, proto.WithImport(func(i *proto.Import) { if i.Filename == AnyImport { containsAny = true return } protoImportSet.AddStr(i.Filename) if i.Comment != nil { lines := i.Comment.Lines for _, line := range lines { line = strings.TrimSpace(line) if !strings.HasPrefix(line, "@") { continue } line = strings.TrimPrefix(line, "@") bridgeImportM[i.Filename] = line } } })) var importList []*Import for _, item := range protoImportSet.KeysStr() { pb := strings.TrimSuffix(filepath.Base(item), filepath.Ext(item)) + ".pb.go" var pbImportName, brideImport string if v, ok := bridgeImportM[item]; ok { pbImportName = v brideImport = "M" + item + "=" + v } else { pbImportName = item } var impo = Import{ ProtoImportName: item, PbImportName: pbImportName, BridgeImport: brideImport, } protoSource := filepath.Join(workDir, item) pbSource := filepath.Join(filepath.Dir(protoSource), pb) if util.FileExists(protoSource) && util.FileExists(pbSource) { impo.OriginalProtoPath = protoSource impo.OriginalPbPath = pbSource impo.OriginalDir = filepath.Dir(protoSource) impo.exists = true impo.protoName = filepath.Base(item) impo.pbName = pb } else { return nil, false, fmt.Errorf("「%v」: import must be found in the relative directory of 「%v」", item, filepath.Base(src)) } importList = append(importList, &impo) } return importList, containsAny, nil } func parseProto(src string, messageM map[string]lang.PlaceholderType, enumM map[string]*Enum) (*Proto, error) { if !filepath.IsAbs(src) { return nil, fmt.Errorf("expected absolute path,but found: %v", src) } r, err := os.Open(src) if err != nil { return nil, err } defer r.Close() parser := proto.NewParser(r) parseRet, err := parser.Parse() if err != nil { return nil, err } // xx.proto fileBase := filepath.Base(src) var resp Proto proto.Walk(parseRet, proto.WithPackage(func(p *proto.Package) { if err != nil { return } if len(resp.Package) != 0 { err = fmt.Errorf("%v:%v duplicate package「%v」", fileBase, p.Position.Line, p.Name) } if len(p.Name) == 0 { err = errors.New("package not found") } resp.Package = p.Name }), proto.WithMessage(func(message *proto.Message) { if err != nil { return } for _, item := range message.Elements { switch item.(type) { case *proto.NormalField, *proto.MapField, *proto.Comment: continue default: err = fmt.Errorf("%v: unsupport inline declaration", fileBase) return } } name := stringx.From(message.Name) if _, ok := messageM[name.Lower()]; ok { err = fmt.Errorf("%v:%v duplicate message 「%v」", fileBase, message.Position.Line, message.Name) return } messageM[name.Lower()] = lang.Placeholder }), proto.WithEnum(func(enum *proto.Enum) { if err != nil { return } var node Enum node.Enum = enum node.Name = stringx.From(enum.Name) for _, item := range enum.Elements { v, ok := item.(*proto.EnumField) if !ok { continue } node.Element = append(node.Element, &EnumField{ Key: v.Name, Value: v.Integer, }) } if _, ok := enumM[node.Name.Lower()]; ok { err = fmt.Errorf("%v:%v duplicate enum 「%v」", fileBase, node.Position.Line, node.Name.Source()) return } lower := stringx.From(enum.Name).Lower() enumM[lower] = &node })) if err != nil { return nil, err } resp.Message = messageM resp.Enum = enumM return &resp, nil } func (e *Enum) GenEnumCode() (string, error) { var element []string for _, item := range e.Element { code, err := item.GenEnumFieldCode(e.Name.Source()) if err != nil { return "", err } element = append(element, code) } buffer, err := util.With("enum").Parse(enumTemplate).Execute(map[string]interface{}{ "element": strings.Join(element, util.NL), }) if err != nil { return "", err } return buffer.String(), nil } func (e *Enum) GenEnumTypeCode() (string, error) { buffer, err := util.With("enumAlias").Parse(enumTypeTemplate).Execute(map[string]interface{}{ "name": e.Name.Source(), }) if err != nil { return "", err } return buffer.String(), nil } func (e *EnumField) GenEnumFieldCode(parentName string) (string, error) { buffer, err := util.With("enumField").Parse(enumFiledTemplate).Execute(map[string]interface{}{ "key": e.Key, "name": parentName, "value": e.Value, }) if err != nil { return "", err } return buffer.String(), nil }