...

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

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

     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 stringer plugin generates a String method for each message.
    31  
    32  It is enabled by the following extensions:
    33  
    34    - stringer
    35    - stringer_all
    36  
    37  The stringer plugin also generates a test given it is enabled using one of the following extensions:
    38  
    39    - testgen
    40    - testgen_all
    41  
    42  Let us look at:
    43  
    44    github.com/gogo/protobuf/test/example/example.proto
    45  
    46  Btw all the output can be seen at:
    47  
    48    github.com/gogo/protobuf/test/example/*
    49  
    50  The following message:
    51  
    52    option (gogoproto.goproto_stringer_all) = false;
    53    option (gogoproto.stringer_all) =  true;
    54  
    55    message A {
    56  	optional string Description = 1 [(gogoproto.nullable) = false];
    57  	optional int64 Number = 2 [(gogoproto.nullable) = false];
    58  	optional bytes Id = 3 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uuid", (gogoproto.nullable) = false];
    59    }
    60  
    61  given to the stringer stringer, will generate the following code:
    62  
    63    func (this *A) String() string {
    64  	if this == nil {
    65  		return "nil"
    66  	}
    67  	s := strings.Join([]string{`&A{`,
    68  		`Description:` + fmt.Sprintf("%v", this.Description) + `,`,
    69  		`Number:` + fmt.Sprintf("%v", this.Number) + `,`,
    70  		`Id:` + fmt.Sprintf("%v", this.Id) + `,`,
    71  		`XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`,
    72  		`}`,
    73  	}, "")
    74  	return s
    75    }
    76  
    77  and the following test code:
    78  
    79  	func TestAStringer(t *testing4.T) {
    80  		popr := math_rand4.New(math_rand4.NewSource(time4.Now().UnixNano()))
    81  		p := NewPopulatedA(popr, false)
    82  		s1 := p.String()
    83  		s2 := fmt1.Sprintf("%v", p)
    84  		if s1 != s2 {
    85  			t.Fatalf("String want %v got %v", s1, s2)
    86  		}
    87  	}
    88  
    89  Typically fmt.Printf("%v") will stop to print when it reaches a pointer and
    90  not print their values, while the generated String method will always print all values, recursively.
    91  
    92  */
    93  package stringer
    94  
    95  import (
    96  	"github.com/gogo/protobuf/gogoproto"
    97  	"github.com/gogo/protobuf/protoc-gen-gogo/generator"
    98  	"strings"
    99  )
   100  
   101  type stringer struct {
   102  	*generator.Generator
   103  	generator.PluginImports
   104  	atleastOne bool
   105  	localName  string
   106  }
   107  
   108  func NewStringer() *stringer {
   109  	return &stringer{}
   110  }
   111  
   112  func (p *stringer) Name() string {
   113  	return "stringer"
   114  }
   115  
   116  func (p *stringer) Init(g *generator.Generator) {
   117  	p.Generator = g
   118  }
   119  
   120  func (p *stringer) Generate(file *generator.FileDescriptor) {
   121  	proto3 := gogoproto.IsProto3(file.FileDescriptorProto)
   122  	p.PluginImports = generator.NewPluginImports(p.Generator)
   123  	p.atleastOne = false
   124  
   125  	p.localName = generator.FileName(file)
   126  
   127  	fmtPkg := p.NewImport("fmt")
   128  	stringsPkg := p.NewImport("strings")
   129  	reflectPkg := p.NewImport("reflect")
   130  	sortKeysPkg := p.NewImport("github.com/gogo/protobuf/sortkeys")
   131  	protoPkg := p.NewImport("github.com/gogo/protobuf/proto")
   132  	for _, message := range file.Messages() {
   133  		if !gogoproto.IsStringer(file.FileDescriptorProto, message.DescriptorProto) {
   134  			continue
   135  		}
   136  		if gogoproto.EnabledGoStringer(file.FileDescriptorProto, message.DescriptorProto) {
   137  			panic("old string method needs to be disabled, please use gogoproto.goproto_stringer or gogoproto.goproto_stringer_all and set it to false")
   138  		}
   139  		if message.DescriptorProto.GetOptions().GetMapEntry() {
   140  			continue
   141  		}
   142  		p.atleastOne = true
   143  		ccTypeName := generator.CamelCaseSlice(message.TypeName())
   144  		p.P(`func (this *`, ccTypeName, `) String() string {`)
   145  		p.In()
   146  		p.P(`if this == nil {`)
   147  		p.In()
   148  		p.P(`return "nil"`)
   149  		p.Out()
   150  		p.P(`}`)
   151  		for _, field := range message.Field {
   152  			if p.IsMap(field) || !field.IsRepeated() {
   153  				continue
   154  			}
   155  			if (field.IsMessage() && !gogoproto.IsCustomType(field)) || p.IsGroup(field) {
   156  				nullable := gogoproto.IsNullable(field)
   157  				desc := p.ObjectNamed(field.GetTypeName())
   158  				msgname := p.TypeName(desc)
   159  				msgnames := strings.Split(msgname, ".")
   160  				typeName := msgnames[len(msgnames)-1]
   161  				fieldMessageDesc := file.GetMessage(msgname)
   162  				gogoStringer := false
   163  				if fieldMessageDesc != nil {
   164  					gogoStringer = gogoproto.IsStringer(file.FileDescriptorProto, fieldMessageDesc)
   165  				}
   166  				fieldname := p.GetFieldName(message, field)
   167  				stringfunc := fmtPkg.Use() + `.Sprintf("%v", f)`
   168  				if gogoStringer {
   169  					stringfunc = `f.String()`
   170  				}
   171  				repeatedName := `repeatedStringFor` + fieldname
   172  				if nullable {
   173  					p.P(repeatedName, ` := "[]*`, typeName, `{"`)
   174  				} else {
   175  					p.P(repeatedName, ` := "[]`, typeName, `{"`)
   176  				}
   177  
   178  				p.P(`for _, f := range `, `this.`, fieldname, ` {`)
   179  				p.In()
   180  				if nullable {
   181  					p.P(repeatedName, " += ", stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1)", ` + ","`)
   182  				} else if gogoStringer {
   183  					p.P(repeatedName, " += ", stringsPkg.Use(), `.Replace(`, stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1),`&`,``,1)", ` + ","`)
   184  				} else {
   185  					p.P(repeatedName, " += ", stringfunc, ` + ","`)
   186  				}
   187  				p.Out()
   188  				p.P(`}`)
   189  				p.P(repeatedName, ` += "}"`)
   190  			}
   191  		}
   192  		for _, field := range message.Field {
   193  			if !p.IsMap(field) {
   194  				continue
   195  			}
   196  			fieldname := p.GetFieldName(message, field)
   197  
   198  			m := p.GoMapType(nil, field)
   199  			mapgoTyp, keyField, keyAliasField := m.GoType, m.KeyField, m.KeyAliasField
   200  			keysName := `keysFor` + fieldname
   201  			keygoTyp, _ := p.GoType(nil, keyField)
   202  			keygoTyp = strings.Replace(keygoTyp, "*", "", 1)
   203  			keygoAliasTyp, _ := p.GoType(nil, keyAliasField)
   204  			keygoAliasTyp = strings.Replace(keygoAliasTyp, "*", "", 1)
   205  			keyCapTyp := generator.CamelCase(keygoTyp)
   206  			p.P(keysName, ` := make([]`, keygoTyp, `, 0, len(this.`, fieldname, `))`)
   207  			p.P(`for k, _ := range this.`, fieldname, ` {`)
   208  			p.In()
   209  			if keygoAliasTyp == keygoTyp {
   210  				p.P(keysName, ` = append(`, keysName, `, k)`)
   211  			} else {
   212  				p.P(keysName, ` = append(`, keysName, `, `, keygoTyp, `(k))`)
   213  			}
   214  			p.Out()
   215  			p.P(`}`)
   216  			p.P(sortKeysPkg.Use(), `.`, keyCapTyp, `s(`, keysName, `)`)
   217  			mapName := `mapStringFor` + fieldname
   218  			p.P(mapName, ` := "`, mapgoTyp, `{"`)
   219  			p.P(`for _, k := range `, keysName, ` {`)
   220  			p.In()
   221  			if keygoAliasTyp == keygoTyp {
   222  				p.P(mapName, ` += fmt.Sprintf("%v: %v,", k, this.`, fieldname, `[k])`)
   223  			} else {
   224  				p.P(mapName, ` += fmt.Sprintf("%v: %v,", k, this.`, fieldname, `[`, keygoAliasTyp, `(k)])`)
   225  			}
   226  			p.Out()
   227  			p.P(`}`)
   228  			p.P(mapName, ` += "}"`)
   229  		}
   230  		p.P("s := ", stringsPkg.Use(), ".Join([]string{`&", ccTypeName, "{`,")
   231  		oneofs := make(map[string]struct{})
   232  		for _, field := range message.Field {
   233  			nullable := gogoproto.IsNullable(field)
   234  			repeated := field.IsRepeated()
   235  			fieldname := p.GetFieldName(message, field)
   236  			oneof := field.OneofIndex != nil
   237  			if oneof {
   238  				if _, ok := oneofs[fieldname]; ok {
   239  					continue
   240  				} else {
   241  					oneofs[fieldname] = struct{}{}
   242  				}
   243  				p.P("`", fieldname, ":`", ` + `, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, ") + `,", "`,")
   244  			} else if p.IsMap(field) {
   245  				mapName := `mapStringFor` + fieldname
   246  				p.P("`", fieldname, ":`", ` + `, mapName, " + `,", "`,")
   247  			} else if (field.IsMessage() && !gogoproto.IsCustomType(field)) || p.IsGroup(field) {
   248  				desc := p.ObjectNamed(field.GetTypeName())
   249  				msgname := p.TypeName(desc)
   250  				msgnames := strings.Split(msgname, ".")
   251  				typeName := msgnames[len(msgnames)-1]
   252  				fieldMessageDesc := file.GetMessage(msgname)
   253  				gogoStringer := false
   254  				if fieldMessageDesc != nil {
   255  					gogoStringer = gogoproto.IsStringer(file.FileDescriptorProto, fieldMessageDesc)
   256  				}
   257  				stringfunc := fmtPkg.Use() + `.Sprintf("%v", this.` + fieldname + `)`
   258  				if gogoStringer {
   259  					stringfunc = `this.` + fieldname + `.String()`
   260  				}
   261  				if nullable && !repeated {
   262  					p.P("`", fieldname, ":`", ` + `, stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1) + `,", "`,")
   263  				} else if repeated {
   264  					repeatedName := `repeatedStringFor` + fieldname
   265  					p.P("`", fieldname, ":`", ` + `, repeatedName, " + `,", "`,")
   266  				} else {
   267  					p.P("`", fieldname, ":`", ` + `, stringsPkg.Use(), `.Replace(`, stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1),`&`,``,1) + `,", "`,")
   268  				}
   269  			} else {
   270  				if nullable && !repeated && !proto3 {
   271  					p.P("`", fieldname, ":`", ` + valueToString`, p.localName, `(this.`, fieldname, ") + `,", "`,")
   272  				} else {
   273  					p.P("`", fieldname, ":`", ` + `, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, ") + `,", "`,")
   274  				}
   275  			}
   276  		}
   277  		if message.DescriptorProto.HasExtension() {
   278  			if gogoproto.HasExtensionsMap(file.FileDescriptorProto, message.DescriptorProto) {
   279  				p.P("`XXX_InternalExtensions:` + ", protoPkg.Use(), ".StringFromInternalExtension(this) + `,`,")
   280  			} else {
   281  				p.P("`XXX_extensions:` + ", protoPkg.Use(), ".StringFromExtensionsBytes(this.XXX_extensions) + `,`,")
   282  			}
   283  		}
   284  		if gogoproto.HasUnrecognized(file.FileDescriptorProto, message.DescriptorProto) {
   285  			p.P("`XXX_unrecognized:` + ", fmtPkg.Use(), `.Sprintf("%v", this.XXX_unrecognized) + `, "`,`,")
   286  		}
   287  		p.P("`}`,")
   288  		p.P(`}`, `,""`, ")")
   289  		p.P(`return s`)
   290  		p.Out()
   291  		p.P(`}`)
   292  
   293  		//Generate String methods for oneof fields
   294  		for _, field := range message.Field {
   295  			oneof := field.OneofIndex != nil
   296  			if !oneof {
   297  				continue
   298  			}
   299  			ccTypeName := p.OneOfTypeName(message, field)
   300  			p.P(`func (this *`, ccTypeName, `) String() string {`)
   301  			p.In()
   302  			p.P(`if this == nil {`)
   303  			p.In()
   304  			p.P(`return "nil"`)
   305  			p.Out()
   306  			p.P(`}`)
   307  			p.P("s := ", stringsPkg.Use(), ".Join([]string{`&", ccTypeName, "{`,")
   308  			fieldname := p.GetOneOfFieldName(message, field)
   309  			if field.IsMessage() || p.IsGroup(field) {
   310  				desc := p.ObjectNamed(field.GetTypeName())
   311  				msgname := p.TypeName(desc)
   312  				msgnames := strings.Split(msgname, ".")
   313  				typeName := msgnames[len(msgnames)-1]
   314  				p.P("`", fieldname, ":`", ` + `, stringsPkg.Use(), `.Replace(`, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, `), "`, typeName, `","`, msgname, `"`, ", 1) + `,", "`,")
   315  			} else {
   316  				p.P("`", fieldname, ":`", ` + `, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, ") + `,", "`,")
   317  			}
   318  			p.P("`}`,")
   319  			p.P(`}`, `,""`, ")")
   320  			p.P(`return s`)
   321  			p.Out()
   322  			p.P(`}`)
   323  		}
   324  	}
   325  
   326  	if !p.atleastOne {
   327  		return
   328  	}
   329  
   330  	p.P(`func valueToString`, p.localName, `(v interface{}) string {`)
   331  	p.In()
   332  	p.P(`rv := `, reflectPkg.Use(), `.ValueOf(v)`)
   333  	p.P(`if rv.IsNil() {`)
   334  	p.In()
   335  	p.P(`return "nil"`)
   336  	p.Out()
   337  	p.P(`}`)
   338  	p.P(`pv := `, reflectPkg.Use(), `.Indirect(rv).Interface()`)
   339  	p.P(`return `, fmtPkg.Use(), `.Sprintf("*%v", pv)`)
   340  	p.Out()
   341  	p.P(`}`)
   342  
   343  }
   344  
   345  func init() {
   346  	generator.RegisterPlugin(NewStringer())
   347  }
   348  

View as plain text