Compare commits

...

4 Commits

Author SHA1 Message Date
01e8e2905e
fix: create file protobuf content 2024-03-22 00:07:09 +08:00
d0a43019bc
fix: receive method to function 2024-03-20 23:45:19 +08:00
06d0f1cec5
feat: new function for create file 2024-03-20 23:36:09 +08:00
83cdb3f6bb
release: new version 2024-03-19 23:38:00 +08:00
9 changed files with 895 additions and 42 deletions

27
README.md Normal file
View File

@ -0,0 +1,27 @@
## gobuf
一个 protobuf 文件解析工具
## 用法
```go
package main
import "gitter.top/common/gobuf"
func main() {
parser, err := gobuf.NewParser("example.proto")
if err != nil {
// do something
}
// 是否存在Service User
parser.ExistService("User")
// 是否存在Message UserAddResp
parser.ExistMessage("UserAddResp")
// 是否存在RPC User.Add
parser.ExistRPC("User", "Add")
// 添加一个RPC Update
parser.AddRPC("User", "Update")
// 添加一个Service Member
parser.AddService("Member")
```

404
example.pb.go Normal file
View File

@ -0,0 +1,404 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.21.5
// source: example.proto
package gobuf
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ListReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListReq) Reset() {
*x = ListReq{}
if protoimpl.UnsafeEnabled {
mi := &file_example_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListReq) ProtoMessage() {}
func (x *ListReq) ProtoReflect() protoreflect.Message {
mi := &file_example_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListReq.ProtoReflect.Descriptor instead.
func (*ListReq) Descriptor() ([]byte, []int) {
return file_example_proto_rawDescGZIP(), []int{0}
}
type ListResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Items []*ListResp_Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty" pson:"items" bson:"items"` // 列表
}
func (x *ListResp) Reset() {
*x = ListResp{}
if protoimpl.UnsafeEnabled {
mi := &file_example_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListResp) ProtoMessage() {}
func (x *ListResp) ProtoReflect() protoreflect.Message {
mi := &file_example_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListResp.ProtoReflect.Descriptor instead.
func (*ListResp) Descriptor() ([]byte, []int) {
return file_example_proto_rawDescGZIP(), []int{1}
}
func (x *ListResp) GetItems() []*ListResp_Item {
if x != nil {
return x.Items
}
return nil
}
type DownloadReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// @v: required
F string `protobuf:"bytes,1,opt,name=f,proto3" json:"f,omitempty" pson:"f" bson:"f"` // 文件地址
}
func (x *DownloadReq) Reset() {
*x = DownloadReq{}
if protoimpl.UnsafeEnabled {
mi := &file_example_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DownloadReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DownloadReq) ProtoMessage() {}
func (x *DownloadReq) ProtoReflect() protoreflect.Message {
mi := &file_example_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DownloadReq.ProtoReflect.Descriptor instead.
func (*DownloadReq) Descriptor() ([]byte, []int) {
return file_example_proto_rawDescGZIP(), []int{2}
}
func (x *DownloadReq) GetF() string {
if x != nil {
return x.F
}
return ""
}
type DownloadResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DownloadResp) Reset() {
*x = DownloadResp{}
if protoimpl.UnsafeEnabled {
mi := &file_example_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DownloadResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DownloadResp) ProtoMessage() {}
func (x *DownloadResp) ProtoReflect() protoreflect.Message {
mi := &file_example_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DownloadResp.ProtoReflect.Descriptor instead.
func (*DownloadResp) Descriptor() ([]byte, []int) {
return file_example_proto_rawDescGZIP(), []int{3}
}
type ListResp_Item struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Filename string `protobuf:"bytes,1,opt,name=filename,proto3" json:"filename,omitempty" pson:"filename" bson:"filename"` // 文件名
FileSize string `protobuf:"bytes,2,opt,name=file_size,json=fileSize,proto3" json:"file_size,omitempty" bson:"file_size" pson:"fileSize"` // 文件大小
CreatedAt string `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" pson:"createdAt" bson:"created_at"` // 上传时间
}
func (x *ListResp_Item) Reset() {
*x = ListResp_Item{}
if protoimpl.UnsafeEnabled {
mi := &file_example_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListResp_Item) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListResp_Item) ProtoMessage() {}
func (x *ListResp_Item) ProtoReflect() protoreflect.Message {
mi := &file_example_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListResp_Item.ProtoReflect.Descriptor instead.
func (*ListResp_Item) Descriptor() ([]byte, []int) {
return file_example_proto_rawDescGZIP(), []int{1, 0}
}
func (x *ListResp_Item) GetFilename() string {
if x != nil {
return x.Filename
}
return ""
}
func (x *ListResp_Item) GetFileSize() string {
if x != nil {
return x.FileSize
}
return ""
}
func (x *ListResp_Item) GetCreatedAt() string {
if x != nil {
return x.CreatedAt
}
return ""
}
var File_example_proto protoreflect.FileDescriptor
var file_example_proto_rawDesc = []byte{
0x0a, 0x0d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x22, 0x09, 0x0a, 0x07, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x22, 0x9b, 0x01, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x65, 0x73, 0x70, 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69,
0x74, 0x65, 0x6d, 0x73, 0x1a, 0x5e, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08,
0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65,
0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c,
0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x41, 0x74, 0x22, 0x1b, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64,
0x52, 0x65, 0x71, 0x12, 0x0c, 0x0a, 0x01, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01,
0x66, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73,
0x70, 0x32, 0x78, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, 0x69, 0x73,
0x74, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3d, 0x0a, 0x08,
0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65,
0x71, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x44,
0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x42, 0x0a, 0x5a, 0x08, 0x2e,
0x2f, 0x3b, 0x67, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_example_proto_rawDescOnce sync.Once
file_example_proto_rawDescData = file_example_proto_rawDesc
)
func file_example_proto_rawDescGZIP() []byte {
file_example_proto_rawDescOnce.Do(func() {
file_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_proto_rawDescData)
})
return file_example_proto_rawDescData
}
var file_example_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_example_proto_goTypes = []interface{}{
(*ListReq)(nil), // 0: controller.ListReq
(*ListResp)(nil), // 1: controller.ListResp
(*DownloadReq)(nil), // 2: controller.DownloadReq
(*DownloadResp)(nil), // 3: controller.DownloadResp
(*ListResp_Item)(nil), // 4: controller.ListResp.Item
}
var file_example_proto_depIdxs = []int32{
4, // 0: controller.ListResp.items:type_name -> controller.ListResp.Item
0, // 1: controller.File.List:input_type -> controller.ListReq
2, // 2: controller.File.Download:input_type -> controller.DownloadReq
1, // 3: controller.File.List:output_type -> controller.ListResp
3, // 4: controller.File.Download:output_type -> controller.DownloadResp
3, // [3:5] is the sub-list for method output_type
1, // [1:3] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_example_proto_init() }
func file_example_proto_init() {
if File_example_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_example_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DownloadReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_example_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DownloadResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_example_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListResp_Item); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_example_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_example_proto_goTypes,
DependencyIndexes: file_example_proto_depIdxs,
MessageInfos: file_example_proto_msgTypes,
}.Build()
File_example_proto = out.File
file_example_proto_rawDesc = nil
file_example_proto_goTypes = nil
file_example_proto_depIdxs = nil
}

View File

@ -2,7 +2,7 @@ syntax = "proto3";
package controller;
option go_package = "./;core";
option go_package = "./;gobuf";
// @route_group: true
@ -14,26 +14,11 @@ service File {
// @method: GET
// @api: /list
rpc List (ListReq) returns (ListResp);
// @desc:
// @author: Young Xu
// @method: POST
// @api: /upload
rpc Upload (UploadReq) returns (UploadResp);
// @desc:
// @author: Young Xu
// @method: POST
// @api: /delete
rpc Delete (DeleteReq) returns (DeleteResp);
// @desc:
// @author: Young Xu
// @method: GET
// @api: /download
rpc Download (DownloadReq) returns (DownloadResp);
// @desc:
// @author:
// @method:
// @api: /update_user_info
rpc UpdateUserInfo (UpdateUserInfoReq) returns (UpdateUserInfoResp);
}
@ -48,23 +33,9 @@ message ListResp {
repeated Item items = 1; //
}
message UploadReq {}
message UploadResp {}
message DeleteReq {
string filename = 1; //
}
message DeleteResp {}
message DownloadReq {
// @v: required
string f = 1; //
}
message DownloadResp {}
message UpdateUserInfoReq {}
message UpdateUserInfoResp {}

7
go.mod
View File

@ -1,10 +1,11 @@
module gitter.top/coco/gobuf
module gitter.top/common/gobuf
go 1.18
require (
github.com/emicklei/proto v1.11.1
github.com/stretchr/testify v1.8.2
github.com/emicklei/proto v1.13.2
github.com/stretchr/testify v1.9.0
google.golang.org/protobuf v1.33.0
)
require (

View File

@ -2,10 +2,13 @@ package gobuf
import (
"bytes"
"github.com/emicklei/proto"
"gitter.top/sync/proto-contrib/pkg/protofmt"
"fmt"
"io"
"os"
"path/filepath"
"github.com/emicklei/proto"
"gitter.top/sync/proto-contrib/pkg/protofmt"
)
type Parser struct {
@ -33,6 +36,31 @@ func NewParser(file string) (*Parser, error) {
}, nil
}
func CreateFile(filename string) error {
// 检查文件是否存在
if _, err := os.Stat(filename); err == nil {
return nil
} else if !os.IsNotExist(err) {
return err
}
// 确保文件所在的目录存在
baseDir := filepath.Dir(filename)
if baseDir != "." && baseDir != ".." {
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return err
}
}
// 写入基础的protobuf内容此步骤会创建文件
baseContent := []byte("syntax = \"proto3\";\n\npackage proto.v1;\n\noption go_package = \"projects/gen;genv1\";\n")
if err := os.WriteFile(filename, baseContent, os.ModePerm); err != nil {
return err
}
return nil
}
func (parser *Parser) ExistService(serviceName string) bool {
var result bool
proto.Walk(parser.proto, proto.WithService(func(service *proto.Service) {
@ -79,25 +107,46 @@ func (parser *Parser) AddRPC(serviceName, rpcName string) error {
service.Elements = append(service.Elements, &proto.RPC{
Comment: &proto.Comment{
Lines: []string{
" @desc: ",
" @author: ",
" @method: ",
" @desc: 描述",
" @author: 作者",
" @method: GET",
" @api: /" + calm2Case(rpcName),
},
},
Name: rpcName,
RequestType: rpcName + "Req",
ReturnsType: rpcName + "Resp",
RequestType: toUpperCamlCase(serviceName) + toUpperCamlCase(rpcName) + "Req",
ReturnsType: toUpperCamlCase(serviceName) + toUpperCamlCase(rpcName) + "Resp",
Parent: service,
})
}))
parser.proto.Elements = append(parser.proto.Elements, &proto.Message{
Name: rpcName + "Req",
Name: toUpperCamlCase(serviceName) + toUpperCamlCase(rpcName) + "Req",
Parent: parser.proto,
})
parser.proto.Elements = append(parser.proto.Elements, &proto.Message{
Name: rpcName + "Resp",
Name: toUpperCamlCase(serviceName) + toUpperCamlCase(rpcName) + "Resp",
Parent: parser.proto,
})
return parser.writeSync()
}
func (parser *Parser) AddService(serviceName string) error {
if parser.ExistService(serviceName) {
return fmt.Errorf("service name exist")
}
parser.proto.Elements = append(parser.proto.Elements, &proto.Service{
Comment: &proto.Comment{
Lines: []string{
" @route_group: true",
" @base_url: /v1/" + calm2Case(serviceName),
" @gen_to: ./services/controller/v1/" + calm2Case(serviceName) + "_controller.go",
" @rpc_to: ./services/microservice/v1/" + calm2Case(serviceName) + "_service.go",
},
},
Name: serviceName,
Parent: parser.proto,
})

View File

@ -20,3 +20,12 @@ func TestParser_AddRPC(t *testing.T) {
err = parser.AddRPC("File", "UpdateUserInfo")
assert.Nil(t, err)
}
func TestParser_AddService(t *testing.T) {
parser, err := NewParser("example.proto")
assert.Nil(t, err)
err = parser.AddService("UserInfo")
assert.Nil(t, err)
err = parser.AddRPC("UserInfo", "Detail")
assert.Nil(t, err)
}

350
inject_tag.go Normal file
View File

@ -0,0 +1,350 @@
package gobuf
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"unicode"
)
var (
rComment = regexp.MustCompile(`^//.*?@(?i:gotags?):\s*(.*)$`)
bsonComment = regexp.MustCompile(`^//.*?@(?i:bson?):\s*(.*)$`)
jsonComment = regexp.MustCompile(`^//.*?@(?i:json?):\s*(.*)$`)
rInject = regexp.MustCompile("`.+`$")
rTags = regexp.MustCompile(`[\w_]+:"[^"]+"`)
)
type textArea struct {
Start int
End int
CurrentTag string
InjectTag string
CommentStart int
CommentEnd int
}
type TagValueStyle int
const (
Underline TagValueStyle = iota
LowerCase
UpperCase
)
type InjectTagProps struct {
TagName string // tag name
Style TagValueStyle // tag value style, default underline
comment *ast.Comment
fieldName string
fieldValue string
}
type InjectTag struct {
inputFiles string
defaultTags map[string]TagValueStyle
}
func NewInjectTag(pbFiles string) *InjectTag {
return &InjectTag{inputFiles: pbFiles}
}
func (it *InjectTag) Inject() error {
globResults, err := filepath.Glob(it.inputFiles)
if err != nil {
return fmt.Errorf("parser input file failed: %v", err)
}
var matched int
for _, path := range globResults {
fileInfo, err := os.Stat(path)
if err != nil {
return fmt.Errorf("read file stat failed: %v", err)
}
if fileInfo.IsDir() {
continue
}
// It should end with ".go" at a minimum.
if !strings.HasSuffix(strings.ToLower(fileInfo.Name()), ".go") {
continue
}
matched++
areas, err := it.parserFile(path)
if err != nil {
log.Fatal(err)
}
if err = it.writeFile(path, areas); err != nil {
log.Fatal(err)
}
}
if matched == 0 {
return fmt.Errorf("input %q matched no files, see: -help", it.inputFiles)
}
return nil
}
func (it *InjectTag) WithTags(tags ...InjectTagProps) *InjectTag {
if len(it.defaultTags) == 0 {
it.defaultTags = make(map[string]TagValueStyle)
}
for _, tag := range tags {
it.defaultTags[tag.TagName] = tag.Style
}
return it
}
func (it *InjectTag) parserFile(inputPath string) (areas []textArea, err error) {
it.logf("parsing file %q for inject tag comments", inputPath)
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, inputPath, nil, parser.ParseComments)
if err != nil {
return
}
for _, decl := range f.Decls {
// check if is generic declaration
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
var typeSpec *ast.TypeSpec
for _, spec := range genDecl.Specs {
if ts, tsOK := spec.(*ast.TypeSpec); tsOK {
typeSpec = ts
break
}
}
// skip if can't get type spec
if typeSpec == nil {
continue
}
// not a struct, skip
structDecl, ok := typeSpec.Type.(*ast.StructType)
if !ok {
continue
}
for _, field := range structDecl.Fields.List {
// skip if field name abnormal
if len(field.Names) != 1 {
continue
}
fieldName := field.Names[0].Name
if unicode.IsLower(rune(fieldName[0])) {
continue
}
// skip if field has no doc
var comments []*ast.Comment
if field.Doc != nil {
comments = append(comments, field.Doc.List...)
}
// The "doc" field (above comment) is more commonly "free-form"
// due to the ability to have a much larger comment without it
// being unwieldy. As such, the "comment" field (trailing comment),
// should take precedence if there happen to be multiple tags
// specified, both in the field doc, and the field line. Whichever
// comes last, will take precedence.
if field.Comment != nil {
comments = append(comments, field.Comment.List...)
}
tags := it.tagFromComment(field.Names[0].Name, comments)
if len(tags) == 0 {
continue
}
currentTag := field.Tag.Value
for _, tag := range tags {
area := textArea{
Start: int(field.Pos()),
End: int(field.End()),
CurrentTag: currentTag[1 : len(currentTag)-1],
InjectTag: tag.fieldValue,
}
if tag.comment != nil {
area.CommentStart = int(tag.comment.Pos())
area.CommentEnd = int(tag.comment.End())
}
areas = append(areas, area)
}
}
}
//it.logf("parsed file %q, number of fields to inject custom tags: %d", inputPath, len(areas))
return
}
func (it *InjectTag) writeFile(inputPath string, areas []textArea) error {
f, err := os.Open(inputPath)
if err != nil {
return err
}
contents, err := io.ReadAll(f)
if err != nil {
return err
}
if err = f.Close(); err != nil {
return err
}
// inject custom tags from tail of file first to preserve order
for i := range areas {
area := areas[len(areas)-i-1]
it.logf("inject custom tag %q to expression %q", area.InjectTag, string(contents[area.Start-1:area.End-1]))
contents = it.injectTag(contents, area)
}
if err = os.WriteFile(inputPath, contents, 0o644); err != nil {
return err
}
if len(areas) > 0 {
it.logf("file %q is injected with custom tags", inputPath)
}
return nil
}
func (it *InjectTag) logf(format string, v ...interface{}) {
log.Printf(format, v...)
}
func (it *InjectTag) tagFromComment(fieldName string, comments []*ast.Comment) (tags []InjectTagProps) {
var commentInject = make(map[string]struct{})
for i := range comments {
bsonMatch := bsonComment.FindStringSubmatch(comments[i].Text)
if len(bsonMatch) == 2 {
tags = append(tags, InjectTagProps{
TagName: "bson",
comment: comments[i],
fieldName: fieldName,
fieldValue: fmt.Sprintf(`bson:%v`, bsonMatch[1]),
})
commentInject["bson"] = struct{}{}
continue
}
jsonMatch := jsonComment.FindStringSubmatch(comments[i].Text)
if len(jsonMatch) == 2 {
tags = append(tags, InjectTagProps{
TagName: "json",
comment: comments[i],
fieldName: fieldName,
fieldValue: fmt.Sprintf(`json:%v`, jsonMatch[1]),
})
commentInject["json"] = struct{}{}
continue
}
match := rComment.FindStringSubmatch(comments[i].Text)
if len(match) == 2 {
tagVal := match[1]
tagName := strings.Split(match[1], ":")[0]
tags = append(tags, InjectTagProps{
TagName: tagName,
comment: comments[i],
fieldName: fieldName,
fieldValue: tagVal,
})
commentInject[tagName] = struct{}{}
continue
}
}
for tag, style := range it.defaultTags {
_, exist := commentInject[tag]
if exist {
continue
}
tags = append(tags, InjectTagProps{
TagName: tag,
fieldName: fieldName,
fieldValue: fmt.Sprintf("%s:\"%s\"", tag, getFieldValue(fieldName, style)),
})
}
return
}
type tagItem struct {
key string
value string
}
type tagItems []tagItem
func (ti *tagItems) format() string {
var tags []string
for _, item := range *ti {
tags = append(tags, fmt.Sprintf(`%s:%s`, item.key, item.value))
}
return strings.Join(tags, " ")
}
func (ti *tagItems) override(nti tagItems) tagItems {
var overrides []tagItem
for i := range *ti {
dup := -1
for j := range nti {
if (*ti)[i].key == nti[j].key {
dup = j
break
}
}
if dup == -1 {
overrides = append(overrides, (*ti)[i])
} else {
overrides = append(overrides, nti[dup])
nti = append(nti[:dup], nti[dup+1:]...)
}
}
return append(overrides, nti...)
}
func (it *InjectTag) newTagItems(tag string) tagItems {
var items []tagItem
it.logf("new tag: %v\n", tag)
split := rTags.FindAllString(tag, -1)
for _, t := range split {
sepPos := strings.Index(t, ":")
items = append(items, tagItem{
key: t[:sepPos],
value: t[sepPos+1:],
})
}
return items
}
func (it *InjectTag) injectTag(contents []byte, area textArea) (injected []byte) {
expr := make([]byte, area.End-area.Start)
copy(expr, contents[area.Start-1:area.End-1])
log.Println("expr: ", string(expr))
cti := it.newTagItems(area.CurrentTag)
log.Printf("cti: %v\n", cti)
iti := it.newTagItems(area.InjectTag)
log.Printf("iti: %v\n", iti)
ti := cti.override(iti)
log.Printf("ti: %v\n", ti)
expr = rInject.ReplaceAll(expr, []byte(fmt.Sprintf("`%s`", ti.format())))
injected = append(injected, contents[:area.Start-1]...)
injected = append(injected, expr...)
injected = append(injected, contents[area.End-1:]...)
return
}

17
inject_tag_test.go Normal file
View File

@ -0,0 +1,17 @@
package gobuf
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestInjectTag_Inject(t *testing.T) {
var parser = NewInjectTag("./example.pb.go")
err := parser.WithTags(InjectTagProps{
TagName: "bson",
}, InjectTagProps{
TagName: "pson",
Style: LowerCase,
}).Inject()
assert.Nil(t, err)
}

View File

@ -20,3 +20,28 @@ func calm2Case(src string) string {
}
return buffer.String()
}
func toLowerCamelCase(str string) string {
if len(str) != 0 {
return string(unicode.ToLower(rune(str[0]))) + str[1:]
}
return ""
}
func toUpperCamlCase(str string) string {
if len(str) != 0 {
return string(unicode.ToUpper(rune(str[0]))) + str[1:]
}
return ""
}
func getFieldValue(fieldName string, cases TagValueStyle) string {
switch cases {
case Underline:
return calm2Case(fieldName)
case LowerCase:
return toLowerCamelCase(fieldName)
default:
return fieldName
}
}