github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/godoc/main.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // godoc: Go Documentation Server 6 7 // Web server tree: 8 // 9 // http://godoc/ main landing page 10 // http://godoc/doc/ serve from $GOROOT/doc - spec, mem, etc. 11 // http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed 12 // http://godoc/cmd/ serve documentation about commands 13 // http://godoc/pkg/ serve documentation about packages 14 // (idea is if you say import "compress/zlib", you go to 15 // http://godoc/pkg/compress/zlib) 16 // 17 // Command-line interface: 18 // 19 // godoc packagepath [name ...] 20 // 21 // godoc compress/zlib 22 // - prints doc for package compress/zlib 23 // godoc crypto/block Cipher NewCMAC 24 // - prints doc for Cipher and NewCMAC in package crypto/block 25 26 // +build !appengine 27 28 package main 29 30 import ( 31 "archive/zip" 32 "bytes" 33 "errors" 34 _ "expvar" // to serve /debug/vars 35 "flag" 36 "fmt" 37 "go/ast" 38 "go/build" 39 "go/printer" 40 "io" 41 "log" 42 "net/http" 43 _ "net/http/pprof" // to serve /debug/pprof/* 44 "net/url" 45 "os" 46 pathpkg "path" 47 "path/filepath" 48 "regexp" 49 "runtime" 50 "strings" 51 ) 52 53 const defaultAddr = ":6060" // default webserver address 54 55 var ( 56 // file system to serve 57 // (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico) 58 zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty") 59 60 // file-based index 61 writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files") 62 63 // network 64 httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')") 65 serverAddr = flag.String("server", "", "webserver address for command line searches") 66 67 // layout control 68 html = flag.Bool("html", false, "print HTML in command-line mode") 69 srcMode = flag.Bool("src", false, "print (exported) source in command-line mode") 70 urlFlag = flag.String("url", "", "print HTML for named URL") 71 72 // command-line searches 73 query = flag.Bool("q", false, "arguments are considered search queries") 74 ) 75 76 func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) { 77 w.WriteHeader(http.StatusNotFound) 78 servePage(w, Page{ 79 Title: "File " + relpath, 80 Subtitle: relpath, 81 Body: applyTemplate(errorHTML, "errorHTML", err), // err may contain an absolute path! 82 }) 83 } 84 85 func usage() { 86 fmt.Fprintf(os.Stderr, 87 "usage: godoc package [name ...]\n"+ 88 " godoc -http="+defaultAddr+"\n") 89 flag.PrintDefaults() 90 os.Exit(2) 91 } 92 93 func loggingHandler(h http.Handler) http.Handler { 94 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 95 log.Printf("%s\t%s", req.RemoteAddr, req.URL) 96 h.ServeHTTP(w, req) 97 }) 98 } 99 100 func remoteSearch(query string) (res *http.Response, err error) { 101 // list of addresses to try 102 var addrs []string 103 if *serverAddr != "" { 104 // explicit server address - only try this one 105 addrs = []string{*serverAddr} 106 } else { 107 addrs = []string{ 108 defaultAddr, 109 "golang.org", 110 } 111 } 112 113 // remote search 114 search := remoteSearchURL(query, *html) 115 for _, addr := range addrs { 116 url := "http://" + addr + search 117 res, err = http.Get(url) 118 if err == nil && res.StatusCode == http.StatusOK { 119 break 120 } 121 } 122 123 if err == nil && res.StatusCode != http.StatusOK { 124 err = errors.New(res.Status) 125 } 126 127 return 128 } 129 130 // Does s look like a regular expression? 131 func isRegexp(s string) bool { 132 return strings.IndexAny(s, ".(|)*+?^$[]") >= 0 133 } 134 135 // Make a regular expression of the form 136 // names[0]|names[1]|...names[len(names)-1]. 137 // Returns nil if the regular expression is illegal. 138 func makeRx(names []string) (rx *regexp.Regexp) { 139 if len(names) > 0 { 140 s := "" 141 for i, name := range names { 142 if i > 0 { 143 s += "|" 144 } 145 if isRegexp(name) { 146 s += name 147 } else { 148 s += "^" + name + "$" // must match exactly 149 } 150 } 151 rx, _ = regexp.Compile(s) // rx is nil if there's a compilation error 152 } 153 return 154 } 155 156 func main() { 157 flag.Usage = usage 158 flag.Parse() 159 160 // Check usage: either server and no args, command line and args, or index creation mode 161 if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex { 162 usage() 163 } 164 165 if *tabwidth < 0 { 166 log.Fatalf("negative tabwidth %d", *tabwidth) 167 } 168 169 // Determine file system to use. 170 // TODO(gri) - fs and fsHttp should really be the same. Try to unify. 171 // - fsHttp doesn't need to be set up in command-line mode, 172 // same is true for the http handlers in initHandlers. 173 if *zipfile == "" { 174 // use file system of underlying OS 175 fs.Bind("/", OS(*goroot), "/", bindReplace) 176 if *templateDir != "" { 177 fs.Bind("/lib/godoc", OS(*templateDir), "/", bindBefore) 178 } 179 } else { 180 // use file system specified via .zip file (path separator must be '/') 181 rc, err := zip.OpenReader(*zipfile) 182 if err != nil { 183 log.Fatalf("%s: %s\n", *zipfile, err) 184 } 185 defer rc.Close() // be nice (e.g., -writeIndex mode) 186 fs.Bind("/", NewZipFS(rc, *zipfile), *goroot, bindReplace) 187 } 188 189 // Bind $GOPATH trees into Go root. 190 for _, p := range filepath.SplitList(build.Default.GOPATH) { 191 fs.Bind("/src/pkg", OS(p), "/src", bindAfter) 192 } 193 194 readTemplates() 195 initHandlers() 196 197 if *writeIndex { 198 // Write search index and exit. 199 if *indexFiles == "" { 200 log.Fatal("no index file specified") 201 } 202 203 log.Println("initialize file systems") 204 *verbose = true // want to see what happens 205 initFSTree() 206 207 *indexThrottle = 1 208 updateIndex() 209 210 log.Println("writing index file", *indexFiles) 211 f, err := os.Create(*indexFiles) 212 if err != nil { 213 log.Fatal(err) 214 } 215 index, _ := searchIndex.get() 216 err = index.(*Index).Write(f) 217 if err != nil { 218 log.Fatal(err) 219 } 220 221 log.Println("done") 222 return 223 } 224 225 // Print content that would be served at the URL *urlFlag. 226 if *urlFlag != "" { 227 registerPublicHandlers(http.DefaultServeMux) 228 initFSTree() 229 updateMetadata() 230 // Try up to 10 fetches, following redirects. 231 urlstr := *urlFlag 232 for i := 0; i < 10; i++ { 233 // Prepare request. 234 u, err := url.Parse(urlstr) 235 if err != nil { 236 log.Fatal(err) 237 } 238 req := &http.Request{ 239 URL: u, 240 } 241 242 // Invoke default HTTP handler to serve request 243 // to our buffering httpWriter. 244 w := &httpWriter{h: http.Header{}, code: 200} 245 http.DefaultServeMux.ServeHTTP(w, req) 246 247 // Return data, error, or follow redirect. 248 switch w.code { 249 case 200: // ok 250 os.Stdout.Write(w.Bytes()) 251 return 252 case 301, 302, 303, 307: // redirect 253 redirect := w.h.Get("Location") 254 if redirect == "" { 255 log.Fatalf("HTTP %d without Location header", w.code) 256 } 257 urlstr = redirect 258 default: 259 log.Fatalf("HTTP error %d", w.code) 260 } 261 } 262 log.Fatalf("too many redirects") 263 } 264 265 if *httpAddr != "" { 266 // HTTP server mode. 267 var handler http.Handler = http.DefaultServeMux 268 if *verbose { 269 log.Printf("Go Documentation Server") 270 log.Printf("version = %s", runtime.Version()) 271 log.Printf("address = %s", *httpAddr) 272 log.Printf("goroot = %s", *goroot) 273 log.Printf("tabwidth = %d", *tabwidth) 274 switch { 275 case !*indexEnabled: 276 log.Print("search index disabled") 277 case *maxResults > 0: 278 log.Printf("full text index enabled (maxresults = %d)", *maxResults) 279 default: 280 log.Print("identifier search index enabled") 281 } 282 fs.Fprint(os.Stderr) 283 handler = loggingHandler(handler) 284 } 285 286 registerPublicHandlers(http.DefaultServeMux) 287 registerPlaygroundHandlers(http.DefaultServeMux) 288 289 // Initialize default directory tree with corresponding timestamp. 290 // (Do it in a goroutine so that launch is quick.) 291 go initFSTree() 292 293 // Immediately update metadata. 294 updateMetadata() 295 // Periodically refresh metadata. 296 go refreshMetadataLoop() 297 298 // Initialize search index. 299 if *indexEnabled { 300 go indexer() 301 } 302 303 // Start http server. 304 if err := http.ListenAndServe(*httpAddr, handler); err != nil { 305 log.Fatalf("ListenAndServe %s: %v", *httpAddr, err) 306 } 307 308 return 309 } 310 311 // Command line mode. 312 if *html { 313 packageText = packageHTML 314 searchText = packageHTML 315 } 316 317 if *query { 318 // Command-line queries. 319 for i := 0; i < flag.NArg(); i++ { 320 res, err := remoteSearch(flag.Arg(i)) 321 if err != nil { 322 log.Fatalf("remoteSearch: %s", err) 323 } 324 io.Copy(os.Stdout, res.Body) 325 } 326 return 327 } 328 329 // Determine paths. 330 // 331 // If we are passed an operating system path like . or ./foo or /foo/bar or c:\mysrc, 332 // we need to map that path somewhere in the fs name space so that routines 333 // like getPageInfo will see it. We use the arbitrarily-chosen virtual path "/target" 334 // for this. That is, if we get passed a directory like the above, we map that 335 // directory so that getPageInfo sees it as /target. 336 const target = "/target" 337 const cmdPrefix = "cmd/" 338 path := flag.Arg(0) 339 var forceCmd bool 340 var abspath, relpath string 341 if filepath.IsAbs(path) { 342 fs.Bind(target, OS(path), "/", bindReplace) 343 abspath = target 344 } else if build.IsLocalImport(path) { 345 cwd, _ := os.Getwd() // ignore errors 346 path = filepath.Join(cwd, path) 347 fs.Bind(target, OS(path), "/", bindReplace) 348 abspath = target 349 } else if strings.HasPrefix(path, cmdPrefix) { 350 path = strings.TrimPrefix(path, cmdPrefix) 351 forceCmd = true 352 } else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" { 353 fs.Bind(target, OS(bp.Dir), "/", bindReplace) 354 abspath = target 355 relpath = bp.ImportPath 356 } else { 357 abspath = pathpkg.Join(pkgHandler.fsRoot, path) 358 } 359 if relpath == "" { 360 relpath = abspath 361 } 362 363 var mode PageInfoMode 364 if relpath == builtinPkgPath { 365 // the fake built-in package contains unexported identifiers 366 mode = noFiltering 367 } 368 if *srcMode { 369 // only filter exports if we don't have explicit command-line filter arguments 370 if flag.NArg() > 1 { 371 mode |= noFiltering 372 } 373 mode |= showSource 374 } 375 376 // first, try as package unless forced as command 377 var info *PageInfo 378 if !forceCmd { 379 info = pkgHandler.getPageInfo(abspath, relpath, mode) 380 } 381 382 // second, try as command unless the path is absolute 383 // (the go command invokes godoc w/ absolute paths; don't override) 384 var cinfo *PageInfo 385 if !filepath.IsAbs(path) { 386 abspath = pathpkg.Join(cmdHandler.fsRoot, path) 387 cinfo = cmdHandler.getPageInfo(abspath, relpath, mode) 388 } 389 390 // determine what to use 391 if info == nil || info.IsEmpty() { 392 if cinfo != nil && !cinfo.IsEmpty() { 393 // only cinfo exists - switch to cinfo 394 info = cinfo 395 } 396 } else if cinfo != nil && !cinfo.IsEmpty() { 397 // both info and cinfo exist - use cinfo if info 398 // contains only subdirectory information 399 if info.PAst == nil && info.PDoc == nil { 400 info = cinfo 401 } else { 402 fmt.Printf("use 'godoc %s%s' for documentation on the %s command \n\n", cmdPrefix, relpath, relpath) 403 } 404 } 405 406 if info == nil { 407 log.Fatalf("%s: no such directory or package", flag.Arg(0)) 408 } 409 if info.Err != nil { 410 log.Fatalf("%v", info.Err) 411 } 412 413 if info.PDoc != nil && info.PDoc.ImportPath == target { 414 // Replace virtual /target with actual argument from command line. 415 info.PDoc.ImportPath = flag.Arg(0) 416 } 417 418 // If we have more than one argument, use the remaining arguments for filtering. 419 if flag.NArg() > 1 { 420 args := flag.Args()[1:] 421 rx := makeRx(args) 422 if rx == nil { 423 log.Fatalf("illegal regular expression from %v", args) 424 } 425 426 filter := func(s string) bool { return rx.MatchString(s) } 427 switch { 428 case info.PAst != nil: 429 cmap := ast.NewCommentMap(info.FSet, info.PAst, info.PAst.Comments) 430 ast.FilterFile(info.PAst, filter) 431 // Special case: Don't use templates for printing 432 // so we only get the filtered declarations without 433 // package clause or extra whitespace. 434 for i, d := range info.PAst.Decls { 435 // determine the comments associated with d only 436 comments := cmap.Filter(d).Comments() 437 cn := &printer.CommentedNode{Node: d, Comments: comments} 438 if i > 0 { 439 fmt.Println() 440 } 441 if *html { 442 var buf bytes.Buffer 443 writeNode(&buf, info.FSet, cn) 444 FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil) 445 } else { 446 writeNode(os.Stdout, info.FSet, cn) 447 } 448 fmt.Println() 449 } 450 return 451 452 case info.PDoc != nil: 453 info.PDoc.Filter(filter) 454 } 455 } 456 457 if err := packageText.Execute(os.Stdout, info); err != nil { 458 log.Printf("packageText.Execute: %s", err) 459 } 460 } 461 462 // An httpWriter is an http.ResponseWriter writing to a bytes.Buffer. 463 type httpWriter struct { 464 bytes.Buffer 465 h http.Header 466 code int 467 } 468 469 func (w *httpWriter) Header() http.Header { return w.h } 470 func (w *httpWriter) WriteHeader(code int) { w.code = code }