| // 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 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 != "" { |
| // 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("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)); |
| } |
| |
| // 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; |
| parseerrorText = parseerrorHtml; |
| } |
| |
| 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:len(args)]); |
| } |
| |
| if err := packageText.Execute(info, os.Stdout); err != nil { |
| log.Stderrf("packageText.Execute: %s", err); |
| } |
| } |