123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- package main
- import (
- "bytes"
- "io/ioutil"
- "log"
- "path"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "unicode"
- )
- type ProtoInfo struct {
- src []string
- importPath string
- packageName string
- imports []string
- isGogo bool
- hasServices bool
- }
- var protoRe = buildProtoRegexp()
- const (
- importSubexpIndex = 1
- packageSubexpIndex = 2
- goPackageSubexpIndex = 3
- serviceSubexpIndex = 4
- goCommonTimeIndex = 5
- )
- func protoFileInfo(goPrefix, basepath string, protosrc []string) ProtoInfo {
- //info := fileNameInfo(dir, rel, name)
- var info ProtoInfo
- info.src = protosrc
- for _, srcpath := range info.src {
- content, err := ioutil.ReadFile(filepath.Join(basepath, srcpath))
- if err != nil {
- log.Printf("%s: error reading proto file: %v", srcpath, err)
- return info
- }
- for _, match := range protoRe.FindAllSubmatch(content, -1) {
- switch {
- case match[importSubexpIndex] != nil:
- imp := unquoteProtoString(match[importSubexpIndex])
- info.imports = append(info.imports, imp)
- case match[packageSubexpIndex] != nil:
- pkg := string(match[packageSubexpIndex])
- if info.packageName == "" {
- info.packageName = strings.Replace(pkg, ".", "_", -1)
- }
- case match[goPackageSubexpIndex] != nil:
- gopkg := unquoteProtoString(match[goPackageSubexpIndex])
- // If there's no / in the package option, then it's just a
- // simple package name, not a full import path.
- if strings.LastIndexByte(gopkg, '/') == -1 {
- info.packageName = gopkg
- } else {
- if i := strings.LastIndexByte(gopkg, ';'); i != -1 {
- info.importPath = gopkg[:i]
- info.packageName = gopkg[i+1:]
- } else {
- info.importPath = gopkg
- info.packageName = path.Base(gopkg)
- }
- }
- case match[serviceSubexpIndex] != nil:
- info.hasServices = true
- default:
- // Comment matched. Nothing to extract.
- }
- }
- rtime := regexp.MustCompile("go-common/library/time.Time")
- if rtime.FindAllSubmatchIndex(content, -1) != nil {
- info.imports = append(info.imports, "//library/time:go_default_library")
- }
- sort.Strings(info.imports)
- if info.packageName == "" {
- stem := strings.TrimSuffix(filepath.Base(srcpath), ".proto")
- fs := strings.FieldsFunc(stem, func(r rune) bool {
- return !(unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_')
- })
- info.packageName = strings.Join(fs, "_")
- }
- }
- if len(info.imports) > 1 {
- info.imports = unique(info.imports)
- }
- for _, v := range info.imports {
- if strings.Contains(v, "gogo") {
- info.isGogo = true
- }
- }
- info.importPath = filepath.Join(goPrefix, basepath)
- return info
- }
- // Based on https://developers.google.com/protocol-buffers/docs/reference/proto3-spec
- func buildProtoRegexp() *regexp.Regexp {
- hexEscape := `\\[xX][0-9a-fA-f]{2}`
- octEscape := `\\[0-7]{3}`
- charEscape := `\\[abfnrtv'"\\]`
- charValue := strings.Join([]string{hexEscape, octEscape, charEscape, "[^\x00\\'\\\"\\\\]"}, "|")
- strLit := `'(?:` + charValue + `|")*'|"(?:` + charValue + `|')*"`
- ident := `[A-Za-z][A-Za-z0-9_]*`
- fullIdent := ident + `(?:\.` + ident + `)*`
- importStmt := `\bimport\s*(?:public|weak)?\s*(?P<import>` + strLit + `)\s*;`
- packageStmt := `\bpackage\s*(?P<package>` + fullIdent + `)\s*;`
- goPackageStmt := `\boption\s*go_package\s*=\s*(?P<go_package>` + strLit + `)\s*;`
- serviceStmt := `(?P<service>service)`
- comment := `//[^\n]*`
- protoReSrc := strings.Join([]string{importStmt, packageStmt, goPackageStmt, serviceStmt, comment}, "|")
- return regexp.MustCompile(protoReSrc)
- }
- func unquoteProtoString(q []byte) string {
- // Adjust quotes so that Unquote is happy. We need a double quoted string
- // without unescaped double quote characters inside.
- noQuotes := bytes.Split(q[1:len(q)-1], []byte{'"'})
- if len(noQuotes) != 1 {
- for i := 0; i < len(noQuotes)-1; i++ {
- if len(noQuotes[i]) == 0 || noQuotes[i][len(noQuotes[i])-1] != '\\' {
- noQuotes[i] = append(noQuotes[i], '\\')
- }
- }
- q = append([]byte{'"'}, bytes.Join(noQuotes, []byte{'"'})...)
- q = append(q, '"')
- }
- if q[0] == '\'' {
- q[0] = '"'
- q[len(q)-1] = '"'
- }
- s, err := strconv.Unquote(string(q))
- if err != nil {
- log.Panicf("unquoting string literal %s from proto: %v", q, err)
- }
- return s
- }
- func unique(intSlice []string) []string {
- keys := make(map[string]interface{})
- list := []string{}
- for _, entry := range intSlice {
- if _, value := keys[entry]; !value {
- keys[entry] = nil
- list = append(list, entry)
- }
- }
- return list
- }
|