gostring.go 12 KB

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