// 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; } 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 syncTime.set(nil); fallthrough; case 1: // sync failed because no files changed // don't change the sync time 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 first; get usage message out early. switch { case *httpaddr != "": if flag.NArg() != 0 { usage(); } default: if flag.NArg() == 0 { usage(); } } if err := os.Chdir(goroot); err != nil { log.Exitf("chdir %s: %v", goroot, err); } readTemplates(); if *httpaddr != "" { 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("pkgroot = %s\n", *pkgroot); log.Stderrf("tmplroot = %s\n", *tmplroot); handler = loggingHandler(handler); } registerPublicHandlers(http.DefaultServeMux); if *syncCmd != "" { http.Handle("/debug/sync", http.HandlerFunc(dosync)); } // The server may have been restarted; always wait 1sec to // give the forking server a chance to shut down and release // the http port. time.Sleep(1e9); // 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(); // Start http server. if err := http.ListenAndServe(*httpaddr, handler); err != nil { log.Exitf("ListenAndServe %s: %v", *httpaddr, err); } return; } if *html { packageText = packageHtml; parseerrorText = parseerrorHtml; } info := getPageInfo(flag.Arg(0)); if info.PDoc != nil && flag.NArg() > 1 { args := flag.Args(); info.PDoc.Filter(args[1:len(args)]); } if err := packageText.Execute(info, os.Stdout); err != nil { log.Stderrf("packageText.Execute: %s", err); } }