...

Source file src/github.com/gogo/protobuf/plugin/gostring/gostring.go

Documentation: github.com/gogo/protobuf/plugin/gostring

     1  // Protocol Buffers for Go with Gadgets
     2  //
     3  // Copyright (c) 2013, The GoGo Authors. All rights reserved.
     4  // http://github.com/gogo/protobuf
     5  //
     6  // Redistribution and use in source and binary forms, with or without
     7  // modification, are permitted provided that the following conditions are
     8  // met:
     9  //
    10  //     * Redistributions of source code must retain the above copyright
    11  // notice, this list of conditions and the following disclaimer.
    12  //     * Redistributions in binary form must reproduce the above
    13  // copyright notice, this list of conditions and the following disclaimer
    14  // in the documentation and/or other materials provided with the
    15  // distribution.
    16  //
    17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    18  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    19  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    20  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    21  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    22  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    23  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    24  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    25  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    26  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    27  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    28  
    29  /*
    30  The gostring plugin generates a GoString method for each message.
    31  The GoString method is called whenever you use a fmt.Printf as such:
    32  
    33    fmt.Printf("%#v", mymessage)
    34  
    35  or whenever you actually call GoString()
    36  The output produced by the GoString method can be copied from the output into code and used to set a variable.
    37  It is totally valid Go Code and is populated exactly as the struct that was printed out.
    38  
    39  It is enabled by the following extensions:
    40  
    41    - gostring
    42    - gostring_all
    43  
    44  The gostring plugin also generates a test given it is enabled using one of the following extensions:
    45  
    46    - testgen
    47    - testgen_all
    48  
    49  Let us look at:
    50  
    51    github.com/gogo/protobuf/test/example/example.proto
    52  
    53  Btw all the output can be seen at:
    54  
    55    github.com/gogo/protobuf/test/example/*
    56  
    57  The following message:
    58  
    59    option (gogoproto.gostring_all) = true;
    60  
    61    message A {
    62  	optional string Description = 1 [(gogoproto.nullable) = false];
    63  	optional int64 Number = 2 [(gogoproto.nullable) = false];
    64  	optional bytes Id = 3 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uuid", (gogoproto.nullable) = false];
    65    }
    66  
    67  given to the gostring plugin, will generate the following code:
    68  
    69    func (this *A) GoString() string {
    70  	if this == nil {
    71  		return "nil"
    72  	}
    73  	s := strings1.Join([]string{`&test.A{` + `Description:` + fmt1.Sprintf("%#v", this.Description), `Number:` + fmt1.Sprintf("%#v", this.Number), `Id:` + fmt1.Sprintf("%#v", this.Id), `XXX_unrecognized:` + fmt1.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
    74  	return s
    75    }
    76  
    77  and the following test code:
    78  
    79  	func TestAGoString(t *testing6.T) {
    80  		popr := math_rand6.New(math_rand6.NewSource(time6.Now().UnixNano()))
    81  		p := NewPopulatedA(popr, false)
    82  		s1 := p.GoString()
    83  		s2 := fmt2.Sprintf("%#v", p)
    84  		if s1 != s2 {
    85  			t.Fatalf("GoString want %v got %v", s1, s2)
    86  		}
    87  		_, err := go_parser.ParseExpr(s1)
    88  		if err != nil {
    89  			panic(err)
    90  		}
    91  	}
    92  
    93  Typically fmt.Printf("%#v") will stop to print when it reaches a pointer and
    94  not print their values, while the generated GoString method will always print all values, recursively.
    95  
    96  */
    97  package gostring
    98  
    99  import (
   100  	"fmt"
   101  	"os"
   102  	"strconv"
   103  	"strings"
   104  
   105  	"github.com/gogo/protobuf/gogoproto"
   106  	"github.com/gogo/protobuf/protoc-gen-gogo/generator"
   107  )
   108  
   109  type gostring struct {
   110  	*generator.Generator
   111  	generator.PluginImports
   112  	atleastOne bool
   113  	localName  string
   114  	overwrite  bool
   115  }
   116  
   117  func NewGoString() *gostring {
   118  	return &gostring{}
   119  }
   120  
   121  func (p *gostring) Name() string {
   122  	return "gostring"
   123  }
   124  
   125  func (p *gostring) Overwrite() {
   126  	p.overwrite = true
   127  }
   128  
   129  func (p *gostring) Init(g *generator.Generator) {
   130  	p.Generator = g
   131  }
   132  
   133  func (p *gostring) Generate(file *generator.FileDescriptor) {
   134  	proto3 := gogoproto.IsProto3(file.FileDescriptorProto)
   135  	p.PluginImports = generator.NewPluginImports(p.Generator)
   136  	p.atleastOne = false
   137  
   138  	p.localName = generator.FileName(file)
   139  
   140  	fmtPkg := p.NewImport("fmt")
   141  	stringsPkg := p.NewImport("strings")
   142  	protoPkg := p.NewImport("github.com/gogo/protobuf/proto")
   143  	if !gogoproto.ImportsGoGoProto(file.FileDescriptorProto) {
   144  		protoPkg = p.NewImport("github.com/golang/protobuf/proto")
   145  	}
   146  	sortPkg := p.NewImport("sort")
   147  	strconvPkg := p.NewImport("strconv")
   148  	reflectPkg := p.NewImport("reflect")
   149  	sortKeysPkg := p.NewImport("github.com/gogo/protobuf/sortkeys")
   150  
   151  	extensionToGoStringUsed := false
   152  	for _, message := range file.Messages() {
   153  		if !p.overwrite && !gogoproto.HasGoString(file.FileDescriptorProto, message.DescriptorProto) {
   154  			continue
   155  		}
   156  		if message.DescriptorProto.GetOptions().GetMapEntry() {
   157  			continue
   158  		}
   159  		p.atleastOne = true
   160  		packageName := file.GoPackageName()
   161  
   162  		ccTypeName := generator.CamelCaseSlice(message.TypeName())
   163  		p.P(`func (this *`, ccTypeName, `) GoString() string {`)
   164  		p.In()
   165  		p.P(`if this == nil {`)
   166  		p.In()
   167  		p.P(`return "nil"`)
   168  		p.Out()
   169  		p.P(`}`)
   170  
   171  		p.P(`s := make([]string, 0, `, strconv.Itoa(len(message.Field)+4), `)`)
   172  		p.P(`s = append(s, "&`, packageName, ".", ccTypeName, `{")`)
   173  
   174  		oneofs := make(map[string]struct{})
   175  		for _, field := range message.Field {
   176  			nullable := gogoproto.IsNullable(field)
   177  			repeated := field.IsRepeated()
   178  			fieldname := p.GetFieldName(message, field)
   179  			oneof := field.OneofIndex != nil
   180  			if oneof {
   181  				if _, ok := oneofs[fieldname]; ok {
   182  					continue
   183  				} else {
   184  					oneofs[fieldname] = struct{}{}
   185  				}
   186  				p.P(`if this.`, fieldname, ` != nil {`)
   187  				p.In()
   188  				p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
   189  				p.Out()
   190  				p.P(`}`)
   191  			} else if p.IsMap(field) {
   192  				m := p.GoMapType(nil, field)
   193  				mapgoTyp, keyField, keyAliasField := m.GoType, m.KeyField, m.KeyAliasField
   194  				keysName := `keysFor` + fieldname
   195  				keygoTyp, _ := p.GoType(nil, keyField)
   196  				keygoTyp = strings.Replace(keygoTyp, "*", "", 1)
   197  				keygoAliasTyp, _ := p.GoType(nil, keyAliasField)
   198  				keygoAliasTyp = strings.Replace(keygoAliasTyp, "*", "", 1)
   199  				keyCapTyp := generator.CamelCase(keygoTyp)
   200  				p.P(keysName, ` := make([]`, keygoTyp, `, 0, len(this.`, fieldname, `))`)
   201  				p.P(`for k, _ := range this.`, fieldname, ` {`)
   202  				p.In()
   203  				if keygoAliasTyp == keygoTyp {
   204  					p.P(keysName, ` = append(`, keysName, `, k)`)
   205  				} else {
   206  					p.P(keysName, ` = append(`, keysName, `, `, keygoTyp, `(k))`)
   207  				}
   208  				p.Out()
   209  				p.P(`}`)
   210  				p.P(sortKeysPkg.Use(), `.`, keyCapTyp, `s(`, keysName, `)`)
   211  				mapName := `mapStringFor` + fieldname
   212  				p.P(mapName, ` := "`, mapgoTyp, `{"`)
   213  				p.P(`for _, k := range `, keysName, ` {`)
   214  				p.In()
   215  				if keygoAliasTyp == keygoTyp {
   216  					p.P(mapName, ` += fmt.Sprintf("%#v: %#v,", k, this.`, fieldname, `[k])`)
   217  				} else {
   218  					p.P(mapName, ` += fmt.Sprintf("%#v: %#v,", k, this.`, fieldname, `[`, keygoAliasTyp, `(k)])`)
   219  				}
   220  				p.Out()
   221  				p.P(`}`)
   222  				p.P(mapName, ` += "}"`)
   223  				p.P(`if this.`, fieldname, ` != nil {`)
   224  				p.In()
   225  				p.P(`s = append(s, "`, fieldname, `: " + `, mapName, `+ ",\n")`)
   226  				p.Out()
   227  				p.P(`}`)
   228  			} else if (field.IsMessage() && !gogoproto.IsCustomType(field) && !gogoproto.IsStdType(field)) || p.IsGroup(field) {
   229  				if nullable || repeated {
   230  					p.P(`if this.`, fieldname, ` != nil {`)
   231  					p.In()
   232  				}
   233  				if nullable {
   234  					p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
   235  				} else if repeated {
   236  					if nullable {
   237  						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
   238  					} else {
   239  						goTyp, _ := p.GoType(message, field)
   240  						goTyp = strings.Replace(goTyp, "[]", "", 1)
   241  						p.P("vs := make([]", goTyp, ", len(this.", fieldname, "))")
   242  						p.P("for i := range vs {")
   243  						p.In()
   244  						p.P("vs[i] = this.", fieldname, "[i]")
   245  						p.Out()
   246  						p.P("}")
   247  						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", vs) + ",\n")`)
   248  					}
   249  				} else {
   250  					p.P(`s = append(s, "`, fieldname, `: " + `, stringsPkg.Use(), `.Replace(this.`, fieldname, `.GoString()`, ",`&`,``,1)", ` + ",\n")`)
   251  				}
   252  				if nullable || repeated {
   253  					p.Out()
   254  					p.P(`}`)
   255  				}
   256  			} else {
   257  				if !proto3 && (nullable || repeated) {
   258  					p.P(`if this.`, fieldname, ` != nil {`)
   259  					p.In()
   260  				}
   261  				if field.IsEnum() {
   262  					if nullable && !repeated && !proto3 {
   263  						goTyp, _ := p.GoType(message, field)
   264  						p.P(`s = append(s, "`, fieldname, `: " + valueToGoString`, p.localName, `(this.`, fieldname, `,"`, generator.GoTypeToName(goTyp), `"`, `) + ",\n")`)
   265  					} else {
   266  						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
   267  					}
   268  				} else {
   269  					if nullable && !repeated && !proto3 {
   270  						goTyp, _ := p.GoType(message, field)
   271  						p.P(`s = append(s, "`, fieldname, `: " + valueToGoString`, p.localName, `(this.`, fieldname, `,"`, generator.GoTypeToName(goTyp), `"`, `) + ",\n")`)
   272  					} else {
   273  						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
   274  					}
   275  				}
   276  				if !proto3 && (nullable || repeated) {
   277  					p.Out()
   278  					p.P(`}`)
   279  				}
   280  			}
   281  		}
   282  		if message.DescriptorProto.HasExtension() {
   283  			if gogoproto.HasExtensionsMap(file.FileDescriptorProto, message.DescriptorProto) {
   284  				p.P(`s = append(s, "XXX_InternalExtensions: " + extensionToGoString`, p.localName, `(this) + ",\n")`)
   285  				extensionToGoStringUsed = true
   286  			} else {
   287  				p.P(`if this.XXX_extensions != nil {`)
   288  				p.In()
   289  				p.P(`s = append(s, "XXX_extensions: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.XXX_extensions) + ",\n")`)
   290  				p.Out()
   291  				p.P(`}`)
   292  			}
   293  		}
   294  		if gogoproto.HasUnrecognized(file.FileDescriptorProto, message.DescriptorProto) {
   295  			p.P(`if this.XXX_unrecognized != nil {`)
   296  			p.In()
   297  			p.P(`s = append(s, "XXX_unrecognized:" + `, fmtPkg.Use(), `.Sprintf("%#v", this.XXX_unrecognized) + ",\n")`)
   298  			p.Out()
   299  			p.P(`}`)
   300  		}
   301  
   302  		p.P(`s = append(s, "}")`)
   303  		p.P(`return `, stringsPkg.Use(), `.Join(s, "")`)
   304  		p.Out()
   305  		p.P(`}`)
   306  
   307  		//Generate GoString methods for oneof fields
   308  		for _, field := range message.Field {
   309  			oneof := field.OneofIndex != nil
   310  			if !oneof {
   311  				continue
   312  			}
   313  			ccTypeName := p.OneOfTypeName(message, field)
   314  			p.P(`func (this *`, ccTypeName, `) GoString() string {`)
   315  			p.In()
   316  			p.P(`if this == nil {`)
   317  			p.In()
   318  			p.P(`return "nil"`)
   319  			p.Out()
   320  			p.P(`}`)
   321  			fieldname := p.GetOneOfFieldName(message, field)
   322  			outStr := strings.Join([]string{
   323  				"s := ",
   324  				stringsPkg.Use(), ".Join([]string{`&", packageName, ".", ccTypeName, "{` + \n",
   325  				"`", fieldname, ":` + ", fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `)`,
   326  				" + `}`",
   327  				`}`,
   328  				`,", "`,
   329  				`)`}, "")
   330  			p.P(outStr)
   331  			p.P(`return s`)
   332  			p.Out()
   333  			p.P(`}`)
   334  		}
   335  	}
   336  
   337  	if !p.atleastOne {
   338  		return
   339  	}
   340  
   341  	p.P(`func valueToGoString`, p.localName, `(v interface{}, typ string) string {`)
   342  	p.In()
   343  	p.P(`rv := `, reflectPkg.Use(), `.ValueOf(v)`)
   344  	p.P(`if rv.IsNil() {`)
   345  	p.In()
   346  	p.P(`return "nil"`)
   347  	p.Out()
   348  	p.P(`}`)
   349  	p.P(`pv := `, reflectPkg.Use(), `.Indirect(rv).Interface()`)
   350  	p.P(`return `, fmtPkg.Use(), `.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)`)
   351  	p.Out()
   352  	p.P(`}`)
   353  
   354  	if extensionToGoStringUsed {
   355  		if !gogoproto.ImportsGoGoProto(file.FileDescriptorProto) {
   356  			fmt.Fprintf(os.Stderr, "The GoString plugin for messages with extensions requires importing gogoprotobuf. Please see file %s", file.GetName())
   357  			os.Exit(1)
   358  		}
   359  		p.P(`func extensionToGoString`, p.localName, `(m `, protoPkg.Use(), `.Message) string {`)
   360  		p.In()
   361  		p.P(`e := `, protoPkg.Use(), `.GetUnsafeExtensionsMap(m)`)
   362  		p.P(`if e == nil { return "nil" }`)
   363  		p.P(`s := "proto.NewUnsafeXXX_InternalExtensions(map[int32]proto.Extension{"`)
   364  		p.P(`keys := make([]int, 0, len(e))`)
   365  		p.P(`for k := range e {`)
   366  		p.In()
   367  		p.P(`keys = append(keys, int(k))`)
   368  		p.Out()
   369  		p.P(`}`)
   370  		p.P(sortPkg.Use(), `.Ints(keys)`)
   371  		p.P(`ss := []string{}`)
   372  		p.P(`for _, k := range keys {`)
   373  		p.In()
   374  		p.P(`ss = append(ss, `, strconvPkg.Use(), `.Itoa(k) + ": " + e[int32(k)].GoString())`)
   375  		p.Out()
   376  		p.P(`}`)
   377  		p.P(`s+=`, stringsPkg.Use(), `.Join(ss, ",") + "})"`)
   378  		p.P(`return s`)
   379  		p.Out()
   380  		p.P(`}`)
   381  	}
   382  }
   383  
   384  func init() {
   385  	generator.RegisterPlugin(NewGoString())
   386  }
   387  

View as plain text