OLD | NEW |
1 // Copyright 2009 The Go Authors. All rights reserved. | 1 // Copyright 2009 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package main | 5 package main |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
9 "flag" | 9 "flag" |
10 "fmt" | 10 "fmt" |
11 "go/ast" | 11 "go/ast" |
12 "go/doc" | 12 "go/doc" |
13 "go/parser" | 13 "go/parser" |
14 "go/printer" | 14 "go/printer" |
15 "go/token" | 15 "go/token" |
16 "http" | 16 "http" |
17 "io" | 17 "io" |
18 "io/ioutil" | 18 "io/ioutil" |
19 "log" | 19 "log" |
20 "os" | 20 "os" |
21 pathutil "path" | 21 pathutil "path" |
22 "regexp" | 22 "regexp" |
23 "runtime" | 23 "runtime" |
24 "strings" | 24 "strings" |
25 "sync" | 25 "sync" |
26 "template" | 26 "template" |
27 "time" | 27 "time" |
28 "unicode" | |
29 "utf8" | 28 "utf8" |
30 ) | 29 ) |
31 | 30 |
32 | 31 |
33 // ---------------------------------------------------------------------------- | 32 // ---------------------------------------------------------------------------- |
34 // Support types | 33 // Support types |
35 | 34 |
36 // An RWValue wraps a value and permits mutually exclusive | 35 // An RWValue wraps a value and permits mutually exclusive |
37 // access to it and records the time the value was last set. | 36 // access to it and records the time the value was last set. |
38 type RWValue struct { | 37 type RWValue struct { |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
108 func registerPublicHandlers(mux *http.ServeMux) { | 107 func registerPublicHandlers(mux *http.ServeMux) { |
109 mux.Handle(cmdHandler.pattern, &cmdHandler) | 108 mux.Handle(cmdHandler.pattern, &cmdHandler) |
110 mux.Handle(pkgHandler.pattern, &pkgHandler) | 109 mux.Handle(pkgHandler.pattern, &pkgHandler) |
111 mux.HandleFunc("/doc/codewalk/", codewalk) | 110 mux.HandleFunc("/doc/codewalk/", codewalk) |
112 mux.HandleFunc("/search", search) | 111 mux.HandleFunc("/search", search) |
113 mux.HandleFunc("/", serveFile) | 112 mux.HandleFunc("/", serveFile) |
114 } | 113 } |
115 | 114 |
116 | 115 |
117 // ---------------------------------------------------------------------------- | 116 // ---------------------------------------------------------------------------- |
118 // Predicates and small utility functions | 117 // Path mapping |
119 | |
120 func isGoFile(f *os.FileInfo) bool { | |
121 » return f.IsRegular() && | |
122 » » !strings.HasPrefix(f.Name, ".") && // ignore .files | |
123 » » pathutil.Ext(f.Name) == ".go" | |
124 } | |
125 | |
126 | |
127 func isPkgFile(f *os.FileInfo) bool { | |
128 » return isGoFile(f) && | |
129 » » !strings.HasSuffix(f.Name, "_test.go") // ignore test files | |
130 } | |
131 | |
132 | |
133 func isPkgDir(f *os.FileInfo) bool { | |
134 » return f.IsDirectory() && len(f.Name) > 0 && f.Name[0] != '_' | |
135 } | |
136 | |
137 | |
138 func pkgName(filename string) string { | |
139 » file, err := parser.ParseFile(filename, nil, parser.PackageClauseOnly) | |
140 » if err != nil || file == nil { | |
141 » » return "" | |
142 » } | |
143 » return file.Name.Name | |
144 } | |
145 | |
146 | |
147 func firstSentence(s string) string { | |
148 » i := -1 // index+1 of first terminator (punctuation ending a sentence) | |
149 » j := -1 // index+1 of first terminator followed by white space | |
150 » prev := 'A' | |
151 » for k, ch := range s { | |
152 » » k1 := k + 1 | |
153 » » if ch == '.' || ch == '!' || ch == '?' { | |
154 » » » if i < 0 { | |
155 » » » » i = k1 // first terminator | |
156 » » » } | |
157 » » » if k1 < len(s) && s[k1] <= ' ' { | |
158 » » » » if j < 0 { | |
159 » » » » » j = k1 // first terminator followed by w
hite space | |
160 » » » » } | |
161 » » » » if !unicode.IsUpper(prev) { | |
162 » » » » » j = k1 | |
163 » » » » » break | |
164 » » » » } | |
165 » » » } | |
166 » » } | |
167 » » prev = ch | |
168 » } | |
169 | |
170 » if j < 0 { | |
171 » » // use the next best terminator | |
172 » » j = i | |
173 » » if j < 0 { | |
174 » » » // no terminator at all, use the entire string | |
175 » » » j = len(s) | |
176 » » } | |
177 » } | |
178 | |
179 » return s[0:j] | |
180 } | |
181 | |
182 | 118 |
183 func absolutePath(path, defaultRoot string) string { | 119 func absolutePath(path, defaultRoot string) string { |
184 abspath := fsMap.ToAbsolute(path) | 120 abspath := fsMap.ToAbsolute(path) |
185 if abspath == "" { | 121 if abspath == "" { |
186 // no user-defined mapping found; use default mapping | 122 // no user-defined mapping found; use default mapping |
187 abspath = pathutil.Join(defaultRoot, path) | 123 abspath = pathutil.Join(defaultRoot, path) |
188 } | 124 } |
189 return abspath | 125 return abspath |
190 } | 126 } |
191 | 127 |
192 | 128 |
193 func relativePath(path string) string { | 129 func relativePath(path string) string { |
194 relpath := fsMap.ToRelative(path) | 130 relpath := fsMap.ToRelative(path) |
195 if relpath == "" && strings.HasPrefix(path, *goroot+"/") { | 131 if relpath == "" && strings.HasPrefix(path, *goroot+"/") { |
196 // no user-defined mapping found; use default mapping | 132 // no user-defined mapping found; use default mapping |
197 relpath = path[len(*goroot)+1:] | 133 relpath = path[len(*goroot)+1:] |
198 } | 134 } |
199 // Only if path is an invalid absolute path is relpath == "" | 135 // Only if path is an invalid absolute path is relpath == "" |
200 // at this point. This should never happen since absolute paths | 136 // at this point. This should never happen since absolute paths |
201 // are only created via godoc for files that do exist. However, | 137 // are only created via godoc for files that do exist. However, |
202 // it is ok to return ""; it will simply provide a link to the | 138 // it is ok to return ""; it will simply provide a link to the |
203 // top of the pkg or src directories. | 139 // top of the pkg or src directories. |
204 return relpath | 140 return relpath |
205 } | 141 } |
206 | 142 |
207 | 143 |
208 // ---------------------------------------------------------------------------- | 144 // ---------------------------------------------------------------------------- |
209 // Package directories | |
210 | |
211 type Directory struct { | |
212 Depth int | |
213 Path string // includes Name | |
214 Name string | |
215 Text string // package documentation, if any | |
216 Dirs []*Directory // subdirectories | |
217 } | |
218 | |
219 | |
220 func newDirTree(path, name string, depth, maxDepth int) *Directory { | |
221 if depth >= maxDepth { | |
222 // return a dummy directory so that the parent directory | |
223 // doesn't get discarded just because we reached the max | |
224 // directory depth | |
225 return &Directory{depth, path, name, "", nil} | |
226 } | |
227 | |
228 list, _ := ioutil.ReadDir(path) // ignore errors | |
229 | |
230 // determine number of subdirectories and package files | |
231 ndirs := 0 | |
232 nfiles := 0 | |
233 var synopses [4]string // prioritized package documentation (0 == highes
t priority) | |
234 for _, d := range list { | |
235 switch { | |
236 case isPkgDir(d): | |
237 ndirs++ | |
238 case isPkgFile(d): | |
239 nfiles++ | |
240 if synopses[0] == "" { | |
241 // no "optimal" package synopsis yet; continue t
o collect synopses | |
242 file, err := parser.ParseFile(pathutil.Join(path
, d.Name), nil, | |
243 parser.ParseComments|parser.PackageClaus
eOnly) | |
244 if err == nil && file.Doc != nil { | |
245 // prioritize documentation | |
246 i := -1 | |
247 switch file.Name.Name { | |
248 case name: | |
249 i = 0 // normal case: directory
name matches package name | |
250 case fakePkgName: | |
251 i = 1 // synopses for commands | |
252 case "main": | |
253 i = 2 // directory contains a ma
in package | |
254 default: | |
255 i = 3 // none of the above | |
256 } | |
257 if 0 <= i && i < len(synopses) && synops
es[i] == "" { | |
258 synopses[i] = firstSentence(doc.
CommentText(file.Doc)) | |
259 } | |
260 } | |
261 } | |
262 } | |
263 } | |
264 | |
265 // create subdirectory tree | |
266 var dirs []*Directory | |
267 if ndirs > 0 { | |
268 dirs = make([]*Directory, ndirs) | |
269 i := 0 | |
270 for _, d := range list { | |
271 if isPkgDir(d) { | |
272 dd := newDirTree(pathutil.Join(path, d.Name), d.
Name, depth+1, maxDepth) | |
273 if dd != nil { | |
274 dirs[i] = dd | |
275 i++ | |
276 } | |
277 } | |
278 } | |
279 dirs = dirs[0:i] | |
280 } | |
281 | |
282 // if there are no package files and no subdirectories | |
283 // (with package files), ignore the directory | |
284 if nfiles == 0 && len(dirs) == 0 { | |
285 return nil | |
286 } | |
287 | |
288 // select the highest-priority synopsis for the directory entry, if any | |
289 synopsis := "" | |
290 for _, synopsis = range synopses { | |
291 if synopsis != "" { | |
292 break | |
293 } | |
294 } | |
295 | |
296 return &Directory{depth, path, name, synopsis, dirs} | |
297 } | |
298 | |
299 | |
300 // newDirectory creates a new package directory tree with at most maxDepth | |
301 // levels, anchored at root. The result tree is pruned such that it only | |
302 // contains directories that contain package files or that contain | |
303 // subdirectories containing package files (transitively). If maxDepth is | |
304 // too shallow, the leaf nodes are assumed to contain package files even if | |
305 // their contents are not known (i.e., in this case the tree may contain | |
306 // directories w/o any package files). | |
307 // | |
308 func newDirectory(root string, maxDepth int) *Directory { | |
309 d, err := os.Lstat(root) | |
310 if err != nil || !isPkgDir(d) { | |
311 return nil | |
312 } | |
313 return newDirTree(root, d.Name, 0, maxDepth) | |
314 } | |
315 | |
316 | |
317 func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) { | |
318 if dir != nil { | |
319 if !skipRoot { | |
320 c <- dir | |
321 } | |
322 for _, d := range dir.Dirs { | |
323 d.walk(c, false) | |
324 } | |
325 } | |
326 } | |
327 | |
328 | |
329 func (dir *Directory) iter(skipRoot bool) <-chan *Directory { | |
330 c := make(chan *Directory) | |
331 go func() { | |
332 dir.walk(c, skipRoot) | |
333 close(c) | |
334 }() | |
335 return c | |
336 } | |
337 | |
338 | |
339 func (dir *Directory) lookupLocal(name string) *Directory { | |
340 for _, d := range dir.Dirs { | |
341 if d.Name == name { | |
342 return d | |
343 } | |
344 } | |
345 return nil | |
346 } | |
347 | |
348 | |
349 // lookup looks for the *Directory for a given path, relative to dir. | |
350 func (dir *Directory) lookup(path string) *Directory { | |
351 d := strings.Split(dir.Path, "/", -1) | |
352 p := strings.Split(path, "/", -1) | |
353 i := 0 | |
354 for i < len(d) { | |
355 if i >= len(p) || d[i] != p[i] { | |
356 return nil | |
357 } | |
358 i++ | |
359 } | |
360 for dir != nil && i < len(p) { | |
361 dir = dir.lookupLocal(p[i]) | |
362 i++ | |
363 } | |
364 return dir | |
365 } | |
366 | |
367 | |
368 // DirEntry describes a directory entry. The Depth and Height values | |
369 // are useful for presenting an entry in an indented fashion. | |
370 // | |
371 type DirEntry struct { | |
372 Depth int // >= 0 | |
373 Height int // = DirList.MaxHeight - Depth, > 0 | |
374 Path string // includes Name, relative to DirList root | |
375 Name string | |
376 Synopsis string | |
377 } | |
378 | |
379 | |
380 type DirList struct { | |
381 MaxHeight int // directory tree height, > 0 | |
382 List []DirEntry | |
383 } | |
384 | |
385 | |
386 // listing creates a (linear) directory listing from a directory tree. | |
387 // If skipRoot is set, the root directory itself is excluded from the list. | |
388 // | |
389 func (root *Directory) listing(skipRoot bool) *DirList { | |
390 if root == nil { | |
391 return nil | |
392 } | |
393 | |
394 // determine number of entries n and maximum height | |
395 n := 0 | |
396 minDepth := 1 << 30 // infinity | |
397 maxDepth := 0 | |
398 for d := range root.iter(skipRoot) { | |
399 n++ | |
400 if minDepth > d.Depth { | |
401 minDepth = d.Depth | |
402 } | |
403 if maxDepth < d.Depth { | |
404 maxDepth = d.Depth | |
405 } | |
406 } | |
407 maxHeight := maxDepth - minDepth + 1 | |
408 | |
409 if n == 0 { | |
410 return nil | |
411 } | |
412 | |
413 // create list | |
414 list := make([]DirEntry, n) | |
415 i := 0 | |
416 for d := range root.iter(skipRoot) { | |
417 p := &list[i] | |
418 p.Depth = d.Depth - minDepth | |
419 p.Height = maxHeight - p.Depth | |
420 // the path is relative to root.Path - remove the root.Path | |
421 // prefix (the prefix should always be present but avoid | |
422 // crashes and check) | |
423 path := d.Path | |
424 if strings.HasPrefix(d.Path, root.Path) { | |
425 path = d.Path[len(root.Path):] | |
426 } | |
427 // remove trailing '/' if any - path must be relative | |
428 if len(path) > 0 && path[0] == '/' { | |
429 path = path[1:] | |
430 } | |
431 p.Path = path | |
432 p.Name = d.Name | |
433 p.Synopsis = d.Text | |
434 i++ | |
435 } | |
436 | |
437 return &DirList{maxHeight, list} | |
438 } | |
439 | |
440 | |
441 // ---------------------------------------------------------------------------- | |
442 // HTML formatting support | 145 // HTML formatting support |
443 | 146 |
444 // aposescaper implements an io.Writer that escapes single quotes: | 147 // aposescaper implements an io.Writer that escapes single quotes: |
445 // ' is written as \' . It is used to escape text such that it can | 148 // ' is written as \' . It is used to escape text such that it can |
446 // be used as the content of single-quoted string literals. | 149 // be used as the content of single-quoted string literals. |
447 type aposescaper struct { | 150 type aposescaper struct { |
448 w io.Writer | 151 w io.Writer |
449 } | 152 } |
450 | 153 |
451 | 154 |
(...skipping 1047 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1499 nwords, nspots := index.Size() | 1202 nwords, nspots := index.Size() |
1500 log.Stderrf("index updated (%gs, %d unique words
, %d spots)", secs, nwords, nspots) | 1203 log.Stderrf("index updated (%gs, %d unique words
, %d spots)", secs, nwords, nspots) |
1501 } | 1204 } |
1502 log.Stderrf("bytes=%d footprint=%d\n", runtime.MemStats.
HeapAlloc, runtime.MemStats.Sys) | 1205 log.Stderrf("bytes=%d footprint=%d\n", runtime.MemStats.
HeapAlloc, runtime.MemStats.Sys) |
1503 runtime.GC() | 1206 runtime.GC() |
1504 log.Stderrf("bytes=%d footprint=%d\n", runtime.MemStats.
HeapAlloc, runtime.MemStats.Sys) | 1207 log.Stderrf("bytes=%d footprint=%d\n", runtime.MemStats.
HeapAlloc, runtime.MemStats.Sys) |
1505 } | 1208 } |
1506 time.Sleep(1 * 60e9) // try once a minute | 1209 time.Sleep(1 * 60e9) // try once a minute |
1507 } | 1210 } |
1508 } | 1211 } |
OLD | NEW |