160 lines
3.0 KiB
Go
160 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/fatih/structtag"
|
|
tag_options "github.com/unistack-org/micro-proto/tag"
|
|
"google.golang.org/protobuf/compiler/protogen"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var (
|
|
astFields = make(map[string]map[string]map[string]*structtag.Tags) // map proto file with proto message ast struct
|
|
)
|
|
|
|
func (g *Generator) astGenerate(plugin *protogen.Plugin) error {
|
|
if g.tagPath == "" {
|
|
return nil
|
|
}
|
|
for _, file := range plugin.Files {
|
|
if !file.Generate {
|
|
continue
|
|
}
|
|
|
|
for _, message := range file.Messages {
|
|
for _, field := range message.Fields {
|
|
if field.Desc.Options() == nil {
|
|
continue
|
|
}
|
|
if !proto.HasExtension(field.Desc.Options(), tag_options.E_Tags) {
|
|
continue
|
|
}
|
|
|
|
opts := proto.GetExtension(field.Desc.Options(), tag_options.E_Tags)
|
|
if opts != nil {
|
|
fpath := filepath.Join(g.tagPath, file.GeneratedFilenamePrefix+".pb.go")
|
|
mp, ok := astFields[fpath]
|
|
if !ok {
|
|
mp = make(map[string]map[string]*structtag.Tags)
|
|
}
|
|
nmp, ok := mp[message.GoIdent.GoName]
|
|
if !ok {
|
|
nmp = make(map[string]*structtag.Tags)
|
|
}
|
|
tags, err := structtag.Parse(opts.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nmp[field.GoName] = tags
|
|
mp[message.GoIdent.GoName] = nmp
|
|
astFields[fpath] = mp
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for file, mp := range astFields {
|
|
fset := token.NewFileSet()
|
|
pf, err := parser.ParseFile(fset, file, nil, parser.AllErrors|parser.ParseComments)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r := retag{}
|
|
f := func(n ast.Node) ast.Visitor {
|
|
if r.err != nil {
|
|
return nil
|
|
}
|
|
|
|
if v, ok := n.(*ast.TypeSpec); ok {
|
|
r.fields = mp[v.Name.Name]
|
|
return r
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
ast.Walk(structVisitor{f}, pf)
|
|
|
|
if r.err != nil {
|
|
return err
|
|
}
|
|
|
|
fp, err := os.OpenFile(file, os.O_WRONLY|os.O_TRUNC, os.FileMode(0644))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = format.Node(fp, fset, pf); err != nil {
|
|
fp.Close()
|
|
return err
|
|
}
|
|
if err = fp.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type retag struct {
|
|
err error
|
|
fields map[string]*structtag.Tags
|
|
}
|
|
|
|
func (v retag) Visit(n ast.Node) ast.Visitor {
|
|
if v.err != nil {
|
|
return nil
|
|
}
|
|
if f, ok := n.(*ast.Field); ok {
|
|
if len(f.Names) == 0 {
|
|
return nil
|
|
}
|
|
|
|
newTags := v.fields[f.Names[0].String()]
|
|
if newTags == nil {
|
|
return nil
|
|
}
|
|
if f.Tag == nil {
|
|
f.Tag = &ast.BasicLit{
|
|
Kind: token.STRING,
|
|
}
|
|
}
|
|
|
|
oldTags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
|
|
if err != nil {
|
|
v.err = err
|
|
return nil
|
|
}
|
|
for _, t := range newTags.Tags() {
|
|
oldTags.Set(t)
|
|
}
|
|
|
|
f.Tag.Value = "`" + oldTags.String() + "`"
|
|
|
|
return nil
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
type structVisitor struct {
|
|
visitor func(n ast.Node) ast.Visitor
|
|
}
|
|
|
|
func (v structVisitor) Visit(n ast.Node) ast.Visitor {
|
|
if tp, ok := n.(*ast.TypeSpec); ok {
|
|
if _, ok := tp.Type.(*ast.StructType); ok {
|
|
ast.Walk(v.visitor(n), n)
|
|
return nil // This will ensure this struct is no longer traversed
|
|
}
|
|
}
|
|
return v
|
|
}
|