You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1403 lines
39 KiB
1403 lines
39 KiB
// Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"). You may not |
|
// use this file except in compliance with the License. A copy of the License is |
|
// located at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// or in the "license" file accompanying this file. This file is distributed on |
|
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
|
// express or implied. See the License for the specific language governing |
|
// permissions and limitations under the License. |
|
|
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"compress/gzip" |
|
"fmt" |
|
"go/ast" |
|
"go/parser" |
|
"go/printer" |
|
"go/token" |
|
"io/ioutil" |
|
"os" |
|
"path" |
|
"path/filepath" |
|
"reflect" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
"unicode" |
|
|
|
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen" |
|
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/stringutils" |
|
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/typemap" |
|
|
|
"github.com/golang/protobuf/proto" |
|
"github.com/golang/protobuf/protoc-gen-go/descriptor" |
|
plugin "github.com/golang/protobuf/protoc-gen-go/plugin" |
|
"github.com/pkg/errors" |
|
"github.com/siddontang/go/ioutil2" |
|
) |
|
|
|
var legacyPathMapping = map[string]string{ |
|
"live.webucenter": "live.web-ucenter", |
|
"live.webroom": "live.web-room", |
|
"live.appucenter": "live.app-ucenter", |
|
"live.appblink": "live.app-blink", |
|
"live.approom": "live.app-room", |
|
"live.appinterface": "live.app-interface", |
|
"live.liveadmin": "live.live-admin", |
|
"live.resource": "live.resource", |
|
"live.livedemo": "live.live-demo", |
|
"live.lotteryinterface": "live.lottery-interface", |
|
} |
|
|
|
type bm struct { |
|
filesHandled int |
|
|
|
reg *typemap.Registry |
|
|
|
// Map to record whether we've built each package |
|
pkgs map[string]string |
|
pkgNamesInUse map[string]bool |
|
|
|
importPrefix string // String to prefix to imported package file names. |
|
importMap map[string]string // Mapping from .proto file name to import path. |
|
tpl bool // only generate service template file, no docs, no .bm.go, default false |
|
|
|
// Package naming: |
|
genPkgName string // Name of the package that we're generating |
|
fileToGoPackageName map[*descriptor.FileDescriptorProto]string |
|
|
|
// List of files that were inputs to the generator. We need to hold this in |
|
// the struct so we can write a header for the file that lists its inputs. |
|
genFiles []*descriptor.FileDescriptorProto |
|
|
|
// Output buffer that holds the bytes we want to write out for a single file. |
|
// Gets reset after working on a file. |
|
output *bytes.Buffer |
|
|
|
deps map[string]string |
|
} |
|
|
|
// if current dir is a go-common project |
|
// or is the internal directory of a go-common project |
|
// this present a project info |
|
type projectInfo struct { |
|
absolutePath string |
|
// relative to go-common |
|
importPath string |
|
name string |
|
department string |
|
// interface, service, admin ... |
|
typ string |
|
hasInternalPkg bool |
|
// 从工作目录到project目录的相对路径 比如./ ../ |
|
pathRefToProj string |
|
} |
|
|
|
// projectInfo for current directory |
|
var projInfo *projectInfo |
|
|
|
func bmGenerator() *bm { |
|
t := &bm{ |
|
pkgs: make(map[string]string), |
|
pkgNamesInUse: make(map[string]bool), |
|
importMap: make(map[string]string), |
|
fileToGoPackageName: make(map[*descriptor.FileDescriptorProto]string), |
|
output: bytes.NewBuffer(nil), |
|
} |
|
|
|
return t |
|
} |
|
|
|
func (t *bm) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse { |
|
params, err := parseCommandLineParams(in.GetParameter()) |
|
if err != nil { |
|
gen.Fail("could not parse parameters passed to --bm_out", err.Error()) |
|
} |
|
t.importPrefix = params.importPrefix |
|
t.importMap = params.importMap |
|
t.tpl = params.tpl |
|
|
|
t.genFiles = gen.FilesToGenerate(in) |
|
|
|
// Collect information on types. |
|
t.reg = typemap.New(in.ProtoFile) |
|
|
|
t.registerPackageName("context") |
|
t.registerPackageName("ioutil") |
|
t.registerPackageName("proto") |
|
|
|
// Time to figure out package names of objects defined in protobuf. First, |
|
// we'll figure out the name for the package we're generating. |
|
genPkgName, err := deduceGenPkgName(t.genFiles) |
|
if err != nil { |
|
gen.Fail(err.Error()) |
|
} |
|
t.genPkgName = genPkgName |
|
|
|
// Next, we need to pick names for all the files that are dependencies. |
|
if len(in.ProtoFile) > 0 { |
|
t.initProjInfo(in.ProtoFile[0]) |
|
} |
|
for _, f := range in.ProtoFile { |
|
if fileDescSliceContains(t.genFiles, f) { |
|
// This is a file we are generating. It gets the shared package name. |
|
t.fileToGoPackageName[f] = t.genPkgName |
|
} else { |
|
// This is a dependency. Use its package name. |
|
name := f.GetPackage() |
|
if name == "" { |
|
name = stringutils.BaseName(f.GetName()) |
|
} |
|
name = stringutils.CleanIdentifier(name) |
|
alias := t.registerPackageName(name) |
|
t.fileToGoPackageName[f] = alias |
|
} |
|
} |
|
|
|
// Showtime! Generate the response. |
|
resp := new(plugin.CodeGeneratorResponse) |
|
for _, f := range t.genFiles { |
|
respFile := t.generate(f) |
|
if respFile != nil { |
|
resp.File = append(resp.File, respFile) |
|
} |
|
for _, s := range f.Service { |
|
docResp := t.generateDoc(f, s) |
|
if docResp != nil { |
|
resp.File = append(resp.File, docResp) |
|
} |
|
} |
|
|
|
if t.tpl { |
|
if projInfo != nil { |
|
for _, s := range f.Service { |
|
serviceResp := t.generateServiceImpl(f, s) |
|
if serviceResp != nil { |
|
resp.File = append(resp.File, serviceResp) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return resp |
|
} |
|
|
|
// lookupProjPath get project path by proto absolute path |
|
// assume that proto is in the project's model directory |
|
func lookupProjPath(protoAbs string) (result string) { |
|
lastIndex := len(protoAbs) |
|
curPath := protoAbs |
|
|
|
for lastIndex > 0 { |
|
if ioutil2.FileExists(curPath+"/cmd") && ioutil2.FileExists(curPath+"/api") { |
|
result = curPath |
|
return |
|
} |
|
lastIndex = strings.LastIndex(curPath, string(os.PathSeparator)) |
|
curPath = protoAbs[:lastIndex] |
|
} |
|
result = "" |
|
return |
|
} |
|
|
|
func (t *bm) initProjInfo(file *descriptor.FileDescriptorProto) { |
|
var err error |
|
projInfo = &projectInfo{} |
|
defer func() { |
|
if err != nil { |
|
projInfo = nil |
|
} |
|
}() |
|
wd, err := os.Getwd() |
|
if err != nil { |
|
panic("cannot get working directory") |
|
} |
|
protoAbs := wd + "/" + file.GetName() |
|
appIndex := strings.Index(wd, "go-common/app/") |
|
if appIndex == -1 { |
|
err = errors.New("not in go-common/app/") |
|
return |
|
} |
|
|
|
projPath := lookupProjPath(protoAbs) |
|
if projPath == "" { |
|
err = errors.New("not in project") |
|
return |
|
} |
|
if strings.Contains(wd, projPath) { |
|
rest := strings.Replace(wd, projPath, "", 1) |
|
projInfo.pathRefToProj = "./" |
|
if rest != "" { |
|
split := strings.Split(rest, "/") |
|
ref := "" |
|
for i := 0; i < len(split)-1; i++ { |
|
ref = ref + "../" |
|
} |
|
projInfo.pathRefToProj = ref |
|
} |
|
} |
|
projInfo.absolutePath = projPath |
|
if ioutil2.FileExists(projPath + "/internal") { |
|
projInfo.hasInternalPkg = true |
|
} |
|
|
|
relativePath := projInfo.absolutePath[appIndex+len("go-common/app/"):] |
|
projInfo.importPath = "go-common/app/" + relativePath |
|
split := strings.Split(relativePath, "/") |
|
projInfo.typ = split[0] |
|
projInfo.department = split[1] |
|
projInfo.name = split[2] |
|
} |
|
|
|
// find tag between backtick, start & end is the position of backtick |
|
func getLineTag(line string) (tag reflect.StructTag, start int, end int) { |
|
start = strings.Index(line, "`") |
|
end = strings.LastIndex(line, "`") |
|
if end <= start { |
|
return |
|
} |
|
tag = reflect.StructTag(line[start+1 : end]) |
|
return |
|
} |
|
|
|
func getCommentWithoutTag(comment string) []string { |
|
var lines []string |
|
if comment == "" { |
|
return lines |
|
} |
|
split := strings.Split(strings.TrimRight(comment, "\n\r"), "\n") |
|
for _, line := range split { |
|
tag, _, _ := getLineTag(line) |
|
if tag == "" { |
|
lines = append(lines, line) |
|
} |
|
} |
|
return lines |
|
} |
|
|
|
func getTagsInComment(comment string) []reflect.StructTag { |
|
split := strings.Split(comment, "\n") |
|
var tagsInComment []reflect.StructTag |
|
for _, line := range split { |
|
tag, _, _ := getLineTag(line) |
|
if tag != "" { |
|
tagsInComment = append(tagsInComment, tag) |
|
} |
|
} |
|
return tagsInComment |
|
} |
|
|
|
func getTagValue(key string, tags []reflect.StructTag) string { |
|
for _, t := range tags { |
|
val := t.Get(key) |
|
if val != "" { |
|
return val |
|
} |
|
} |
|
return "" |
|
} |
|
|
|
// Is this field repeated? |
|
func isRepeated(field *descriptor.FieldDescriptorProto) bool { |
|
return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED |
|
} |
|
|
|
func (t *bm) isMap(field *descriptor.FieldDescriptorProto) bool { |
|
if field.GetType() != descriptor.FieldDescriptorProto_TYPE_MESSAGE { |
|
return false |
|
} |
|
md := t.reg.MessageDefinition(field.GetTypeName()) |
|
if md == nil || !md.Descriptor.GetOptions().GetMapEntry() { |
|
return false |
|
} |
|
return true |
|
} |
|
|
|
func (t *bm) generateToc(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto) { |
|
for _, method := range service.Method { |
|
comment, _ := t.reg.MethodComments(file, service, method) |
|
tags := getTagsInComment(comment.Leading) |
|
_, path, newPath := t.getHttpInfo(file, service, method, tags) |
|
|
|
cleanComments := getCommentWithoutTag(comment.Leading) |
|
var title string |
|
if len(cleanComments) > 0 { |
|
title = cleanComments[0] |
|
} |
|
|
|
// 如果有老的路径,只显示老的路径文档 |
|
if path != "" { |
|
anchor := strings.Replace(path, "/", "", -1) |
|
t.P(fmt.Sprintf("- [%s](#%s) %s", path, anchor, title)) |
|
} else { |
|
anchor := strings.Replace(newPath, "/", "", -1) |
|
t.P(fmt.Sprintf("- [%s](#%s) %s", newPath, anchor, title)) |
|
} |
|
} |
|
} |
|
|
|
func (t *bm) generateDoc(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto) *plugin.CodeGeneratorResponse_File { |
|
resp := new(plugin.CodeGeneratorResponse_File) |
|
var name = goFileName(file, "."+lcFirst(service.GetName())+".md") |
|
resp.Name = &name |
|
t.P("<!-- package=" + file.GetPackage() + " -->") |
|
t.generateToc(file, service) |
|
for _, method := range service.Method { |
|
comment, err := t.reg.MethodComments(file, service, method) |
|
tags := getTagsInComment(comment.Leading) |
|
cleanComments := getCommentWithoutTag(comment.Leading) |
|
midwaresStr := getTagValue("midware", tags) |
|
needAuth := false |
|
if midwaresStr != "" { |
|
split := strings.Split(midwaresStr, ",") |
|
for _, m := range split { |
|
if m == "auth" { |
|
needAuth = true |
|
break |
|
} |
|
} |
|
} |
|
t.P() |
|
httpMethod, legacyPath, path := t.getHttpInfo(file, service, method, tags) |
|
if legacyPath != "" { |
|
path = legacyPath |
|
} |
|
t.P("## " + path) |
|
|
|
if err == nil { |
|
if len(cleanComments) == 0 { |
|
t.P(`### 无标题`) |
|
} else { |
|
t.P(`###`, strings.Join(cleanComments, "\n")) |
|
} |
|
} |
|
t.P() |
|
|
|
if needAuth { |
|
t.P(`> `, "需要登录") |
|
t.P() |
|
} |
|
|
|
t.P("#### 方法:" + httpMethod) |
|
t.P() |
|
|
|
t.genRequestParam(file, service, method) |
|
t.P("#### 响应") |
|
t.P() |
|
|
|
t.P("```javascript") |
|
t.P(`{`) |
|
t.P(` "code": 0,`) |
|
t.P(` "message": "ok",`) |
|
t.P(t.getExampleJson(file, service, method)) |
|
t.P(`}`) |
|
t.P("```") |
|
t.P() |
|
} |
|
resp.Content = proto.String(t.output.String()) |
|
t.output.Reset() |
|
return resp |
|
} |
|
|
|
func (t *bm) genRequestParam( |
|
file *descriptor.FileDescriptorProto, |
|
svc *descriptor.ServiceDescriptorProto, |
|
method *descriptor.MethodDescriptorProto) { |
|
md := t.reg.MessageDefinition(method.GetInputType()) |
|
t.P(`#### 请求参数`) |
|
t.P() |
|
|
|
var outputs []string |
|
for i, f := range md.Descriptor.Field { |
|
if f.GetType() == descriptor.FieldDescriptorProto_TYPE_MESSAGE { |
|
// 如果有message 只能以json的方式显示参数了 |
|
var buf = &[]string{} |
|
t.exampleJsonForMsg(md, file, buf, "", 0, "") |
|
j := strings.Join(*buf, "\n") |
|
t.P("```javascript") |
|
t.P(j) |
|
t.P("```") |
|
t.P() |
|
return |
|
} |
|
if i == 0 { |
|
outputs = append(outputs, `|参数名|必选|类型|描述|`) |
|
outputs = append(outputs, `|:---|:---|:---|:---|`) |
|
} |
|
fComment, _ := t.reg.FieldComments(file, md, f) |
|
var tags []reflect.StructTag |
|
{ |
|
//get required info from gogoproto.moretags |
|
moretags := getMoreTags(f) |
|
if moretags != nil { |
|
tags = []reflect.StructTag{reflect.StructTag(*moretags)} |
|
} |
|
} |
|
if len(tags) == 0 { |
|
tags = getTagsInComment(fComment.Leading) |
|
} |
|
validateTag := getTagValue("validate", tags) |
|
var validateRules []string |
|
if validateTag != "" { |
|
validateRules = strings.Split(validateTag, ",") |
|
} |
|
required := false |
|
for _, rule := range validateRules { |
|
if rule == "required" { |
|
required = true |
|
} |
|
} |
|
requiredDesc := "是" |
|
if !required { |
|
requiredDesc = "否" |
|
} |
|
_, typeName := t.mockValueForField(f, tags) |
|
split := strings.Split(fComment.Leading, "\n") |
|
desc := "" |
|
for _, line := range split { |
|
if line != "" { |
|
tag, _, _ := getLineTag(line) |
|
if tag == "" { |
|
desc += line |
|
} |
|
} |
|
} |
|
outputs = append(outputs, fmt.Sprintf(`|%s|%s|%s|%s|`, getJsonTag(f), requiredDesc, typeName, desc)) |
|
} |
|
for _, s := range outputs { |
|
t.P(s) |
|
} |
|
t.P() |
|
} |
|
|
|
func (t *bm) getExampleJson(file *descriptor.FileDescriptorProto, |
|
svc *descriptor.ServiceDescriptorProto, |
|
method *descriptor.MethodDescriptorProto) string { |
|
md := t.reg.MessageDefinition(method.GetOutputType()) |
|
var buf = &[]string{} |
|
t.exampleJsonForMsg(md, file, buf, "data", 4, "") |
|
return strings.Join(*buf, "\n") |
|
} |
|
|
|
func makeIndentStr(i int) string { |
|
return strings.Repeat(" ", i) |
|
} |
|
|
|
func (t *bm) mockValueForField(field *descriptor.FieldDescriptorProto, |
|
tags []reflect.StructTag) (mockVal string, typeName string) { |
|
tagMock := getTagValue("mock", tags) |
|
mockVal = "\"unknown\"" |
|
typeName = "unknown" |
|
switch field.GetType() { |
|
case descriptor.FieldDescriptorProto_TYPE_BOOL: |
|
if tagMock == "true" || tagMock == "false" { |
|
mockVal = tagMock |
|
} else { |
|
mockVal = "true" |
|
} |
|
typeName = "bool" |
|
case descriptor.FieldDescriptorProto_TYPE_DOUBLE, |
|
descriptor.FieldDescriptorProto_TYPE_FLOAT: |
|
mockVal = "0.1" |
|
if tagMock != "" { |
|
if _, err := strconv.ParseFloat(tagMock, 64); err == nil { |
|
mockVal = tagMock |
|
} |
|
} |
|
typeName = "float" |
|
case |
|
descriptor.FieldDescriptorProto_TYPE_INT64, |
|
descriptor.FieldDescriptorProto_TYPE_UINT64, |
|
descriptor.FieldDescriptorProto_TYPE_INT32, |
|
descriptor.FieldDescriptorProto_TYPE_FIXED64, |
|
descriptor.FieldDescriptorProto_TYPE_FIXED32, |
|
descriptor.FieldDescriptorProto_TYPE_ENUM, |
|
descriptor.FieldDescriptorProto_TYPE_UINT32, |
|
descriptor.FieldDescriptorProto_TYPE_SFIXED32, |
|
descriptor.FieldDescriptorProto_TYPE_SFIXED64, |
|
descriptor.FieldDescriptorProto_TYPE_SINT32, |
|
descriptor.FieldDescriptorProto_TYPE_SINT64: |
|
mockVal = "0" |
|
if tagMock != "" { |
|
if _, err := strconv.Atoi(tagMock); err == nil { |
|
mockVal = tagMock |
|
} |
|
} |
|
typeName = "integer" |
|
case |
|
descriptor.FieldDescriptorProto_TYPE_STRING, |
|
descriptor.FieldDescriptorProto_TYPE_BYTES: |
|
mockVal = `""` |
|
if tagMock != "" { |
|
mockVal = strconv.Quote(tagMock) |
|
} |
|
typeName = "string" |
|
} |
|
if isRepeated(field) { |
|
typeName = "多个" + typeName |
|
} |
|
return |
|
} |
|
|
|
func (t *bm) exampleJsonForMsg( |
|
msg *typemap.MessageDefinition, |
|
file *descriptor.FileDescriptorProto, |
|
buf *[]string, fieldName string, indent int, outEndComma string) { |
|
if fieldName == "" { |
|
*buf = append(*buf, makeIndentStr(indent)+"{") |
|
} else { |
|
*buf = append(*buf, makeIndentStr(indent)+fmt.Sprintf(`"%s": {`, fieldName)) |
|
} |
|
num := len(msg.Descriptor.Field) |
|
for i, f := range msg.Descriptor.Field { |
|
isScalar := isScalar(f) |
|
fComment, _ := t.reg.FieldComments(file, msg, f) |
|
cleanComment := getCommentWithoutTag(fComment.Leading) |
|
for _, line := range cleanComment { |
|
if strings.Trim(line, " \t\n\r") != "" { |
|
*buf = append(*buf, makeIndentStr(indent+4)+"// "+line) |
|
} |
|
} |
|
|
|
endComma := "" |
|
if i < (num - 1) { |
|
endComma = "," |
|
} |
|
repeated := isRepeated(f) |
|
tags := getTagsInComment(fComment.Leading) |
|
if isScalar { |
|
mockVal, _ := t.mockValueForField(f, tags) |
|
|
|
if repeated { |
|
// "key" : [ |
|
// value |
|
// ] |
|
*buf = append(*buf, makeIndentStr(indent+4)+`"`+getJsonTag(f)+`": [`) |
|
*buf = append(*buf, makeIndentStr(indent+8)+mockVal) |
|
*buf = append(*buf, makeIndentStr(indent+4)+`]`+endComma) |
|
} else { |
|
// "key" : value |
|
*buf = append(*buf, makeIndentStr(indent+4)+`"`+getJsonTag(f)+`": `+mockVal+endComma) |
|
} |
|
} else { |
|
isMap := t.isMap(f) |
|
if repeated { |
|
if isMap { |
|
*buf = append(*buf, makeIndentStr(indent+4)+`"`+getJsonTag(f)+`": {`) |
|
} else { |
|
*buf = append(*buf, makeIndentStr(indent+4)+`"`+getJsonTag(f)+`": [`) |
|
} |
|
} |
|
subMsg := t.reg.MessageDefinition(f.GetTypeName()) |
|
if subMsg == nil { |
|
panic(fmt.Sprintf("%v%v", f.TypeName, f.Type)) |
|
} |
|
nextIndent := indent + 4 |
|
nextFname := getJsonTag(f) |
|
if repeated { |
|
nextIndent = indent + 8 |
|
nextFname = "" |
|
} |
|
if isMap { |
|
mapKeyField := subMsg.Descriptor.Field[0] |
|
mapValueField := subMsg.Descriptor.Field[1] |
|
keyDesc := "mapKey" |
|
if mapKeyField.GetType() != descriptor.FieldDescriptorProto_TYPE_STRING { |
|
keyDesc = "1" |
|
} |
|
|
|
if mapValueField.GetType() == descriptor.FieldDescriptorProto_TYPE_MESSAGE { |
|
// "mapKey" : { |
|
// ... |
|
// } |
|
mapValueMsg := t.reg.MessageDefinition(mapValueField.GetTypeName()) |
|
t.exampleJsonForMsg(mapValueMsg, file, buf, keyDesc, nextIndent, "") |
|
} else { |
|
// "mapKey" : "map value" |
|
val, _ := t.mockValueForField(mapValueField, tags) |
|
|
|
*buf = append(*buf, makeIndentStr(indent+8)+`"`+keyDesc+`": `+val) |
|
} |
|
*buf = append(*buf, makeIndentStr(indent+4)+`}`+endComma) |
|
} else { |
|
if repeated { |
|
t.exampleJsonForMsg(subMsg, file, buf, nextFname, nextIndent, "") |
|
*buf = append(*buf, makeIndentStr(indent+4)+`]`+endComma) |
|
} else { |
|
t.exampleJsonForMsg(subMsg, file, buf, nextFname, nextIndent, endComma) |
|
} |
|
} |
|
} |
|
} |
|
*buf = append(*buf, makeIndentStr(indent)+"}"+outEndComma) |
|
} |
|
|
|
// Is this field a scalar numeric type? |
|
func isScalar(field *descriptor.FieldDescriptorProto) bool { |
|
if field.Type == nil { |
|
return false |
|
} |
|
switch *field.Type { |
|
case descriptor.FieldDescriptorProto_TYPE_DOUBLE, |
|
descriptor.FieldDescriptorProto_TYPE_FLOAT, |
|
descriptor.FieldDescriptorProto_TYPE_INT64, |
|
descriptor.FieldDescriptorProto_TYPE_UINT64, |
|
descriptor.FieldDescriptorProto_TYPE_INT32, |
|
descriptor.FieldDescriptorProto_TYPE_FIXED64, |
|
descriptor.FieldDescriptorProto_TYPE_FIXED32, |
|
descriptor.FieldDescriptorProto_TYPE_BOOL, |
|
descriptor.FieldDescriptorProto_TYPE_UINT32, |
|
descriptor.FieldDescriptorProto_TYPE_ENUM, |
|
descriptor.FieldDescriptorProto_TYPE_SFIXED32, |
|
descriptor.FieldDescriptorProto_TYPE_SFIXED64, |
|
descriptor.FieldDescriptorProto_TYPE_SINT32, |
|
descriptor.FieldDescriptorProto_TYPE_SINT64, |
|
descriptor.FieldDescriptorProto_TYPE_BYTES, |
|
descriptor.FieldDescriptorProto_TYPE_STRING: |
|
return true |
|
default: |
|
return false |
|
} |
|
} |
|
|
|
func (t *bm) registerPackageName(name string) (alias string) { |
|
alias = name |
|
i := 1 |
|
for t.pkgNamesInUse[alias] { |
|
alias = name + strconv.Itoa(i) |
|
i++ |
|
} |
|
t.pkgNamesInUse[alias] = true |
|
t.pkgs[name] = alias |
|
return alias |
|
} |
|
|
|
type visitor struct { |
|
funcMap map[string]bool |
|
} |
|
|
|
func (v visitor) Visit(n ast.Node) ast.Visitor { |
|
switch d := n.(type) { |
|
case *ast.FuncDecl: |
|
v.funcMap[d.Name.Name] = true |
|
} |
|
return v |
|
} |
|
|
|
// generateServiceImpl returns service implementation file service/{prefix}/service.go |
|
// if file not exists |
|
// else it returns nil |
|
func (t *bm) generateServiceImpl(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto) *plugin.CodeGeneratorResponse_File { |
|
resp := new(plugin.CodeGeneratorResponse_File) |
|
prefix := t.getVersionPrefix() |
|
importPath := t.getPbImportPath(file.GetName()) |
|
var alias = t.getPkgAlias() |
|
confPath := projInfo.importPath + "/conf" |
|
if projInfo.hasInternalPkg { |
|
confPath = projInfo.importPath + "/internal/conf" |
|
} |
|
name := "service/" + prefix + "/" + lcFirst(svc.GetName()) + ".go" |
|
if projInfo.hasInternalPkg { |
|
name = "internal/" + name |
|
} |
|
name = projInfo.pathRefToProj + name |
|
resp.Name = &name |
|
if _, err := os.Stat(name); !os.IsNotExist(err) { |
|
// Insert methods if file already exists |
|
fset := token.NewFileSet() |
|
astTree, err := parser.ParseFile(fset, name, nil, parser.ParseComments) |
|
if err != nil { |
|
panic("parse file error: " + name + " err: " + err.Error()) |
|
} |
|
v := visitor{funcMap: map[string]bool{}} |
|
ast.Walk(v, astTree) |
|
|
|
t.output.Reset() |
|
buf, err := ioutil.ReadFile(name) |
|
if err != nil { |
|
panic("cannot read file:" + name) |
|
} |
|
t.P(string(buf)) |
|
t.generateBmImpl(file, svc, v.funcMap) |
|
resp.Content = proto.String(t.formattedOutput()) |
|
t.output.Reset() |
|
return resp |
|
} |
|
|
|
tplPkg := "service" |
|
if t.genPkgName[:1] == "v" { |
|
tplPkg = t.genPkgName |
|
} |
|
t.P(`package `, tplPkg) |
|
t.P() |
|
t.P(`import (`) |
|
t.P(` `, alias, ` "`, importPath, `"`) |
|
t.P(` "`, confPath, `"`) |
|
t.P(` "context"`) |
|
t.P(`)`) |
|
for pkg, importPath := range t.deps { |
|
t.P(`import `, pkg, ` `, importPath) |
|
} |
|
svcStructName := serviceName(svc) + "Service" |
|
t.P(`// `, svcStructName, ` struct`) |
|
t.P(`type `, svcStructName, ` struct {`) |
|
t.P(` conf *conf.Config`) |
|
t.P(` // optionally add other properties here, such as dao`) |
|
t.P(` // dao *dao.Dao`) |
|
t.P(`}`) |
|
t.P() |
|
t.P(`//New`, svcStructName, ` init`) |
|
t.P(`func New`, svcStructName, `(c *conf.Config) (s *`, svcStructName, `) {`) |
|
t.P(` s = &`, svcStructName, `{`) |
|
t.P(` conf: c,`) |
|
t.P(` }`) |
|
t.P(` return s`) |
|
t.P(`}`) |
|
|
|
comments, err := t.reg.ServiceComments(file, svc) |
|
if err == nil { |
|
t.printComments(comments) |
|
} |
|
t.P() |
|
t.generateBmImpl(file, svc, map[string]bool{}) |
|
resp.Content = proto.String(t.formattedOutput()) |
|
t.output.Reset() |
|
return resp |
|
} |
|
|
|
func (t *bm) generate(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File { |
|
resp := new(plugin.CodeGeneratorResponse_File) |
|
if len(file.Service) == 0 { |
|
return nil |
|
} |
|
|
|
t.generateFileHeader(file, t.genPkgName) |
|
|
|
t.generateImports(file) |
|
|
|
t.generateMiddlewareInfo(file) |
|
for i, service := range file.Service { |
|
t.generateService(file, service, i) |
|
t.generateSingleRoute(file, service, i) |
|
} |
|
|
|
t.generateFileDescriptor(file) |
|
|
|
resp.Name = proto.String(goFileName(file, ".bm.go")) |
|
resp.Content = proto.String(t.formattedOutput()) |
|
t.output.Reset() |
|
|
|
t.filesHandled++ |
|
return resp |
|
} |
|
|
|
func (t *bm) generateMiddlewareInfo(file *descriptor.FileDescriptorProto) { |
|
t.P() |
|
for _, service := range file.Service { |
|
name := serviceName(service) |
|
for _, method := range service.Method { |
|
_, _, path := t.getHttpInfo(file, service, method, nil) |
|
t.P(`var Path`, name, methodName(method), ` = "`, path, `"`) |
|
} |
|
t.P() |
|
} |
|
} |
|
func (t *bm) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName string) { |
|
t.P("// Code generated by protoc-gen-bm ", gen.Version, ", DO NOT EDIT.") |
|
t.P("// source: ", file.GetName()) |
|
t.P() |
|
if t.filesHandled == 0 { |
|
t.P("/*") |
|
t.P("Package ", t.genPkgName, " is a generated blademaster stub package.") |
|
t.P("This code was generated with go-common/app/tool/bmgen/protoc-gen-bm ", gen.Version, ".") |
|
t.P() |
|
comment, err := t.reg.FileComments(file) |
|
if err == nil && comment.Leading != "" { |
|
for _, line := range strings.Split(comment.Leading, "\n") { |
|
line = strings.TrimPrefix(line, " ") |
|
// ensure we don't escape from the block comment |
|
line = strings.Replace(line, "*/", "* /", -1) |
|
t.P(line) |
|
} |
|
t.P() |
|
} |
|
t.P("It is generated from these files:") |
|
for _, f := range t.genFiles { |
|
t.P("\t", f.GetName()) |
|
} |
|
t.P("*/") |
|
} |
|
t.P(`package `, pkgName) |
|
t.P() |
|
} |
|
|
|
func (t *bm) generateImports(file *descriptor.FileDescriptorProto) { |
|
if len(file.Service) == 0 { |
|
return |
|
} |
|
t.P(`import (`) |
|
//t.P(` `,t.pkgs["context"], ` "context"`) |
|
t.P(` "context"`) |
|
t.P() |
|
t.P(` bm "go-common/library/net/http/blademaster"`) |
|
t.P(` "go-common/library/net/http/blademaster/binding"`) |
|
|
|
t.P(`)`) |
|
// It's legal to import a message and use it as an input or output for a |
|
// method. Make sure to import the package of any such message. First, dedupe |
|
// them. |
|
deps := make(map[string]string) // Map of package name to quoted import path. |
|
ourImportPath := path.Dir(goFileName(file, "")) |
|
for _, s := range file.Service { |
|
for _, m := range s.Method { |
|
defs := []*typemap.MessageDefinition{ |
|
t.reg.MethodInputDefinition(m), |
|
t.reg.MethodOutputDefinition(m), |
|
} |
|
for _, def := range defs { |
|
// By default, import path is the dirname of the Go filename. |
|
importPath := path.Dir(goFileName(def.File, "")) |
|
if importPath == ourImportPath { |
|
continue |
|
} |
|
if substitution, ok := t.importMap[def.File.GetName()]; ok { |
|
importPath = substitution |
|
} |
|
importPath = t.importPrefix + importPath |
|
pkg := t.goPackageName(def.File) |
|
deps[pkg] = strconv.Quote(importPath) |
|
} |
|
} |
|
} |
|
t.deps = deps |
|
for pkg, importPath := range deps { |
|
t.P(`import `, pkg, ` `, importPath) |
|
} |
|
if len(deps) > 0 { |
|
t.P() |
|
} |
|
t.P() |
|
t.P(`// to suppressed 'imported but not used warning'`) |
|
t.P(`var _ *bm.Context`) |
|
t.P(`var _ context.Context`) |
|
t.P(`var _ binding.StructValidator`) |
|
|
|
} |
|
|
|
// P forwards to g.gen.P, which prints output. |
|
func (t *bm) P(args ...string) { |
|
for _, v := range args { |
|
t.output.WriteString(v) |
|
} |
|
t.output.WriteByte('\n') |
|
} |
|
|
|
// Big header comments to makes it easier to visually parse a generated file. |
|
func (t *bm) sectionComment(sectionTitle string) { |
|
t.P() |
|
t.P(`// `, strings.Repeat("=", len(sectionTitle))) |
|
t.P(`// `, sectionTitle) |
|
t.P(`// `, strings.Repeat("=", len(sectionTitle))) |
|
t.P() |
|
} |
|
|
|
func (t *bm) generateService(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto, index int) { |
|
servName := serviceName(service) |
|
|
|
t.sectionComment(servName + ` Interface`) |
|
t.generateBMInterface(file, service) |
|
|
|
} |
|
|
|
// import project/api的路径 |
|
func (t *bm) getPbImportPath(filename string) (importPath string) { |
|
wd, err := os.Getwd() |
|
if err != nil { |
|
panic("cannot get working directory") |
|
} |
|
index := strings.Index(wd, "go-common") |
|
if index == -1 { |
|
gen.Fail("must use inside go-common") |
|
} |
|
dir := filepath.Dir(filename) |
|
if dir != "." { |
|
importPath = wd + "/" + dir |
|
} else { |
|
importPath = wd |
|
} |
|
importPath = importPath[index:] |
|
return |
|
} |
|
|
|
// getProjPath return project path relative to GOPATH |
|
func (t *bm) getProjPath() string { |
|
wd, err := os.Getwd() |
|
if err != nil { |
|
panic("cannot get working directory") |
|
} |
|
index := strings.Index(wd, "go-common") |
|
if index == -1 { |
|
gen.Fail("must use inside go-common") |
|
} |
|
projPkgPath := wd[index:] |
|
return projPkgPath |
|
} |
|
|
|
func lcFirst(str string) string { |
|
for i, v := range str { |
|
return string(unicode.ToLower(v)) + str[i+1:] |
|
} |
|
return "" |
|
} |
|
|
|
// TODO rename |
|
func (t *bm) getLegacyPathPrefix( |
|
svc *descriptor.ServiceDescriptorProto, pathParts []string, isInternal bool) (uriPrefix string) { |
|
var parts []string |
|
parts = append(parts, pathParts[0]) |
|
if isInternal { |
|
parts = append(parts, "internal") |
|
} |
|
parts = append(parts, pathParts[1:]...) |
|
uriPrefix = fmt.Sprintf("/x%s/%s", strings.Join(parts, "/"), lcFirst(svc.GetName())) |
|
return |
|
} |
|
|
|
func (t *bm) getHttpInfo( |
|
file *descriptor.FileDescriptorProto, |
|
service *descriptor.ServiceDescriptorProto, |
|
method *descriptor.MethodDescriptorProto, |
|
tags []reflect.StructTag, |
|
) (httpMethod string, oldPath string, newPath string) { |
|
googleOptionInfo, err := ParseBMMethod(method) |
|
if err == nil { |
|
httpMethod = strings.ToUpper(googleOptionInfo.Method) |
|
p := googleOptionInfo.PathPattern |
|
if p != "" { |
|
oldPath = p |
|
newPath = p |
|
return |
|
} |
|
} |
|
|
|
if httpMethod == "" { |
|
// resolve http method |
|
httpMethod = getTagValue("method", tags) |
|
if httpMethod == "" { |
|
httpMethod = "GET" |
|
} else { |
|
httpMethod = strings.ToUpper(httpMethod) |
|
} |
|
} |
|
|
|
isLegacy, parts := t.convertLegacyPackage(file.GetPackage()) |
|
if isLegacy { |
|
apiInternal := getTagValue("internal", tags) == "true" |
|
pathPrefix := t.getLegacyPathPrefix(service, parts, apiInternal) |
|
oldPath = pathPrefix + `/` + method.GetName() |
|
} |
|
newPath = "/" + file.GetPackage() + "." + service.GetName() + "/" + method.GetName() |
|
return |
|
} |
|
|
|
// 返回空,则不用考虑历史package |
|
// 如果非空,则表示按照返回的pathParts做url规则 |
|
func (t *bm) convertLegacyPackage(pkgName string) (isLegacy bool, pathParts []string) { |
|
var splits = strings.Split(pkgName, ".") |
|
var remain []string |
|
if len(splits) >= 2 { |
|
splits = splits[0:2] |
|
remain = splits[2:] |
|
} |
|
var pkgPrefix = strings.Join(splits, ".") |
|
legacyPkg, isLegacy := legacyPathMapping[pkgPrefix] |
|
if isLegacy { |
|
legacyPkg = strings.Replace(pkgName, pkgPrefix, legacyPkg, 1) |
|
pathParts = append(pathParts, strings.Split(legacyPkg, ".")...) |
|
pathParts = append(pathParts, remain...) |
|
} |
|
return |
|
} |
|
|
|
func (t *bm) generateSingleRoute( |
|
file *descriptor.FileDescriptorProto, |
|
service *descriptor.ServiceDescriptorProto, |
|
index int) { |
|
// old mode is generate xx.route.go in the http pkg |
|
// new mode is generate route code in the same .bm.go |
|
// route rule /x{department}/{project-name}/{path_prefix}/method_name |
|
// generate each route method |
|
servName := serviceName(service) |
|
versionPrefix := t.getVersionPrefix() |
|
svcName := lcFirst(stringutils.CamelCase(versionPrefix)) + servName + "Svc" |
|
t.P(`var `, svcName, ` `, servName, `BMServer`) |
|
|
|
type methodInfo struct { |
|
httpMethod string |
|
midwares []string |
|
routeFuncName string |
|
path string |
|
legacyPath string |
|
methodName string |
|
} |
|
var methList []methodInfo |
|
var allMidwareMap = make(map[string]bool) |
|
var isLegacyPkg = false |
|
for _, method := range service.Method { |
|
var httpMethod string |
|
var midwares []string |
|
comments, _ := t.reg.MethodComments(file, service, method) |
|
tags := getTagsInComment(comments.Leading) |
|
if getTagValue("dynamic", tags) == "true" { |
|
continue |
|
} |
|
httpMethod, legacyPath, path := t.getHttpInfo(file, service, method, tags) |
|
if legacyPath != "" { |
|
isLegacyPkg = true |
|
} |
|
|
|
midStr := getTagValue("midware", tags) |
|
if midStr != "" { |
|
midwares = strings.Split(midStr, ",") |
|
for _, m := range midwares { |
|
allMidwareMap[m] = true |
|
} |
|
} |
|
|
|
methName := methodName(method) |
|
inputType := t.goTypeName(method.GetInputType()) |
|
|
|
routeName := lcFirst(stringutils.CamelCase(servName) + |
|
stringutils.CamelCase(methName)) |
|
|
|
methList = append(methList, methodInfo{ |
|
httpMethod: httpMethod, |
|
midwares: midwares, |
|
routeFuncName: routeName, |
|
path: path, |
|
legacyPath: legacyPath, |
|
methodName: method.GetName(), |
|
}) |
|
|
|
t.P(fmt.Sprintf("func %s (c *bm.Context) {", routeName)) |
|
t.P(` p := new(`, inputType, `)`) |
|
t.P(` if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))); err != nil {`) |
|
t.P(` return`) |
|
t.P(` }`) |
|
t.P(` resp, err := `, svcName, `.`, methName, `(c, p)`) |
|
t.P(` c.JSON(resp, err)`) |
|
t.P(`}`) |
|
t.P(``) |
|
} |
|
|
|
// generate route group |
|
var midList []string |
|
for m := range allMidwareMap { |
|
midList = append(midList, m+" bm.HandlerFunc") |
|
} |
|
|
|
sort.Strings(midList) |
|
|
|
// 注册老的路由的方法 |
|
if isLegacyPkg { |
|
funcName := `Register` + stringutils.CamelCase(versionPrefix) + servName + `Service` |
|
t.P(`// `, funcName, ` Register the blademaster route with middleware map`) |
|
t.P(`// midMap is the middleware map, the key is defined in proto`) |
|
t.P(`func `, funcName, `(e *bm.Engine, svc `, servName, "BMServer, midMap map[string]bm.HandlerFunc)", ` {`) |
|
var keys []string |
|
for m := range allMidwareMap { |
|
keys = append(keys, m) |
|
} |
|
// to keep generated code consistent |
|
sort.Strings(keys) |
|
for _, m := range keys { |
|
t.P(m, ` := midMap["`, m, `"]`) |
|
} |
|
|
|
t.P(svcName, ` = svc`) |
|
for _, methInfo := range methList { |
|
var midArgStr string |
|
if len(methInfo.midwares) == 0 { |
|
midArgStr = "" |
|
} else { |
|
midArgStr = strings.Join(methInfo.midwares, ", ") + ", " |
|
} |
|
t.P(`e.`, methInfo.httpMethod, `("`, methInfo.legacyPath, `", `, midArgStr, methInfo.routeFuncName, `)`) |
|
} |
|
t.P(` }`) |
|
} |
|
// 新的注册路由的方法 |
|
var bmFuncName = fmt.Sprintf("Register%sBMServer", servName) |
|
t.P(`// `, bmFuncName, ` Register the blademaster route`) |
|
t.P(`func `, bmFuncName, `(e *bm.Engine, server `, servName, `BMServer) {`) |
|
t.P(svcName, ` = server`) |
|
for _, methInfo := range methList { |
|
t.P(`e.`, methInfo.httpMethod, `("`, methInfo.path, `",`, methInfo.routeFuncName, ` )`) |
|
} |
|
t.P(` }`) |
|
} |
|
|
|
func (t *bm) generateBMInterface(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto) { |
|
servName := serviceName(service) |
|
|
|
comments, err := t.reg.ServiceComments(file, service) |
|
if err == nil { |
|
t.printComments(comments) |
|
} |
|
t.P(`type `, servName, `BMServer interface {`) |
|
for _, method := range service.Method { |
|
t.generateSignature(file, service, method, comments) |
|
t.P() |
|
} |
|
t.P(`}`) |
|
} |
|
|
|
// pb包的别名 |
|
// 用户生成service实现模板时,对pb文件的引用 |
|
// 如果是v*的package 则为v*pb |
|
// 其他为pb |
|
func (t *bm) getPkgAlias() string { |
|
if t.genPkgName == "" { |
|
return "pb" |
|
} |
|
if t.genPkgName[:1] == "v" { |
|
return t.genPkgName + "pb" |
|
} |
|
return "pb" |
|
} |
|
|
|
// 如果是v*开始的 返回v* |
|
// 否则返回空 |
|
func (t *bm) getVersionPrefix() string { |
|
if t.genPkgName == "" { |
|
return "" |
|
} |
|
if t.genPkgName[:1] == "v" { |
|
return t.genPkgName |
|
} |
|
return "" |
|
} |
|
|
|
func (t *bm) generateBmImpl(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto, |
|
existMap map[string]bool) { |
|
var pkgName = t.getPkgAlias() |
|
svcName := serviceName(service) + "Service" |
|
|
|
for _, method := range service.Method { |
|
methName := methodName(method) |
|
if existMap[methName] { |
|
continue |
|
} |
|
comments, err := t.reg.MethodComments(file, service, method) |
|
tags := getTagsInComment(comments.Leading) |
|
respDynamic := getTagValue("dynamic_resp", tags) == "true" |
|
genImp := func(dynamicRet bool) { |
|
t.P(`// `, methName, " implementation") |
|
if err == nil { |
|
t.printComments(comments) |
|
} |
|
outputType := t.goTypeName(method.GetOutputType()) |
|
inputType := t.goTypeName(method.GetInputType()) |
|
var body string |
|
var ownPkg = t.isOwnPackage(method.GetOutputType()) |
|
var respType string |
|
if ownPkg { |
|
respType = pkgName + "." + outputType |
|
} else { |
|
respType = outputType |
|
} |
|
if dynamicRet { |
|
body = fmt.Sprintf(`func (s *%s) %s(ctx context.Context, req *%s.%s) (resp interface{}, err error) {`, |
|
svcName, methName, pkgName, inputType) |
|
} else { |
|
body = fmt.Sprintf(`func (s *%s) %s(ctx context.Context, req *%s.%s) (resp *%s, err error) {`, |
|
svcName, methName, pkgName, inputType, respType) |
|
} |
|
|
|
t.P(body) |
|
t.P(fmt.Sprintf("resp = &%s{}", respType)) |
|
t.P(` return`) |
|
t.P(`}`) |
|
t.P() |
|
} |
|
genImp(respDynamic) |
|
} |
|
} |
|
|
|
func (t *bm) generateSignature(file *descriptor.FileDescriptorProto, |
|
service *descriptor.ServiceDescriptorProto, |
|
method *descriptor.MethodDescriptorProto, |
|
comments typemap.DefinitionComments) { |
|
comments, err := t.reg.MethodComments(file, service, method) |
|
|
|
methName := methodName(method) |
|
outputType := t.goTypeName(method.GetOutputType()) |
|
inputType := t.goTypeName(method.GetInputType()) |
|
tags := getTagsInComment(comments.Leading) |
|
if getTagValue("dynamic", tags) == "true" { |
|
return |
|
} |
|
|
|
if err == nil { |
|
t.printComments(comments) |
|
} |
|
|
|
respDynamic := getTagValue("dynamic_resp", tags) == "true" |
|
if respDynamic { |
|
t.P(fmt.Sprintf(` %s(ctx context.Context, req *%s) (resp interface{}, err error)`, |
|
methName, inputType)) |
|
} else { |
|
t.P(fmt.Sprintf(` %s(ctx context.Context, req *%s) (resp *%s, err error)`, |
|
methName, inputType, outputType)) |
|
} |
|
} |
|
|
|
func (t *bm) generateFileDescriptor(file *descriptor.FileDescriptorProto) { |
|
// Copied straight of of protoc-gen-go, which trims out comments. |
|
pb := proto.Clone(file).(*descriptor.FileDescriptorProto) |
|
pb.SourceCodeInfo = nil |
|
|
|
b, err := proto.Marshal(pb) |
|
if err != nil { |
|
gen.Fail(err.Error()) |
|
} |
|
|
|
var buf bytes.Buffer |
|
w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression) |
|
w.Write(b) |
|
w.Close() |
|
buf.Bytes() |
|
} |
|
|
|
func (t *bm) printComments(comments typemap.DefinitionComments) bool { |
|
text := strings.TrimSuffix(comments.Leading, "\n") |
|
if len(strings.TrimSpace(text)) == 0 { |
|
return false |
|
} |
|
split := strings.Split(text, "\n") |
|
for _, line := range split { |
|
t.P("// ", strings.TrimPrefix(line, " ")) |
|
} |
|
return len(split) > 0 |
|
} |
|
|
|
// Given a protobuf name for a Message, return the Go name we will use for that |
|
// type, including its package prefix. |
|
func (t *bm) goTypeName(protoName string) string { |
|
def := t.reg.MessageDefinition(protoName) |
|
if def == nil { |
|
gen.Fail("could not find message for", protoName) |
|
} |
|
|
|
var prefix string |
|
if pkg := t.goPackageName(def.File); pkg != t.genPkgName { |
|
prefix = pkg + "." |
|
} |
|
|
|
var name string |
|
for _, parent := range def.Lineage() { |
|
name += parent.Descriptor.GetName() + "_" |
|
} |
|
name += def.Descriptor.GetName() |
|
return prefix + name |
|
} |
|
|
|
func (t *bm) isOwnPackage(protoName string) bool { |
|
def := t.reg.MessageDefinition(protoName) |
|
if def == nil { |
|
gen.Fail("could not find message for", protoName) |
|
} |
|
pkg := t.goPackageName(def.File) |
|
return pkg == t.genPkgName |
|
} |
|
|
|
func (t *bm) goPackageName(file *descriptor.FileDescriptorProto) string { |
|
return t.fileToGoPackageName[file] |
|
} |
|
|
|
func (t *bm) formattedOutput() string { |
|
// Reformat generated code. |
|
fset := token.NewFileSet() |
|
raw := t.output.Bytes() |
|
ast, err := parser.ParseFile(fset, "", raw, parser.ParseComments) |
|
if err != nil { |
|
// Print out the bad code with line numbers. |
|
// This should never happen in practice, but it can while changing generated code, |
|
// so consider this a debugging aid. |
|
var src bytes.Buffer |
|
s := bufio.NewScanner(bytes.NewReader(raw)) |
|
for line := 1; s.Scan(); line++ { |
|
fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes()) |
|
} |
|
gen.Fail("bad Go source code was generated:", err.Error(), "\n"+src.String()) |
|
} |
|
|
|
out := bytes.NewBuffer(nil) |
|
err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(out, fset, ast) |
|
if err != nil { |
|
gen.Fail("generated Go source code could not be reformatted:", err.Error()) |
|
} |
|
|
|
return out.String() |
|
} |
|
|
|
func serviceName(service *descriptor.ServiceDescriptorProto) string { |
|
return stringutils.CamelCase(service.GetName()) |
|
} |
|
|
|
func methodName(method *descriptor.MethodDescriptorProto) string { |
|
return stringutils.CamelCase(method.GetName()) |
|
} |
|
|
|
func fileDescSliceContains(slice []*descriptor.FileDescriptorProto, f *descriptor.FileDescriptorProto) bool { |
|
for _, sf := range slice { |
|
if f == sf { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// deduceGenPkgName figures out the go package name to use for generated code. |
|
// Will try to use the explicit go_package setting in a file (if set, must be |
|
// consistent in all files). If no files have go_package set, then use the |
|
// protobuf package name (must be consistent in all files) |
|
func deduceGenPkgName(genFiles []*descriptor.FileDescriptorProto) (string, error) { |
|
var genPkgName string |
|
for _, f := range genFiles { |
|
name, explicit := goPackageName(f) |
|
if explicit { |
|
name = stringutils.CleanIdentifier(name) |
|
if genPkgName != "" && genPkgName != name { |
|
// Make sure they're all set consistently. |
|
return "", errors.Errorf("files have conflicting go_package settings, must be the same: %q and %q", genPkgName, name) |
|
} |
|
genPkgName = name |
|
} |
|
} |
|
if genPkgName != "" { |
|
return genPkgName, nil |
|
} |
|
|
|
// If there is no explicit setting, then check the implicit package name |
|
// (derived from the protobuf package name) of the files and make sure it's |
|
// consistent. |
|
for _, f := range genFiles { |
|
name, _ := goPackageName(f) |
|
name = stringutils.CleanIdentifier(name) |
|
if genPkgName != "" && genPkgName != name { |
|
return "", errors.Errorf("files have conflicting package names, must be the same or overridden with go_package: %q and %q", genPkgName, name) |
|
} |
|
genPkgName = name |
|
} |
|
|
|
// All the files have the same name, so we're good. |
|
return genPkgName, nil |
|
}
|
|
|