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.
446 lines
13 KiB
446 lines
13 KiB
// Protocol Buffers for Go with Gadgets |
|
// |
|
// Copyright (c) 2013, The GoGo Authors. All rights reserved. |
|
// http://github.com/gogo/protobuf |
|
// |
|
// Redistribution and use in source and binary forms, with or without |
|
// modification, are permitted provided that the following conditions are |
|
// met: |
|
// |
|
// * Redistributions of source code must retain the above copyright |
|
// notice, this list of conditions and the following disclaimer. |
|
// * Redistributions in binary form must reproduce the above |
|
// copyright notice, this list of conditions and the following disclaimer |
|
// in the documentation and/or other materials provided with the |
|
// distribution. |
|
// |
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
package generator |
|
|
|
import ( |
|
"bytes" |
|
"go/parser" |
|
"go/printer" |
|
"go/token" |
|
"path" |
|
"strings" |
|
|
|
"github.com/gogo/protobuf/gogoproto" |
|
"github.com/gogo/protobuf/proto" |
|
descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" |
|
plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin" |
|
) |
|
|
|
func (d *FileDescriptor) Messages() []*Descriptor { |
|
return d.desc |
|
} |
|
|
|
func (d *FileDescriptor) Enums() []*EnumDescriptor { |
|
return d.enum |
|
} |
|
|
|
func (d *Descriptor) IsGroup() bool { |
|
return d.group |
|
} |
|
|
|
func (g *Generator) IsGroup(field *descriptor.FieldDescriptorProto) bool { |
|
if d, ok := g.typeNameToObject[field.GetTypeName()].(*Descriptor); ok { |
|
return d.IsGroup() |
|
} |
|
return false |
|
} |
|
|
|
func (g *Generator) TypeNameByObject(typeName string) Object { |
|
o, ok := g.typeNameToObject[typeName] |
|
if !ok { |
|
g.Fail("can't find object with type", typeName) |
|
} |
|
return o |
|
} |
|
|
|
func (g *Generator) OneOfTypeName(message *Descriptor, field *descriptor.FieldDescriptorProto) string { |
|
typeName := message.TypeName() |
|
ccTypeName := CamelCaseSlice(typeName) |
|
fieldName := g.GetOneOfFieldName(message, field) |
|
tname := ccTypeName + "_" + fieldName |
|
// It is possible for this to collide with a message or enum |
|
// nested in this message. Check for collisions. |
|
ok := true |
|
for _, desc := range message.nested { |
|
if strings.Join(desc.TypeName(), "_") == tname { |
|
ok = false |
|
break |
|
} |
|
} |
|
for _, enum := range message.enums { |
|
if strings.Join(enum.TypeName(), "_") == tname { |
|
ok = false |
|
break |
|
} |
|
} |
|
if !ok { |
|
tname += "_" |
|
} |
|
return tname |
|
} |
|
|
|
type PluginImports interface { |
|
NewImport(pkg string) Single |
|
GenerateImports(file *FileDescriptor) |
|
} |
|
|
|
type pluginImports struct { |
|
generator *Generator |
|
singles []Single |
|
} |
|
|
|
func NewPluginImports(generator *Generator) *pluginImports { |
|
return &pluginImports{generator, make([]Single, 0)} |
|
} |
|
|
|
func (this *pluginImports) NewImport(pkg string) Single { |
|
imp := newImportedPackage(this.generator.ImportPrefix, pkg) |
|
this.singles = append(this.singles, imp) |
|
return imp |
|
} |
|
|
|
func (this *pluginImports) GenerateImports(file *FileDescriptor) { |
|
for _, s := range this.singles { |
|
if s.IsUsed() { |
|
this.generator.PrintImport(GoPackageName(s.Name()), GoImportPath(s.Location())) |
|
} |
|
} |
|
} |
|
|
|
type Single interface { |
|
Use() string |
|
IsUsed() bool |
|
Name() string |
|
Location() string |
|
} |
|
|
|
type importedPackage struct { |
|
used bool |
|
pkg string |
|
name string |
|
importPrefix string |
|
} |
|
|
|
func newImportedPackage(importPrefix string, pkg string) *importedPackage { |
|
return &importedPackage{ |
|
pkg: pkg, |
|
importPrefix: importPrefix, |
|
} |
|
} |
|
|
|
func (this *importedPackage) Use() string { |
|
if !this.used { |
|
this.name = string(cleanPackageName(this.pkg)) |
|
this.used = true |
|
} |
|
return this.name |
|
} |
|
|
|
func (this *importedPackage) IsUsed() bool { |
|
return this.used |
|
} |
|
|
|
func (this *importedPackage) Name() string { |
|
return this.name |
|
} |
|
|
|
func (this *importedPackage) Location() string { |
|
return this.importPrefix + this.pkg |
|
} |
|
|
|
func (g *Generator) GetFieldName(message *Descriptor, field *descriptor.FieldDescriptorProto) string { |
|
goTyp, _ := g.GoType(message, field) |
|
fieldname := CamelCase(*field.Name) |
|
if gogoproto.IsCustomName(field) { |
|
fieldname = gogoproto.GetCustomName(field) |
|
} |
|
if gogoproto.IsEmbed(field) { |
|
fieldname = EmbedFieldName(goTyp) |
|
} |
|
if field.OneofIndex != nil { |
|
fieldname = message.OneofDecl[int(*field.OneofIndex)].GetName() |
|
fieldname = CamelCase(fieldname) |
|
} |
|
for _, f := range methodNames { |
|
if f == fieldname { |
|
return fieldname + "_" |
|
} |
|
} |
|
if !gogoproto.IsProtoSizer(message.file.FileDescriptorProto, message.DescriptorProto) { |
|
if fieldname == "Size" { |
|
return fieldname + "_" |
|
} |
|
} |
|
return fieldname |
|
} |
|
|
|
func (g *Generator) GetOneOfFieldName(message *Descriptor, field *descriptor.FieldDescriptorProto) string { |
|
goTyp, _ := g.GoType(message, field) |
|
fieldname := CamelCase(*field.Name) |
|
if gogoproto.IsCustomName(field) { |
|
fieldname = gogoproto.GetCustomName(field) |
|
} |
|
if gogoproto.IsEmbed(field) { |
|
fieldname = EmbedFieldName(goTyp) |
|
} |
|
for _, f := range methodNames { |
|
if f == fieldname { |
|
return fieldname + "_" |
|
} |
|
} |
|
if !gogoproto.IsProtoSizer(message.file.FileDescriptorProto, message.DescriptorProto) { |
|
if fieldname == "Size" { |
|
return fieldname + "_" |
|
} |
|
} |
|
return fieldname |
|
} |
|
|
|
func (g *Generator) IsMap(field *descriptor.FieldDescriptorProto) bool { |
|
if !field.IsMessage() { |
|
return false |
|
} |
|
byName := g.ObjectNamed(field.GetTypeName()) |
|
desc, ok := byName.(*Descriptor) |
|
if byName == nil || !ok || !desc.GetOptions().GetMapEntry() { |
|
return false |
|
} |
|
return true |
|
} |
|
|
|
func (g *Generator) GetMapKeyField(field, keyField *descriptor.FieldDescriptorProto) *descriptor.FieldDescriptorProto { |
|
if !gogoproto.IsCastKey(field) { |
|
return keyField |
|
} |
|
keyField = proto.Clone(keyField).(*descriptor.FieldDescriptorProto) |
|
if keyField.Options == nil { |
|
keyField.Options = &descriptor.FieldOptions{} |
|
} |
|
keyType := gogoproto.GetCastKey(field) |
|
if err := proto.SetExtension(keyField.Options, gogoproto.E_Casttype, &keyType); err != nil { |
|
g.Fail(err.Error()) |
|
} |
|
return keyField |
|
} |
|
|
|
func (g *Generator) GetMapValueField(field, valField *descriptor.FieldDescriptorProto) *descriptor.FieldDescriptorProto { |
|
if gogoproto.IsCustomType(field) && gogoproto.IsCastValue(field) { |
|
g.Fail("cannot have a customtype and casttype: ", field.String()) |
|
} |
|
valField = proto.Clone(valField).(*descriptor.FieldDescriptorProto) |
|
if valField.Options == nil { |
|
valField.Options = &descriptor.FieldOptions{} |
|
} |
|
|
|
stdtime := gogoproto.IsStdTime(field) |
|
if stdtime { |
|
if err := proto.SetExtension(valField.Options, gogoproto.E_Stdtime, &stdtime); err != nil { |
|
g.Fail(err.Error()) |
|
} |
|
} |
|
|
|
stddur := gogoproto.IsStdDuration(field) |
|
if stddur { |
|
if err := proto.SetExtension(valField.Options, gogoproto.E_Stdduration, &stddur); err != nil { |
|
g.Fail(err.Error()) |
|
} |
|
} |
|
|
|
if valType := gogoproto.GetCastValue(field); len(valType) > 0 { |
|
if err := proto.SetExtension(valField.Options, gogoproto.E_Casttype, &valType); err != nil { |
|
g.Fail(err.Error()) |
|
} |
|
} |
|
if valType := gogoproto.GetCustomType(field); len(valType) > 0 { |
|
if err := proto.SetExtension(valField.Options, gogoproto.E_Customtype, &valType); err != nil { |
|
g.Fail(err.Error()) |
|
} |
|
} |
|
|
|
nullable := gogoproto.IsNullable(field) |
|
if err := proto.SetExtension(valField.Options, gogoproto.E_Nullable, &nullable); err != nil { |
|
g.Fail(err.Error()) |
|
} |
|
return valField |
|
} |
|
|
|
// GoMapValueTypes returns the map value Go type and the alias map value Go type (for casting), taking into |
|
// account whether the map is nullable or the value is a message. |
|
func GoMapValueTypes(mapField, valueField *descriptor.FieldDescriptorProto, goValueType, goValueAliasType string) (nullable bool, outGoType string, outGoAliasType string) { |
|
nullable = gogoproto.IsNullable(mapField) && (valueField.IsMessage() || gogoproto.IsCustomType(mapField)) |
|
if nullable { |
|
// ensure the non-aliased Go value type is a pointer for consistency |
|
if strings.HasPrefix(goValueType, "*") { |
|
outGoType = goValueType |
|
} else { |
|
outGoType = "*" + goValueType |
|
} |
|
outGoAliasType = goValueAliasType |
|
} else { |
|
outGoType = strings.Replace(goValueType, "*", "", 1) |
|
outGoAliasType = strings.Replace(goValueAliasType, "*", "", 1) |
|
} |
|
return |
|
} |
|
|
|
func GoTypeToName(goTyp string) string { |
|
return strings.Replace(strings.Replace(goTyp, "*", "", -1), "[]", "", -1) |
|
} |
|
|
|
func EmbedFieldName(goTyp string) string { |
|
goTyp = GoTypeToName(goTyp) |
|
goTyps := strings.Split(goTyp, ".") |
|
if len(goTyps) == 1 { |
|
return goTyp |
|
} |
|
if len(goTyps) == 2 { |
|
return goTyps[1] |
|
} |
|
panic("unreachable") |
|
} |
|
|
|
func (g *Generator) GeneratePlugin(p Plugin) { |
|
plugins = []Plugin{p} |
|
p.Init(g) |
|
// Generate the output. The generator runs for every file, even the files |
|
// that we don't generate output for, so that we can collate the full list |
|
// of exported symbols to support public imports. |
|
genFileMap := make(map[*FileDescriptor]bool, len(g.genFiles)) |
|
for _, file := range g.genFiles { |
|
genFileMap[file] = true |
|
} |
|
for _, file := range g.allFiles { |
|
g.Reset() |
|
g.writeOutput = genFileMap[file] |
|
g.generatePlugin(file, p) |
|
if !g.writeOutput { |
|
continue |
|
} |
|
g.Response.File = append(g.Response.File, &plugin.CodeGeneratorResponse_File{ |
|
Name: proto.String(file.goFileName(g.pathType)), |
|
Content: proto.String(g.String()), |
|
}) |
|
} |
|
} |
|
|
|
func (g *Generator) generatePlugin(file *FileDescriptor, p Plugin) { |
|
g.writtenImports = make(map[string]bool) |
|
g.file = file |
|
|
|
// Run the plugins before the imports so we know which imports are necessary. |
|
p.Generate(file) |
|
|
|
// Generate header and imports last, though they appear first in the output. |
|
rem := g.Buffer |
|
g.Buffer = new(bytes.Buffer) |
|
g.generateHeader() |
|
p.GenerateImports(g.file) |
|
g.generateImports() |
|
if !g.writeOutput { |
|
return |
|
} |
|
g.Write(rem.Bytes()) |
|
|
|
// Reformat generated code. |
|
contents := string(g.Buffer.Bytes()) |
|
fset := token.NewFileSet() |
|
ast, err := parser.ParseFile(fset, "", g, parser.ParseComments) |
|
if err != nil { |
|
g.Fail("bad Go source code was generated:", contents, err.Error()) |
|
return |
|
} |
|
g.Reset() |
|
err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(g, fset, ast) |
|
if err != nil { |
|
g.Fail("generated Go source code could not be reformatted:", err.Error()) |
|
} |
|
} |
|
|
|
func GetCustomType(field *descriptor.FieldDescriptorProto) (packageName string, typ string, err error) { |
|
return getCustomType(field) |
|
} |
|
|
|
func getCustomType(field *descriptor.FieldDescriptorProto) (packageName string, typ string, err error) { |
|
if field.Options != nil { |
|
var v interface{} |
|
v, err = proto.GetExtension(field.Options, gogoproto.E_Customtype) |
|
if err == nil && v.(*string) != nil { |
|
ctype := *(v.(*string)) |
|
packageName, typ = splitCPackageType(ctype) |
|
return packageName, typ, nil |
|
} |
|
} |
|
return "", "", err |
|
} |
|
|
|
func splitCPackageType(ctype string) (packageName string, typ string) { |
|
ss := strings.Split(ctype, ".") |
|
if len(ss) == 1 { |
|
return "", ctype |
|
} |
|
packageName = strings.Join(ss[0:len(ss)-1], ".") |
|
typeName := ss[len(ss)-1] |
|
importStr := strings.Map(badToUnderscore, packageName) |
|
typ = importStr + "." + typeName |
|
return packageName, typ |
|
} |
|
|
|
func getCastType(field *descriptor.FieldDescriptorProto) (packageName string, typ string, err error) { |
|
if field.Options != nil { |
|
var v interface{} |
|
v, err = proto.GetExtension(field.Options, gogoproto.E_Casttype) |
|
if err == nil && v.(*string) != nil { |
|
ctype := *(v.(*string)) |
|
packageName, typ = splitCPackageType(ctype) |
|
return packageName, typ, nil |
|
} |
|
} |
|
return "", "", err |
|
} |
|
|
|
func FileName(file *FileDescriptor) string { |
|
fname := path.Base(file.FileDescriptorProto.GetName()) |
|
fname = strings.Replace(fname, ".proto", "", -1) |
|
fname = strings.Replace(fname, "-", "_", -1) |
|
fname = strings.Replace(fname, ".", "_", -1) |
|
return CamelCase(fname) |
|
} |
|
|
|
func (g *Generator) AllFiles() *descriptor.FileDescriptorSet { |
|
set := &descriptor.FileDescriptorSet{} |
|
set.File = make([]*descriptor.FileDescriptorProto, len(g.allFiles)) |
|
for i := range g.allFiles { |
|
set.File[i] = g.allFiles[i].FileDescriptorProto |
|
} |
|
return set |
|
} |
|
|
|
func (d *Descriptor) Path() string { |
|
return d.path |
|
} |
|
|
|
func (g *Generator) useTypes() string { |
|
pkg := strings.Map(badToUnderscore, "github.com/gogo/protobuf/types") |
|
g.customImports = append(g.customImports, "github.com/gogo/protobuf/types") |
|
return pkg |
|
} |
|
|
|
func (d *FileDescriptor) GoPackageName() string { |
|
return string(d.packageName) |
|
}
|
|
|