...
1
15
16 package proto
17
18 import (
19 "bytes"
20 "log"
21 "os"
22 "path/filepath"
23 "regexp"
24 "sort"
25 "strconv"
26 "strings"
27 )
28
29
30 type FileInfo struct {
31 Path, Name string
32
33 PackageName string
34
35 Options []Option
36 Imports []string
37
38 HasServices bool
39 }
40
41
42
43 type Option struct {
44 Key, Value string
45 }
46
47 var protoRe = buildProtoRegexp()
48
49 func protoFileInfo(dir, name string) FileInfo {
50 info := FileInfo{
51 Path: filepath.Join(dir, name),
52 Name: name,
53 }
54 content, err := os.ReadFile(info.Path)
55 if err != nil {
56 log.Printf("%s: error reading proto file: %v", info.Path, err)
57 return info
58 }
59
60 for _, match := range protoRe.FindAllSubmatch(content, -1) {
61 switch {
62 case match[importSubexpIndex] != nil:
63 imp := unquoteProtoString(match[importSubexpIndex])
64 info.Imports = append(info.Imports, imp)
65
66 case match[packageSubexpIndex] != nil:
67 pkg := string(match[packageSubexpIndex])
68 if info.PackageName == "" {
69 info.PackageName = pkg
70 }
71
72 case match[optkeySubexpIndex] != nil:
73 key := string(match[optkeySubexpIndex])
74 value := unquoteProtoString(match[optvalSubexpIndex])
75 info.Options = append(info.Options, Option{key, value})
76
77 case match[serviceSubexpIndex] != nil:
78 info.HasServices = true
79
80 default:
81
82 }
83 }
84 sort.Strings(info.Imports)
85
86 return info
87 }
88
89 const (
90 importSubexpIndex = 1
91 packageSubexpIndex = 2
92 optkeySubexpIndex = 3
93 optvalSubexpIndex = 4
94 serviceSubexpIndex = 5
95 )
96
97
98 func buildProtoRegexp() *regexp.Regexp {
99 hexEscape := `\\[xX][0-9a-fA-f]{2}`
100 octEscape := `\\[0-7]{3}`
101 charEscape := `\\[abfnrtv'"\\]`
102 charValue := strings.Join([]string{hexEscape, octEscape, charEscape, "[^\x00\\'\\\"\\\\]"}, "|")
103 strLit := `'(?:` + charValue + `|")*'|"(?:` + charValue + `|')*"`
104 ident := `[A-Za-z][A-Za-z0-9_]*`
105 fullIdent := ident + `(?:\.` + ident + `)*`
106 importStmt := `\bimport\s*(?:public|weak)?\s*(?P<import>` + strLit + `)\s*;`
107 packageStmt := `\bpackage\s*(?P<package>` + fullIdent + `)\s*;`
108 optionStmt := `\boption\s*(?P<optkey>` + fullIdent + `)\s*=\s*(?P<optval>` + strLit + `)\s*;`
109 serviceStmt := `(?P<service>service\s*` + ident + `\s*{)`
110 comment := `//[^\n]*`
111 protoReSrc := strings.Join([]string{importStmt, packageStmt, optionStmt, serviceStmt, comment}, "|")
112 return regexp.MustCompile(protoReSrc)
113 }
114
115 func unquoteProtoString(q []byte) string {
116
117
118 noQuotes := bytes.Split(q[1:len(q)-1], []byte{'"'})
119 if len(noQuotes) != 1 {
120 for i := 0; i < len(noQuotes)-1; i++ {
121 if len(noQuotes[i]) == 0 || noQuotes[i][len(noQuotes[i])-1] != '\\' {
122 noQuotes[i] = append(noQuotes[i], '\\')
123 }
124 }
125 q = append([]byte{'"'}, bytes.Join(noQuotes, []byte{'"'})...)
126 q = append(q, '"')
127 }
128 if q[0] == '\'' {
129 q[0] = '"'
130 q[len(q)-1] = '"'
131 }
132
133 s, err := strconv.Unquote(string(q))
134 if err != nil {
135 log.Panicf("unquoting string literal %s from proto: %v", q, err)
136 }
137 return s
138 }
139
View as plain text