Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(2)

Side by Side Diff: src/cmd/godoc/godoc.go

Issue 2098044: code review 2098044: godoc: moved package directory support code into separa... (Closed)
Patch Set: code review 2098044: godoc: moved package directory support code into separa... Created 14 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/cmd/godoc/dirtrees.go ('k') | src/cmd/godoc/index.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 }
OLDNEW
« no previous file with comments | « src/cmd/godoc/dirtrees.go ('k') | src/cmd/godoc/index.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b