1
2
3
4
5
6
7
8 package godoc
9
10 import (
11 "bufio"
12 "go/build"
13 "log"
14 "os"
15 "path/filepath"
16 "sort"
17 "strconv"
18 "strings"
19 "unicode"
20 )
21
22
23
24
25
26
27 type apiVersions map[string]pkgAPIVersions
28
29
30
31
32
33
34 type pkgAPIVersions struct {
35 typeSince map[string]string
36 methodSince map[string]map[string]string
37 funcSince map[string]string
38 fieldSince map[string]map[string]string
39 }
40
41
42
43
44
45
46
47
48
49
50
51
52 func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
53 pv := v[pkg]
54 switch kind {
55 case "func":
56 return pv.funcSince[name]
57 case "type":
58 return pv.typeSince[name]
59 case "method":
60 return pv.methodSince[receiver][name]
61 }
62 return ""
63 }
64
65
66
67 type versionedRow struct {
68 pkg string
69 kind string
70 recv string
71 name string
72 structName string
73 }
74
75
76 type versionParser struct {
77 res apiVersions
78 }
79
80
81
82
83
84
85
86 func (vp *versionParser) parseFile(name string) error {
87 f, err := os.Open(name)
88 if err != nil {
89 return err
90 }
91 defer f.Close()
92
93 base := filepath.Base(name)
94 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
95
96 sc := bufio.NewScanner(f)
97 for sc.Scan() {
98 row, ok := parseRow(sc.Text())
99 if !ok {
100 continue
101 }
102 if vp.res == nil {
103 vp.res = make(apiVersions)
104 }
105 pkgi, ok := vp.res[row.pkg]
106 if !ok {
107 pkgi = pkgAPIVersions{
108 typeSince: make(map[string]string),
109 methodSince: make(map[string]map[string]string),
110 funcSince: make(map[string]string),
111 fieldSince: make(map[string]map[string]string),
112 }
113 vp.res[row.pkg] = pkgi
114 }
115 switch row.kind {
116 case "func":
117 if ver == "1" {
118 delete(pkgi.funcSince, row.name)
119 break
120 }
121 pkgi.funcSince[row.name] = ver
122 case "type":
123 if ver == "1" {
124 delete(pkgi.typeSince, row.name)
125 break
126 }
127 pkgi.typeSince[row.name] = ver
128 case "method":
129 if ver == "1" {
130 delete(pkgi.methodSince[row.recv], row.name)
131 break
132 }
133 if _, ok := pkgi.methodSince[row.recv]; !ok {
134 pkgi.methodSince[row.recv] = make(map[string]string)
135 }
136 pkgi.methodSince[row.recv][row.name] = ver
137 case "field":
138 if ver == "1" {
139 delete(pkgi.fieldSince[row.structName], row.name)
140 break
141 }
142 if _, ok := pkgi.fieldSince[row.structName]; !ok {
143 pkgi.fieldSince[row.structName] = make(map[string]string)
144 }
145 pkgi.fieldSince[row.structName][row.name] = ver
146 }
147 }
148 return sc.Err()
149 }
150
151 func parseRow(s string) (vr versionedRow, ok bool) {
152 if !strings.HasPrefix(s, "pkg ") {
153
154 return
155 }
156 rest := s[len("pkg "):]
157 endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
158 if endPkg == -1 {
159 return
160 }
161 vr.pkg, rest = rest[:endPkg], rest[endPkg:]
162 if !strings.HasPrefix(rest, ", ") {
163
164
165
166 return
167 }
168 rest = rest[len(", "):]
169
170 switch {
171 case strings.HasPrefix(rest, "type "):
172 rest = rest[len("type "):]
173 sp := strings.IndexByte(rest, ' ')
174 if sp == -1 {
175 return
176 }
177 vr.name, rest = rest[:sp], rest[sp+1:]
178 if !strings.HasPrefix(rest, "struct, ") {
179 vr.kind = "type"
180 return vr, true
181 }
182 rest = rest[len("struct, "):]
183 if i := strings.IndexByte(rest, ' '); i != -1 {
184 vr.kind = "field"
185 vr.structName = vr.name
186 vr.name = rest[:i]
187 return vr, true
188 }
189 case strings.HasPrefix(rest, "func "):
190 vr.kind = "func"
191 rest = rest[len("func "):]
192 if i := strings.IndexByte(rest, '('); i != -1 {
193 vr.name = rest[:i]
194 return vr, true
195 }
196 case strings.HasPrefix(rest, "method "):
197 vr.kind = "method"
198 rest = rest[len("method "):]
199 sp := strings.IndexByte(rest, ' ')
200 if sp == -1 {
201 return
202 }
203 vr.recv = strings.Trim(rest[:sp], "()")
204 rest = rest[sp+1:]
205 paren := strings.IndexByte(rest, '(')
206 if paren == -1 {
207 return
208 }
209 vr.name = rest[:paren]
210 return vr, true
211 }
212 return
213 }
214
215
216
217 func (c *Corpus) InitVersionInfo() {
218 var err error
219 c.pkgAPIInfo, err = parsePackageAPIInfo()
220 if err != nil {
221
222 log.Printf("godoc: error parsing API version files: %v", err)
223 }
224 }
225
226 func parsePackageAPIInfo() (apiVersions, error) {
227 var apiGlob string
228 if os.Getenv("GOROOT") == "" {
229 apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
230 } else {
231 apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
232 }
233
234 files, err := filepath.Glob(apiGlob)
235 if err != nil {
236 return nil, err
237 }
238
239
240
241
242
243
244
245
246
247
248 ver := func(name string) int {
249 base := filepath.Base(name)
250 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go1.")
251 if ver == "go1" {
252 return 0
253 }
254 v, _ := strconv.Atoi(ver)
255 return v
256 }
257 sort.Slice(files, func(i, j int) bool { return ver(files[i]) > ver(files[j]) })
258 vp := new(versionParser)
259 for _, f := range files {
260 if err := vp.parseFile(f); err != nil {
261 return nil, err
262 }
263 }
264 return vp.res, nil
265 }
266
View as plain text