stringer.go 11 KB

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