diff options
Diffstat (limited to 'src/cmd/godoc/main.go')
-rw-r--r-- | src/cmd/godoc/main.go | 223 |
1 files changed, 99 insertions, 124 deletions
diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index 5f4210539..23f712ab3 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -38,13 +38,13 @@ import ( "log" "net/http" _ "net/http/pprof" // to serve /debug/pprof/* + "net/url" "os" - "path" + pathpkg "path" "path/filepath" "regexp" "runtime" "strings" - "time" ) const defaultAddr = ":6060" // default webserver address @@ -57,11 +57,6 @@ var ( // file-based index writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files") - // periodic sync - syncCmd = flag.String("sync", "", "sync command; disabled if empty") - syncMin = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0") - syncDelay delayTime // actual sync interval in minutes; usually syncDelay == syncMin, but syncDelay may back off exponentially - // network httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')") serverAddr = flag.String("server", "", "webserver address for command line searches") @@ -69,6 +64,7 @@ var ( // layout control html = flag.Bool("html", false, "print HTML in command-line mode") srcMode = flag.Bool("src", false, "print (exported) source in command-line mode") + urlFlag = flag.String("url", "", "print HTML for named URL") // command-line searches query = flag.Bool("q", false, "arguments are considered search queries") @@ -77,76 +73,7 @@ var ( func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) { contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path! w.WriteHeader(http.StatusNotFound) - servePage(w, "File "+relpath, "", "", contents) -} - -func exec(rw http.ResponseWriter, args []string) (status int) { - r, w, err := os.Pipe() - if err != nil { - log.Printf("os.Pipe(): %v", err) - return 2 - } - - bin := args[0] - fds := []*os.File{nil, w, w} - if *verbose { - log.Printf("executing %v", args) - } - p, err := os.StartProcess(bin, args, &os.ProcAttr{Files: fds, Dir: *goroot}) - defer r.Close() - w.Close() - if err != nil { - log.Printf("os.StartProcess(%q): %v", bin, err) - return 2 - } - - var buf bytes.Buffer - io.Copy(&buf, r) - wait, err := p.Wait() - if err != nil { - os.Stderr.Write(buf.Bytes()) - log.Printf("os.Wait(%d, 0): %v", p.Pid, err) - return 2 - } - if !wait.Success() { - os.Stderr.Write(buf.Bytes()) - log.Printf("executing %v failed", args) - status = 1 // See comment in default case in dosync. - return - } - - if *verbose { - os.Stderr.Write(buf.Bytes()) - } - if rw != nil { - rw.Header().Set("Content-Type", "text/plain; charset=utf-8") - rw.Write(buf.Bytes()) - } - - return -} - -func dosync(w http.ResponseWriter, r *http.Request) { - args := []string{"/bin/sh", "-c", *syncCmd} - switch exec(w, args) { - case 0: - // sync succeeded and some files have changed; - // update package tree. - // TODO(gri): The directory tree may be temporarily out-of-sync. - // Consider keeping separate time stamps so the web- - // page can indicate this discrepancy. - initFSTree() - fallthrough - case 1: - // sync failed because no files changed; - // don't change the package tree - syncDelay.set(time.Duration(*syncMin) * time.Minute) // revert to regular sync schedule - default: - // TODO(r): this cannot happen now, since Wait has a boolean exit condition, - // not an integer. - // sync failed because of an error - back off exponentially, but try at least once a day - syncDelay.backoff(24 * time.Hour) - } + servePage(w, relpath, "File "+relpath, "", "", contents) } func usage() { @@ -225,7 +152,7 @@ func main() { flag.Parse() // Check usage: either server and no args, command line and args, or index creation mode - if (*httpAddr != "") != (flag.NArg() == 0) && !*writeIndex { + if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex { usage() } @@ -239,19 +166,20 @@ func main() { // same is true for the http handlers in initHandlers. if *zipfile == "" { // use file system of underlying OS - *goroot = filepath.Clean(*goroot) // normalize path separator - fs = OS - fsHttp = http.Dir(*goroot) + fs.Bind("/", OS(*goroot), "/", bindReplace) } else { // use file system specified via .zip file (path separator must be '/') rc, err := zip.OpenReader(*zipfile) if err != nil { log.Fatalf("%s: %s\n", *zipfile, err) } - defer rc.Close() // be nice (e.g., -writeIndex mode) - *goroot = path.Join("/", *goroot) // fsHttp paths are relative to '/' - fs = NewZipFS(rc) - fsHttp = NewHttpZipFS(rc, *goroot) + defer rc.Close() // be nice (e.g., -writeIndex mode) + fs.Bind("/", NewZipFS(rc, *zipfile), *goroot, bindReplace) + } + + // Bind $GOPATH trees into Go root. + for _, p := range filepath.SplitList(build.Default.GOPATH) { + fs.Bind("/src/pkg", OS(p), "/src", bindAfter) } readTemplates() @@ -266,7 +194,6 @@ func main() { log.Println("initialize file systems") *verbose = true // want to see what happens initFSTree() - initDirTrees() *indexThrottle = 1 updateIndex() @@ -286,6 +213,44 @@ func main() { return } + // Print content that would be served at the URL *urlFlag. + if *urlFlag != "" { + registerPublicHandlers(http.DefaultServeMux) + // Try up to 10 fetches, following redirects. + urlstr := *urlFlag + for i := 0; i < 10; i++ { + // Prepare request. + u, err := url.Parse(urlstr) + if err != nil { + log.Fatal(err) + } + req := &http.Request{ + URL: u, + } + + // Invoke default HTTP handler to serve request + // to our buffering httpWriter. + w := &httpWriter{h: http.Header{}, code: 200} + http.DefaultServeMux.ServeHTTP(w, req) + + // Return data, error, or follow redirect. + switch w.code { + case 200: // ok + os.Stdout.Write(w.Bytes()) + return + case 301, 302, 303, 307: // redirect + redirect := w.h.Get("Location") + if redirect == "" { + log.Fatalf("HTTP %d without Location header", w.code) + } + urlstr = redirect + default: + log.Fatalf("HTTP error %d", w.code) + } + } + log.Fatalf("too many redirects") + } + if *httpAddr != "" { // HTTP server mode. var handler http.Handler = http.DefaultServeMux @@ -303,41 +268,20 @@ func main() { default: log.Print("identifier search index enabled") } - if !fsMap.IsEmpty() { - log.Print("user-defined mapping:") - fsMap.Fprint(os.Stderr) - } + fs.Fprint(os.Stderr) handler = loggingHandler(handler) } registerPublicHandlers(http.DefaultServeMux) - if *syncCmd != "" { - http.Handle("/debug/sync", http.HandlerFunc(dosync)) - } + + // Playground handlers are not available in local godoc. + http.HandleFunc("/compile", disabledHandler) + http.HandleFunc("/share", disabledHandler) // Initialize default directory tree with corresponding timestamp. // (Do it in a goroutine so that launch is quick.) go initFSTree() - // Initialize directory trees for user-defined file systems (-path flag). - initDirTrees() - - // Start sync goroutine, if enabled. - if *syncCmd != "" && *syncMin > 0 { - syncDelay.set(*syncMin) // initial sync delay - go func() { - for { - dosync(nil, nil) - delay, _ := syncDelay.get() - dt := delay.(time.Duration) - if *verbose { - log.Printf("next sync in %s", dt) - } - time.Sleep(dt) - } - }() - } - // Immediately update metadata. updateMetadata() // Periodically refresh metadata. @@ -374,27 +318,38 @@ func main() { return } - // determine paths + // Determine paths. + // + // If we are passed an operating system path like . or ./foo or /foo/bar or c:\mysrc, + // we need to map that path somewhere in the fs name space so that routines + // like getPageInfo will see it. We use the arbitrarily-chosen virtual path "/target" + // for this. That is, if we get passed a directory like the above, we map that + // directory so that getPageInfo sees it as /target. + const target = "/target" const cmdPrefix = "cmd/" path := flag.Arg(0) var forceCmd bool - if strings.HasPrefix(path, ".") { - // assume cwd; don't assume -goroot + var abspath, relpath string + if filepath.IsAbs(path) { + fs.Bind(target, OS(path), "/", bindReplace) + abspath = target + } else if build.IsLocalImport(path) { cwd, _ := os.Getwd() // ignore errors path = filepath.Join(cwd, path) + fs.Bind(target, OS(path), "/", bindReplace) + abspath = target } else if strings.HasPrefix(path, cmdPrefix) { path = path[len(cmdPrefix):] forceCmd = true - } - relpath := path - abspath := path - if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" { + } else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" { + fs.Bind(target, OS(bp.Dir), "/", bindReplace) + abspath = target relpath = bp.ImportPath - abspath = bp.Dir - } else if !filepath.IsAbs(path) { - abspath = absolutePath(path, pkgHandler.fsRoot) } else { - relpath = relativeURL(path) + abspath = pathpkg.Join(pkgHandler.fsRoot, path) + } + if relpath == "" { + relpath = abspath } var mode PageInfoMode @@ -422,7 +377,7 @@ func main() { // (the go command invokes godoc w/ absolute paths; don't override) var cinfo PageInfo if !filepath.IsAbs(path) { - abspath = absolutePath(path, cmdHandler.fsRoot) + abspath = pathpkg.Join(cmdHandler.fsRoot, path) cinfo = cmdHandler.getPageInfo(abspath, relpath, "", mode) } @@ -445,6 +400,10 @@ func main() { if info.Err != nil { log.Fatalf("%v", info.Err) } + if info.PDoc != nil && info.PDoc.ImportPath == target { + // Replace virtual /target with actual argument from command line. + info.PDoc.ImportPath = flag.Arg(0) + } // If we have more than one argument, use the remaining arguments for filtering if flag.NArg() > 1 { @@ -485,3 +444,19 @@ func main() { log.Printf("packageText.Execute: %s", err) } } + +// An httpWriter is an http.ResponseWriter writing to a bytes.Buffer. +type httpWriter struct { + bytes.Buffer + h http.Header + code int +} + +func (w *httpWriter) Header() http.Header { return w.h } +func (w *httpWriter) WriteHeader(code int) { w.code = code } + +// disabledHandler serves a 501 "Not Implemented" response. +func disabledHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) + fmt.Fprint(w, "This functionality is not available via local godoc.") +} |