521 lines
13 KiB
Go
521 lines
13 KiB
Go
|
// Copyright (c) 2013, Vastech SA (PTY) LTD. 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 compare
|
||
|
|
||
|
import (
|
||
|
"github.com/gogo/protobuf/gogoproto"
|
||
|
"github.com/gogo/protobuf/proto"
|
||
|
descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
|
||
|
"github.com/gogo/protobuf/protoc-gen-gogo/generator"
|
||
|
"github.com/gogo/protobuf/vanity"
|
||
|
)
|
||
|
|
||
|
type plugin struct {
|
||
|
*generator.Generator
|
||
|
generator.PluginImports
|
||
|
fmtPkg generator.Single
|
||
|
bytesPkg generator.Single
|
||
|
sortkeysPkg generator.Single
|
||
|
}
|
||
|
|
||
|
func NewPlugin() *plugin {
|
||
|
return &plugin{}
|
||
|
}
|
||
|
|
||
|
func (p *plugin) Name() string {
|
||
|
return "compare"
|
||
|
}
|
||
|
|
||
|
func (p *plugin) Init(g *generator.Generator) {
|
||
|
p.Generator = g
|
||
|
}
|
||
|
|
||
|
func (p *plugin) Generate(file *generator.FileDescriptor) {
|
||
|
p.PluginImports = generator.NewPluginImports(p.Generator)
|
||
|
p.fmtPkg = p.NewImport("fmt")
|
||
|
p.bytesPkg = p.NewImport("bytes")
|
||
|
p.sortkeysPkg = p.NewImport("github.com/gogo/protobuf/sortkeys")
|
||
|
|
||
|
for _, msg := range file.Messages() {
|
||
|
if msg.DescriptorProto.GetOptions().GetMapEntry() {
|
||
|
continue
|
||
|
}
|
||
|
if gogoproto.HasCompare(file.FileDescriptorProto, msg.DescriptorProto) {
|
||
|
p.generateMessage(file, msg)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *plugin) generateNullableField(fieldname string) {
|
||
|
p.P(`if this.`, fieldname, ` != nil && that1.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`if *this.`, fieldname, ` != *that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`if *this.`, fieldname, ` < *that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`} else if this.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`} else if that1.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
|
||
|
func (p *plugin) generateMsgNullAndTypeCheck(ccTypeName string) {
|
||
|
p.P(`if that == nil {`)
|
||
|
p.In()
|
||
|
p.P(`if this == nil {`)
|
||
|
p.In()
|
||
|
p.P(`return 0`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(``)
|
||
|
p.P(`that1, ok := that.(*`, ccTypeName, `)`)
|
||
|
p.P(`if !ok {`)
|
||
|
p.In()
|
||
|
p.P(`that2, ok := that.(`, ccTypeName, `)`)
|
||
|
p.P(`if ok {`)
|
||
|
p.In()
|
||
|
p.P(`that1 = &that2`)
|
||
|
p.Out()
|
||
|
p.P(`} else {`)
|
||
|
p.In()
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`if that1 == nil {`)
|
||
|
p.In()
|
||
|
p.P(`if this == nil {`)
|
||
|
p.In()
|
||
|
p.P(`return 0`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`} else if this == nil {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
|
||
|
func (p *plugin) generateField(file *generator.FileDescriptor, message *generator.Descriptor, field *descriptor.FieldDescriptorProto) {
|
||
|
proto3 := gogoproto.IsProto3(file.FileDescriptorProto)
|
||
|
fieldname := p.GetOneOfFieldName(message, field)
|
||
|
repeated := field.IsRepeated()
|
||
|
ctype := gogoproto.IsCustomType(field)
|
||
|
nullable := gogoproto.IsNullable(field)
|
||
|
// oneof := field.OneofIndex != nil
|
||
|
if !repeated {
|
||
|
if ctype {
|
||
|
if nullable {
|
||
|
p.P(`if that1.`, fieldname, ` == nil {`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`} else if this.`, fieldname, ` == nil {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`} else if c := this.`, fieldname, `.Compare(*that1.`, fieldname, `); c != 0 {`)
|
||
|
} else {
|
||
|
p.P(`if c := this.`, fieldname, `.Compare(that1.`, fieldname, `); c != 0 {`)
|
||
|
}
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
if field.IsMessage() || p.IsGroup(field) {
|
||
|
if nullable {
|
||
|
p.P(`if c := this.`, fieldname, `.Compare(that1.`, fieldname, `); c != 0 {`)
|
||
|
} else {
|
||
|
p.P(`if c := this.`, fieldname, `.Compare(&that1.`, fieldname, `); c != 0 {`)
|
||
|
}
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else if field.IsBytes() {
|
||
|
p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `, that1.`, fieldname, `); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else if field.IsString() {
|
||
|
if nullable && !proto3 {
|
||
|
p.generateNullableField(fieldname)
|
||
|
} else {
|
||
|
p.P(`if this.`, fieldname, ` != that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, ` < that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
} else if field.IsBool() {
|
||
|
if nullable && !proto3 {
|
||
|
p.P(`if this.`, fieldname, ` != nil && that1.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`if *this.`, fieldname, ` != *that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`if !*this.`, fieldname, ` {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`} else if this.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`} else if that1.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
p.P(`if this.`, fieldname, ` != that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`if !this.`, fieldname, ` {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
} else {
|
||
|
if nullable && !proto3 {
|
||
|
p.generateNullableField(fieldname)
|
||
|
} else {
|
||
|
p.P(`if this.`, fieldname, ` != that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, ` < that1.`, fieldname, `{`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
p.P(`if len(this.`, fieldname, `) != len(that1.`, fieldname, `) {`)
|
||
|
p.In()
|
||
|
p.P(`if len(this.`, fieldname, `) < len(that1.`, fieldname, `) {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`for i := range this.`, fieldname, ` {`)
|
||
|
p.In()
|
||
|
if ctype {
|
||
|
p.P(`if c := this.`, fieldname, `[i].Compare(that1.`, fieldname, `[i]); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
if p.IsMap(field) {
|
||
|
m := p.GoMapType(nil, field)
|
||
|
valuegoTyp, _ := p.GoType(nil, m.ValueField)
|
||
|
valuegoAliasTyp, _ := p.GoType(nil, m.ValueAliasField)
|
||
|
nullable, valuegoTyp, valuegoAliasTyp = generator.GoMapValueTypes(field, m.ValueField, valuegoTyp, valuegoAliasTyp)
|
||
|
|
||
|
mapValue := m.ValueAliasField
|
||
|
if mapValue.IsMessage() || p.IsGroup(mapValue) {
|
||
|
if nullable && valuegoTyp == valuegoAliasTyp {
|
||
|
p.P(`if c := this.`, fieldname, `[i].Compare(that1.`, fieldname, `[i]); c != 0 {`)
|
||
|
} else {
|
||
|
// Compare() has a pointer receiver, but map value is a value type
|
||
|
a := `this.` + fieldname + `[i]`
|
||
|
b := `that1.` + fieldname + `[i]`
|
||
|
if valuegoTyp != valuegoAliasTyp {
|
||
|
// cast back to the type that has the generated methods on it
|
||
|
a = `(` + valuegoTyp + `)(` + a + `)`
|
||
|
b = `(` + valuegoTyp + `)(` + b + `)`
|
||
|
}
|
||
|
p.P(`a := `, a)
|
||
|
p.P(`b := `, b)
|
||
|
if nullable {
|
||
|
p.P(`if c := a.Compare(b); c != 0 {`)
|
||
|
} else {
|
||
|
p.P(`if c := (&a).Compare(&b); c != 0 {`)
|
||
|
}
|
||
|
}
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else if mapValue.IsBytes() {
|
||
|
p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `[i], that1.`, fieldname, `[i]); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else if mapValue.IsString() {
|
||
|
p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
} else if field.IsMessage() || p.IsGroup(field) {
|
||
|
if nullable {
|
||
|
p.P(`if c := this.`, fieldname, `[i].Compare(that1.`, fieldname, `[i]); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
p.P(`if c := this.`, fieldname, `[i].Compare(&that1.`, fieldname, `[i]); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
} else if field.IsBytes() {
|
||
|
p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `[i], that1.`, fieldname, `[i]); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else if field.IsString() {
|
||
|
p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else if field.IsBool() {
|
||
|
p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`if !this.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
}
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *plugin) generateMessage(file *generator.FileDescriptor, message *generator.Descriptor) {
|
||
|
ccTypeName := generator.CamelCaseSlice(message.TypeName())
|
||
|
p.P(`func (this *`, ccTypeName, `) Compare(that interface{}) int {`)
|
||
|
p.In()
|
||
|
p.generateMsgNullAndTypeCheck(ccTypeName)
|
||
|
oneofs := make(map[string]struct{})
|
||
|
|
||
|
for _, field := range message.Field {
|
||
|
oneof := field.OneofIndex != nil
|
||
|
if oneof {
|
||
|
fieldname := p.GetFieldName(message, field)
|
||
|
if _, ok := oneofs[fieldname]; ok {
|
||
|
continue
|
||
|
} else {
|
||
|
oneofs[fieldname] = struct{}{}
|
||
|
}
|
||
|
p.P(`if that1.`, fieldname, ` == nil {`)
|
||
|
p.In()
|
||
|
p.P(`if this.`, fieldname, ` != nil {`)
|
||
|
p.In()
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`} else if this.`, fieldname, ` == nil {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`} else if c := this.`, fieldname, `.Compare(that1.`, fieldname, `); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
p.generateField(file, message, field)
|
||
|
}
|
||
|
}
|
||
|
if message.DescriptorProto.HasExtension() {
|
||
|
fieldname := "XXX_extensions"
|
||
|
if gogoproto.HasExtensionsMap(file.FileDescriptorProto, message.DescriptorProto) {
|
||
|
p.P(`extkeys := make([]int32, 0, len(this.`, fieldname, `)+len(that1.`, fieldname, `))`)
|
||
|
p.P(`for k, _ := range this.`, fieldname, ` {`)
|
||
|
p.In()
|
||
|
p.P(`extkeys = append(extkeys, k)`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(`for k, _ := range that1.`, fieldname, ` {`)
|
||
|
p.In()
|
||
|
p.P(`if _, ok := this.`, fieldname, `[k]; !ok {`)
|
||
|
p.In()
|
||
|
p.P(`extkeys = append(extkeys, k)`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.P(p.sortkeysPkg.Use(), `.Int32s(extkeys)`)
|
||
|
p.P(`for _, k := range extkeys {`)
|
||
|
p.In()
|
||
|
p.P(`if v, ok := this.`, fieldname, `[k]; ok {`)
|
||
|
p.In()
|
||
|
p.P(`if v2, ok := that1.`, fieldname, `[k]; ok {`)
|
||
|
p.In()
|
||
|
p.P(`if c := v.Compare(&v2); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`} else {`)
|
||
|
p.In()
|
||
|
p.P(`return 1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`} else {`)
|
||
|
p.In()
|
||
|
p.P(`return -1`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
} else {
|
||
|
p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `, that1.`, fieldname, `); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
}
|
||
|
if gogoproto.HasUnrecognized(file.FileDescriptorProto, message.DescriptorProto) {
|
||
|
fieldname := "XXX_unrecognized"
|
||
|
p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `, that1.`, fieldname, `); c != 0 {`)
|
||
|
p.In()
|
||
|
p.P(`return c`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
p.P(`return 0`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
|
||
|
//Generate Compare methods for oneof fields
|
||
|
m := proto.Clone(message.DescriptorProto).(*descriptor.DescriptorProto)
|
||
|
for _, field := range m.Field {
|
||
|
oneof := field.OneofIndex != nil
|
||
|
if !oneof {
|
||
|
continue
|
||
|
}
|
||
|
ccTypeName := p.OneOfTypeName(message, field)
|
||
|
p.P(`func (this *`, ccTypeName, `) Compare(that interface{}) int {`)
|
||
|
p.In()
|
||
|
|
||
|
p.generateMsgNullAndTypeCheck(ccTypeName)
|
||
|
vanity.TurnOffNullableForNativeTypesWithoutDefaultsOnly(field)
|
||
|
p.generateField(file, message, field)
|
||
|
|
||
|
p.P(`return 0`)
|
||
|
p.Out()
|
||
|
p.P(`}`)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
generator.RegisterPlugin(NewPlugin())
|
||
|
}
|