// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // godoc: Go Documentation Server // Web server tree: // // http://godoc/ main landing page // http://godoc/doc/ serve from $GOROOT/doc - spec, mem, tutorial, etc. // http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed // http://godoc/cmd/ serve documentation about commands (TODO) // http://godoc/pkg/ serve documentation about packages // (idea is if you say import "compress/zlib", you go to // http://godoc/pkg/compress/zlib) // // Command-line interface: // // godoc packagepath [name ...] // // godoc compress/zlib // - prints doc for package compress/zlib // godoc crypto/block Cipher NewCMAC // - prints doc for Cipher and NewCMAC in package crypto/block package main import ( "bytes"; "flag"; "fmt"; "http"; "io"; "log"; "os"; "time"; ) var ( // 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 delay in minutes; usually syncDelay == syncMin, but delay may back off exponentially // server control httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')"); // layout control html = flag.Bool("html", false, "print HTML in command-line mode"); ) func exec(c *http.Conn, args []string) (status int) { r, w, err := os.Pipe(); if err != nil { log.Stderrf("os.Pipe(): %v\n", err); return 2; } bin := args[0]; fds := []*os.File{nil, w, w}; if *verbose { log.Stderrf("executing %v", args) } pid, err := os.ForkExec(bin, args, os.Environ(), goroot, fds); defer r.Close(); w.Close(); if err != nil { log.Stderrf("os.ForkExec(%q): %v\n", bin, err); return 2; } var buf bytes.Buffer; io.Copy(&buf, r); wait, err := os.Wait(pid, 0); if err != nil { os.Stderr.Write(buf.Bytes()); log.Stderrf("os.Wait(%d, 0): %v\n", pid, err); return 2; } status = wait.ExitStatus(); if !wait.Exited() || status > 1 { os.Stderr.Write(buf.Bytes()); log.Stderrf("executing %v failed (exit status = %d)", args, status); return; } if *verbose { os.Stderr.Write(buf.Bytes()) } if c != nil { c.SetHeader("content-type", "text/plain; charset=utf-8"); c.Write(buf.Bytes()); } return; } // Maximum directory depth, adjust as needed. const maxDirDepth = 24 func dosync(c *http.Conn, r *http.Request) { args := []string{"/bin/sh", "-c", *syncCmd}; switch exec(c, 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. fsTree.set(newDirectory(".", maxDirDepth)); fallthrough; case 1: // sync failed because no files changed; // don't change the package tree syncDelay.set(*syncMin) // revert to regular sync schedule default: // sync failed because of an error - back off exponentially, but try at least once a day syncDelay.backoff(24 * 60) } } func usage() { fmt.Fprintf(os.Stderr, "usage: godoc package [name ...]\n" " godoc -http=:6060\n"); flag.PrintDefaults(); os.Exit(2); } func loggingHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(c *http.Conn, req *http.Request) { log.Stderrf("%s\t%s", c.RemoteAddr, req.URL); h.ServeHTTP(c, req); }) } func main() { flag.Usage = usage; flag.Parse(); // Check usage: either server and no args, or command line and args if (*httpaddr != "") != (flag.NArg() == 0) { usage() } if *tabwidth < 0 { log.Exitf("negative tabwidth %d", *tabwidth) } if err := os.Chdir(goroot); err != nil { log.Exitf("chdir %s: %v", goroot, err) } readTemplates(); if *httpaddr != "" { // HTTP server mode. var handler http.Handler = http.DefaultServeMux; if *verbose { log.Stderrf("Go Documentation Server\n"); log.Stderrf("address = %s\n", *httpaddr); log.Stderrf("goroot = %s\n", goroot); log.Stderrf("cmdroot = %s\n", *cmdroot); log.Stderrf("pkgroot = %s\n", *pkgroot); log.Stderrf("tmplroot = %s\n", *tmplroot); log.Stderrf("tabwidth = %d\n", *tabwidth); handler = loggingHandler(handler); } registerPublicHandlers(http.DefaultServeMux); if *syncCmd != "" { http.Handle("/debug/sync", http.HandlerFunc(dosync)) } // Initialize directory tree with corresponding timestamp. // Do it in two steps: // 1) set timestamp right away so that the indexer is kicked on fsTree.set(nil); // 2) compute initial directory tree in a goroutine so that launch is quick go func() { fsTree.set(newDirectory(".", maxDirDepth)) }(); // Start sync goroutine, if enabled. if *syncCmd != "" && *syncMin > 0 { syncDelay.set(*syncMin); // initial sync delay go func() { for { dosync(nil, nil); delay, _ := syncDelay.get(); if *verbose { log.Stderrf("next sync in %dmin", delay.(int)) } time.Sleep(int64(delay.(int)) * 60e9); } }(); } // Start indexing goroutine. go indexer(); // The server may have been restarted; always wait 1sec to // give the forking server a chance to shut down and release // the http port. // TODO(gri): Do we still need this? time.Sleep(1e9); // Start http server. if err := http.ListenAndServe(*httpaddr, handler); err != nil { log.Exitf("ListenAndServe %s: %v", *httpaddr, err) } return; } // Command line mode. if *html { packageText = packageHTML } info := pkgHandler.getPageInfo(flag.Arg(0)); if info.PDoc == nil && info.Dirs == nil { // try again, this time assume it's a command info = cmdHandler.getPageInfo(flag.Arg(0)) } if info.PDoc != nil && flag.NArg() > 1 { args := flag.Args(); info.PDoc.Filter(args[1:]); } if err := packageText.Execute(info, os.Stdout); err != nil { log.Stderrf("packageText.Execute: %s", err) } }