proto.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package main
  2. import (
  3. "bytes"
  4. "io/ioutil"
  5. "log"
  6. "path"
  7. "path/filepath"
  8. "regexp"
  9. "sort"
  10. "strconv"
  11. "strings"
  12. "unicode"
  13. )
  14. type ProtoInfo struct {
  15. src []string
  16. importPath string
  17. packageName string
  18. imports []string
  19. isGogo bool
  20. hasServices bool
  21. }
  22. var protoRe = buildProtoRegexp()
  23. const (
  24. importSubexpIndex = 1
  25. packageSubexpIndex = 2
  26. goPackageSubexpIndex = 3
  27. serviceSubexpIndex = 4
  28. goCommonTimeIndex = 5
  29. )
  30. func protoFileInfo(goPrefix, basepath string, protosrc []string) ProtoInfo {
  31. //info := fileNameInfo(dir, rel, name)
  32. var info ProtoInfo
  33. info.src = protosrc
  34. for _, srcpath := range info.src {
  35. content, err := ioutil.ReadFile(filepath.Join(basepath, srcpath))
  36. if err != nil {
  37. log.Printf("%s: error reading proto file: %v", srcpath, err)
  38. return info
  39. }
  40. for _, match := range protoRe.FindAllSubmatch(content, -1) {
  41. switch {
  42. case match[importSubexpIndex] != nil:
  43. imp := unquoteProtoString(match[importSubexpIndex])
  44. info.imports = append(info.imports, imp)
  45. case match[packageSubexpIndex] != nil:
  46. pkg := string(match[packageSubexpIndex])
  47. if info.packageName == "" {
  48. info.packageName = strings.Replace(pkg, ".", "_", -1)
  49. }
  50. case match[goPackageSubexpIndex] != nil:
  51. gopkg := unquoteProtoString(match[goPackageSubexpIndex])
  52. // If there's no / in the package option, then it's just a
  53. // simple package name, not a full import path.
  54. if strings.LastIndexByte(gopkg, '/') == -1 {
  55. info.packageName = gopkg
  56. } else {
  57. if i := strings.LastIndexByte(gopkg, ';'); i != -1 {
  58. info.importPath = gopkg[:i]
  59. info.packageName = gopkg[i+1:]
  60. } else {
  61. info.importPath = gopkg
  62. info.packageName = path.Base(gopkg)
  63. }
  64. }
  65. case match[serviceSubexpIndex] != nil:
  66. info.hasServices = true
  67. default:
  68. // Comment matched. Nothing to extract.
  69. }
  70. }
  71. rtime := regexp.MustCompile("go-common/library/time.Time")
  72. if rtime.FindAllSubmatchIndex(content, -1) != nil {
  73. info.imports = append(info.imports, "//library/time:go_default_library")
  74. }
  75. sort.Strings(info.imports)
  76. if info.packageName == "" {
  77. stem := strings.TrimSuffix(filepath.Base(srcpath), ".proto")
  78. fs := strings.FieldsFunc(stem, func(r rune) bool {
  79. return !(unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_')
  80. })
  81. info.packageName = strings.Join(fs, "_")
  82. }
  83. }
  84. if len(info.imports) > 1 {
  85. info.imports = unique(info.imports)
  86. }
  87. for _, v := range info.imports {
  88. if strings.Contains(v, "gogo") {
  89. info.isGogo = true
  90. }
  91. }
  92. info.importPath = filepath.Join(goPrefix, basepath)
  93. return info
  94. }
  95. // Based on https://developers.google.com/protocol-buffers/docs/reference/proto3-spec
  96. func buildProtoRegexp() *regexp.Regexp {
  97. hexEscape := `\\[xX][0-9a-fA-f]{2}`
  98. octEscape := `\\[0-7]{3}`
  99. charEscape := `\\[abfnrtv'"\\]`
  100. charValue := strings.Join([]string{hexEscape, octEscape, charEscape, "[^\x00\\'\\\"\\\\]"}, "|")
  101. strLit := `'(?:` + charValue + `|")*'|"(?:` + charValue + `|')*"`
  102. ident := `[A-Za-z][A-Za-z0-9_]*`
  103. fullIdent := ident + `(?:\.` + ident + `)*`
  104. importStmt := `\bimport\s*(?:public|weak)?\s*(?P<import>` + strLit + `)\s*;`
  105. packageStmt := `\bpackage\s*(?P<package>` + fullIdent + `)\s*;`
  106. goPackageStmt := `\boption\s*go_package\s*=\s*(?P<go_package>` + strLit + `)\s*;`
  107. serviceStmt := `(?P<service>service)`
  108. comment := `//[^\n]*`
  109. protoReSrc := strings.Join([]string{importStmt, packageStmt, goPackageStmt, serviceStmt, comment}, "|")
  110. return regexp.MustCompile(protoReSrc)
  111. }
  112. func unquoteProtoString(q []byte) string {
  113. // Adjust quotes so that Unquote is happy. We need a double quoted string
  114. // without unescaped double quote characters inside.
  115. noQuotes := bytes.Split(q[1:len(q)-1], []byte{'"'})
  116. if len(noQuotes) != 1 {
  117. for i := 0; i < len(noQuotes)-1; i++ {
  118. if len(noQuotes[i]) == 0 || noQuotes[i][len(noQuotes[i])-1] != '\\' {
  119. noQuotes[i] = append(noQuotes[i], '\\')
  120. }
  121. }
  122. q = append([]byte{'"'}, bytes.Join(noQuotes, []byte{'"'})...)
  123. q = append(q, '"')
  124. }
  125. if q[0] == '\'' {
  126. q[0] = '"'
  127. q[len(q)-1] = '"'
  128. }
  129. s, err := strconv.Unquote(string(q))
  130. if err != nil {
  131. log.Panicf("unquoting string literal %s from proto: %v", q, err)
  132. }
  133. return s
  134. }
  135. func unique(intSlice []string) []string {
  136. keys := make(map[string]interface{})
  137. list := []string{}
  138. for _, entry := range intSlice {
  139. if _, value := keys[entry]; !value {
  140. keys[entry] = nil
  141. list = append(list, entry)
  142. }
  143. }
  144. return list
  145. }