]> Cypherpunks repositories - gostls13.git/commitdiff
move godoc to src/cmd/godoc
authorRobert Griesemer <gri@golang.org>
Tue, 16 Jun 2009 16:14:06 +0000 (09:14 -0700)
committerRobert Griesemer <gri@golang.org>
Tue, 16 Jun 2009 16:14:06 +0000 (09:14 -0700)
R=rsc
DELTA=945  (944 added, 0 deleted, 1 changed)
OCL=30315
CL=30341

lib/godoc/package.html [new file with mode: 0644]
lib/godoc/package.txt [new file with mode: 0644]
lib/godoc/parseerror.html [new file with mode: 0644]
lib/godoc/parseerror.txt [new file with mode: 0644]
src/cmd/godoc/Makefile [new file with mode: 0644]
src/cmd/godoc/godoc.go [new file with mode: 0644]
src/cmd/make.bash

diff --git a/lib/godoc/package.html b/lib/godoc/package.html
new file mode 100644 (file)
index 0000000..8d43074
--- /dev/null
@@ -0,0 +1,54 @@
+{.section Dirs}
+       <h2>Subdirectories</h2>
+       {.repeated section @}
+               <a href="{Name|html}/">{Name|html}</a><br />
+       {.end}
+       <hr />
+{.end}
+{.section PDoc}
+       <h1>package {PackageName|html}</h1>
+       <p><code>import "{ImportPath|html}"</code></p>
+
+       {Doc|html-comment}
+       {.section Consts}
+               <h2>Constants</h2>
+               {.repeated section @}
+                       {Doc|html-comment}
+                       <pre>{Decl|html}</pre>
+               {.end}
+       {.end}
+       {.section Vars}
+               <hr />
+               <h2>Variables</h2>
+               {.repeated section @}
+                       {Doc|html-comment}
+                       <pre>{Decl|html}</pre>
+               {.end}
+       {.end}
+       {.section Funcs}
+               <hr />
+               {.repeated section @}
+                       <h2>func {Name|html}</h2>
+                       <p><code>{Decl|html}</code></p>
+                       {Doc|html-comment}
+               {.end}
+       {.end}
+       {.section Types}
+               {.repeated section @}
+                       <hr />
+                       <h2>type {.section Type}{Name|html}{.end}</h2>
+                       {Doc|html-comment}
+                       <p><pre>{Decl|html}</pre></p>
+                       {.repeated section Factories}
+                               <h3>func {Name|html}</h3>
+                               <p><code>{Decl|html}</code></p>
+                               {Doc|html-comment}
+                       {.end}
+                       {.repeated section Methods}
+                               <h3>func ({Recv|html}) {Name|html}</h3>
+                               <p><code>{Decl|html}</code></p>
+                               {Doc|html-comment}
+                       {.end}
+               {.end}
+       {.end}
+{.end}
diff --git a/lib/godoc/package.txt b/lib/godoc/package.txt
new file mode 100644 (file)
index 0000000..cd85013
--- /dev/null
@@ -0,0 +1,70 @@
+{.section Dirs}
+SUBDIRECTORIES
+{.repeated section @}
+       {Name}
+{.end}
+
+{.end}
+{.section PDoc}
+PACKAGE
+
+package {PackageName}
+import "{ImportPath}"
+{.section Doc}
+
+{@}
+{.end}
+{.section Consts}
+
+CONSTANTS
+{.repeated section @}
+{# the .repeated section, .section idiom skips over nils in the array}
+
+{Decl}
+
+{Doc}
+{.end}
+{.end}
+{.section Vars}
+
+VARIABLES
+{.repeated section @}
+
+{Decl}
+
+{Doc}
+{.end}
+{.end}
+{.section Funcs}
+
+FUNCTIONS
+{.repeated section @}
+
+{Decl}
+
+{Doc}
+{.end}
+{.end}
+{.section Types}
+
+TYPES
+{.repeated section @}
+
+{Decl}
+
+{Doc}
+{.repeated section Factories}
+
+{Decl}
+
+{Doc}
+{.end}
+{.repeated section Methods}
+
+{Decl}
+
+{Doc}
+{.end}
+{.end}
+{.end}
+{.end}
diff --git a/lib/godoc/parseerror.html b/lib/godoc/parseerror.html
new file mode 100644 (file)
index 0000000..4d12425
--- /dev/null
@@ -0,0 +1,4 @@
+<h1>Parse errors in {filename}</h1>
+<pre>
+{.repeated section list}
+{src}{.section msg}<b><font color=red>«{msg|html}»</font></b>{.end}{.end}</pre>
diff --git a/lib/godoc/parseerror.txt b/lib/godoc/parseerror.txt
new file mode 100644 (file)
index 0000000..609cb51
--- /dev/null
@@ -0,0 +1,6 @@
+parse errors:
+{.repeated section list}
+{.section msg}
+{filename}:{line}: {msg}
+{.end}
+{.end}
diff --git a/src/cmd/godoc/Makefile b/src/cmd/godoc/Makefile
new file mode 100644 (file)
index 0000000..8365f9f
--- /dev/null
@@ -0,0 +1,21 @@
+# 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.
+
+include $(GOROOT)/src/Make.$(GOARCH)
+
+TARG=godoc
+OFILES=\
+       godoc.$O\
+
+$(TARG): $(OFILES)
+       $(LD) -o $(TARG) $(OFILES)
+
+clean:
+       rm -f $(OFILES) $(TARG)
+
+install: $(TARG)
+       cp $(TARG) $(HOME)/bin/$(TARG)
+
+%.$O:  %.go
+       $(GC) $<
diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go
new file mode 100644 (file)
index 0000000..971ef44
--- /dev/null
@@ -0,0 +1,707 @@
+// 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 (
+       "container/vector";
+       "flag";
+       "fmt";
+       "go/ast";
+       "go/doc";
+       "go/parser";
+       "go/printer";
+       "go/token";
+       "http";
+       "io";
+       "log";
+       "net";
+       "os";
+       pathutil "path";
+       "sort";
+       "strings";
+       "sync";
+       "syscall";
+       "tabwriter";
+       "template";
+       "time";
+)
+
+
+const Pkg = "/pkg/"    // name for auto-generated package documentation tree
+
+
+type timeStamp struct {
+       mutex sync.RWMutex;
+       seconds int64;
+}
+
+
+func (ts *timeStamp) set() {
+       ts.mutex.Lock();
+       ts.seconds = time.Seconds();
+       ts.mutex.Unlock();
+}
+
+
+func (ts *timeStamp) get() int64 {
+       ts.mutex.RLock();
+       defer ts.mutex.RUnlock();
+       return ts.seconds;
+}
+
+
+var (
+       verbose = flag.Bool("v", false, "verbose mode");
+
+       // file system roots
+       goroot string;
+       pkgroot = flag.String("pkgroot", "src/pkg", "root package source directory (if unrooted, relative to goroot)");
+       tmplroot = flag.String("tmplroot", "src/cmd/godoc", "root template directory (if unrooted, relative to goroot)");
+
+       // periodic sync
+       syncCmd = flag.String("sync", "", "sync command; disabled if empty");
+       syncMin = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0");
+       syncTime timeStamp;  // time of last p4 sync
+
+       // layout control
+       tabwidth = flag.Int("tabwidth", 4, "tab width");
+       html = flag.Bool("html", false, "print HTML in command-line mode");
+
+       // server control
+       httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')");
+)
+
+
+func init() {
+       var err os.Error;
+       goroot, err = os.Getenv("GOROOT");
+       if err != nil {
+               goroot = "/home/r/go-release/go";
+       }
+       flag.StringVar(&goroot, "goroot", goroot, "Go root directory");
+       syncTime.set();  // have a reasonable initial value
+}
+
+
+// ----------------------------------------------------------------------------
+// Support
+
+func isDir(name string) bool {
+       d, err := os.Stat(name);
+       return err == nil && d.IsDirectory();
+}
+
+
+func isGoFile(dir *os.Dir) bool {
+       return dir.IsRegular() && pathutil.Ext(dir.Name) == ".go";
+}
+
+
+func isPkgDir(dir *os.Dir) bool {
+       return dir.IsDirectory() && dir.Name != "_obj";
+}
+
+
+func makeTabwriter(writer io.Writer) *tabwriter.Writer {
+       return tabwriter.NewWriter(writer, *tabwidth, 1, byte(' '), 0);
+}
+
+
+// ----------------------------------------------------------------------------
+// Parsing
+
+// A single error in the parsed file.
+type parseError struct {
+       src []byte;     // source before error
+       line int;       // line number of error
+       msg string;     // error message
+}
+
+
+// All the errors in the parsed file, plus surrounding source code.
+// Each error has a slice giving the source text preceding it
+// (starting where the last error occurred).  The final element in list[]
+// has msg = "", to give the remainder of the source code.
+// This data structure is handed to the templates parseerror.txt and parseerror.html.
+//
+type parseErrors struct {
+       filename string;        // path to file
+       list []parseError;      // the errors
+       src []byte;     // the file's entire source code
+}
+
+
+// Parses a file (path) and returns the corresponding AST and
+// a sorted list (by file position) of errors, if any.
+//
+func parse(path string, mode uint) (*ast.Program, *parseErrors) {
+       src, err := io.ReadFile(path);
+       if err != nil {
+               log.Stderrf("ReadFile %s: %v", path, err);
+               errs := []parseError{parseError{nil, 0, err.String()}};
+               return nil, &parseErrors{path, errs, nil};
+       }
+
+       prog, err := parser.Parse(src, mode);
+       if err != nil {
+               // sort and convert error list
+               if errors, ok := err.(parser.ErrorList); ok {
+                       sort.Sort(errors);
+                       errs := make([]parseError, len(errors) + 1);    // +1 for final fragment of source
+                       offs := 0;
+                       for i, r := range errors {
+                               // Should always be true, but check for robustness.
+                               if 0 <= r.Pos.Offset && r.Pos.Offset <= len(src) {
+                                       errs[i].src = src[offs : r.Pos.Offset];
+                                       offs = r.Pos.Offset;
+                               }
+                               errs[i].line = r.Pos.Line;
+                               errs[i].msg = r.Msg;
+                       }
+                       errs[len(errors)].src = src[offs : len(src)];
+                       return nil, &parseErrors{path, errs, src};
+               } else {
+                       // TODO should have some default handling here to be more robust
+                       panic("unreachable");
+               }
+       }
+
+       return prog, nil;
+}
+
+
+// ----------------------------------------------------------------------------
+// Templates
+
+// Return text for an AST node.
+func nodeText(node interface{}, mode uint) []byte {
+       var buf io.ByteBuffer;
+       tw := makeTabwriter(&buf);
+       printer.Fprint(tw, node, mode);
+       tw.Flush();
+       return buf.Data();
+}
+
+
+// Convert x, whatever it is, to text form.
+func toText(x interface{}) []byte {
+       type String interface { String() string }
+
+       switch v := x.(type) {
+       case []byte:
+               return v;
+       case string:
+               return io.StringBytes(v);
+       case String:
+               return io.StringBytes(v.String());
+       case ast.Decl:
+               return nodeText(v, printer.ExportsOnly);
+       case ast.Expr:
+               return nodeText(v, printer.ExportsOnly);
+       }
+       var buf io.ByteBuffer;
+       fmt.Fprint(&buf, x);
+       return buf.Data();
+}
+
+
+// Template formatter for "html" format.
+func htmlFmt(w io.Writer, x interface{}, format string) {
+       template.HtmlEscape(w, toText(x));
+}
+
+
+// Template formatter for "html-comment" format.
+func htmlCommentFmt(w io.Writer, x interface{}, format string) {
+       doc.ToHtml(w, toText(x));
+}
+
+
+// Template formatter for "" (default) format.
+func textFmt(w io.Writer, x interface{}, format string) {
+       w.Write(toText(x));
+}
+
+
+var fmap = template.FormatterMap{
+       "": textFmt,
+       "html": htmlFmt,
+       "html-comment": htmlCommentFmt,
+}
+
+
+func readTemplate(name string) *template.Template {
+       path := pathutil.Join(*tmplroot, name);
+       data, err := io.ReadFile(path);
+       if err != nil {
+               log.Exitf("ReadFile %s: %v", path, err);
+       }
+       t, err1 := template.Parse(string(data), fmap);
+       if err1 != nil {
+               log.Exitf("%s: %v", name, err);
+       }
+       return t;
+}
+
+
+var godocHtml *template.Template
+var packageHtml *template.Template
+var packageText *template.Template
+var parseerrorHtml *template.Template;
+var parseerrorText *template.Template;
+
+func readTemplates() {
+       // have to delay until after flags processing,
+       // so that main has chdir'ed to goroot.
+       godocHtml = readTemplate("godoc.html");
+       packageHtml = readTemplate("package.html");
+       packageText = readTemplate("package.txt");
+       parseerrorHtml = readTemplate("parseerror.html");
+       parseerrorText = readTemplate("parseerror.txt");
+}
+
+
+// ----------------------------------------------------------------------------
+// Generic HTML wrapper
+
+func servePage(c *http.Conn, title, content interface{}) {
+       type Data struct {
+               title interface{};
+               header interface{};
+               timestamp string;
+               content interface{};
+       }
+
+       var d Data;
+       d.title = title;
+       d.header = title;
+       d.timestamp = time.SecondsToLocalTime(syncTime.get()).String();
+       d.content = content;
+       godocHtml.Execute(&d, c);
+}
+
+
+func serveText(c *http.Conn, text []byte) {
+       c.SetHeader("content-type", "text/plain; charset=utf-8");
+       c.Write(text);
+}
+
+
+// ----------------------------------------------------------------------------
+// Files
+
+func serveParseErrors(c *http.Conn, errors *parseErrors) {
+       // format errors
+       var buf io.ByteBuffer;
+       parseerrorHtml.Execute(errors, &buf);
+       servePage(c, errors.filename + " - Parse Errors", buf.Data());
+}
+
+
+func serveGoSource(c *http.Conn, name string) {
+       prog, errors := parse(name, parser.ParseComments);
+       if errors != nil {
+               serveParseErrors(c, errors);
+               return;
+       }
+
+       var buf io.ByteBuffer;
+       fmt.Fprintln(&buf, "<pre>");
+       template.HtmlEscape(&buf, nodeText(prog, printer.DocComments));
+       fmt.Fprintln(&buf, "</pre>");
+
+       servePage(c, name + " - Go source", buf.Data());
+}
+
+
+var fileServer = http.FileServer(".", "");
+
+func serveFile(c *http.Conn, req *http.Request) {
+       // pick off special cases and hand the rest to the standard file server
+       switch {
+       case req.Url.Path == "/":
+               // serve landing page.
+               // TODO: hide page from ordinary file serving.
+               // writing doc/index.html will take care of that.
+               http.ServeFile(c, req, "doc/root.html");
+
+       case req.Url.Path == "/doc/root.html":
+               // hide landing page from its real name
+               // TODO why - there is no reason for this (remove eventually)
+               http.NotFound(c, req);
+
+       case pathutil.Ext(req.Url.Path) == ".go":
+               serveGoSource(c, req.Url.Path[1 : len(req.Url.Path)]);  // strip leading '/' from name
+
+       default:
+               // TODO not good enough - don't want to download files
+               // want to see them
+               fileServer.ServeHTTP(c, req);
+       }
+}
+
+
+// ----------------------------------------------------------------------------
+// Packages
+
+type pakDesc struct {
+       dirname string;  // relative to goroot
+       pakname string;  // same as last component of importpath
+       importpath string;      // import "___"
+       filenames map[string] bool;  // set of file (names) belonging to this package
+}
+
+
+// TODO if we don't plan to use the directory information, simplify to []string
+type dirList []*os.Dir
+
+func (d dirList) Len() int  { return len(d) }
+func (d dirList) Less(i, j int) bool  { return d[i].Name < d[j].Name }
+func (d dirList) Swap(i, j int)  { d[i], d[j] = d[j], d[i] }
+
+
+func isPackageFile(dirname, filename, pakname string) bool {
+       // ignore test files
+       if strings.HasSuffix(filename, "_test.go") {
+               return false;
+       }
+
+       // determine package name
+       prog, errors := parse(dirname + "/" + filename, parser.PackageClauseOnly);
+       if prog == nil {
+               return false;
+       }
+
+       return prog != nil && prog.Name.Value == pakname;
+}
+
+
+// Returns the canonical URL path, the package denoted by path, and
+// the list of sub-directories in the corresponding package directory.
+// If there is no such package, the package descriptor pd is nil.
+// If there are no sub-directories, the dirs list is nil.
+func findPackage(path string) (canonical string, pd *pakDesc, dirs dirList) {
+       canonical = pathutil.Clean(Pkg + path) + "/";
+
+       // get directory contents, if possible
+       importpath := pathutil.Clean(path);  // no trailing '/'
+       dirname := pathutil.Join(*pkgroot, importpath);
+       if !isDir(dirname) {
+               return;
+       }
+
+       fd, err1 := os.Open(dirname, os.O_RDONLY, 0);
+       if err1 != nil {
+               log.Stderrf("open %s: %v", dirname, err1);
+               return;
+       }
+
+       list, err2 := fd.Readdir(-1);
+       if err2 != nil {
+               log.Stderrf("readdir %s: %v", dirname, err2);
+               return;
+       }
+
+       // the package name is is the directory name within its parent
+       _, pakname := pathutil.Split(dirname);
+
+       // collect all files belonging to the package and count the
+       // number of sub-directories
+       filenames := make(map[string]bool);
+       nsub := 0;
+       for i, entry := range list {
+               switch {
+               case isGoFile(&entry) && isPackageFile(dirname, entry.Name, pakname):
+                       // add file to package desc
+                       if tmp, found := filenames[entry.Name]; found {
+                               panic("internal error: same file added more than once: " + entry.Name);
+                       }
+                       filenames[entry.Name] = true;
+               case isPkgDir(&entry):
+                       nsub++;
+               }
+       }
+
+       // make the list of sub-directories, if any
+       var subdirs dirList;
+       if nsub > 0 {
+               subdirs = make(dirList, nsub);
+               nsub = 0;
+               for i, entry := range list {
+                       if isPkgDir(&entry) {
+                               // make a copy here so sorting (and other code) doesn't
+                               // have to make one every time an entry is moved
+                               copy := new(os.Dir);
+                               *copy = entry;
+                               subdirs[nsub] = copy;
+                               nsub++;
+                       }
+               }
+               sort.Sort(subdirs);
+       }
+
+       // if there are no package files, then there is no package
+       if len(filenames) == 0 {
+               return canonical, nil, subdirs;
+       }
+
+       return canonical, &pakDesc{dirname, pakname, importpath, filenames}, subdirs;
+}
+
+
+func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) {
+       if p == nil {
+               return nil, nil;
+       }
+
+       // compute documentation
+       var r doc.DocReader;
+       i := 0;
+       for filename := range p.filenames {
+               prog, err := parse(p.dirname + "/" + filename, parser.ParseComments);
+               if err != nil {
+                       return nil, err;
+               }
+               if i == 0 {
+                       // first file - initialize doc
+                       r.Init(prog.Name.Value, p.importpath);
+               }
+               i++;
+               r.AddProgram(prog);
+       }
+
+       return r.Doc(), nil;
+}
+
+
+type PageInfo struct {
+       PDoc *doc.PackageDoc;
+       Dirs dirList;
+}
+
+func servePkg(c *http.Conn, r *http.Request) {
+       path := r.Url.Path;
+       path = path[len(Pkg) : len(path)];
+       canonical, desc, dirs := findPackage(path);
+
+       if r.Url.Path != canonical {
+               http.Redirect(c, canonical, http.StatusMovedPermanently);
+               return;
+       }
+
+       pdoc, errors := desc.Doc();
+       if errors != nil {
+               serveParseErrors(c, errors);
+               return;
+       }
+
+       var buf io.ByteBuffer;
+       if false {      // TODO req.Params["format"] == "text"
+               err := packageText.Execute(PageInfo{pdoc, dirs}, &buf);
+               if err != nil {
+                       log.Stderrf("packageText.Execute: %s", err);
+               }
+               serveText(c, buf.Data());
+               return;
+       }
+
+       err := packageHtml.Execute(PageInfo{pdoc, dirs}, &buf);
+       if err != nil {
+               log.Stderrf("packageHtml.Execute: %s", err);
+       }
+
+       if path == "" {
+               path = ".";  // don't display an empty path
+       }
+       servePage(c, path + " - Go package documentation", buf.Data());
+}
+
+
+// ----------------------------------------------------------------------------
+// Server
+
+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 exec(c *http.Conn, args []string) bool {
+       r, w, err := os.Pipe();
+       if err != nil {
+               log.Stderrf("os.Pipe(): %v\n", err);
+               return false;
+       }
+
+       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 false;
+       }
+
+       var buf io.ByteBuffer;
+       io.Copy(r, &buf);
+       wait, err := os.Wait(pid, 0);
+       if err != nil {
+               os.Stderr.Write(buf.Data());
+               log.Stderrf("os.Wait(%d, 0): %v\n", pid, err);
+               return false;
+       }
+       if !wait.Exited() || wait.ExitStatus() != 0 {
+               os.Stderr.Write(buf.Data());
+               log.Stderrf("executing %v failed (exit status = %d)", args, wait.ExitStatus());
+               return false;
+       }
+
+       if *verbose {
+               os.Stderr.Write(buf.Data());
+       }
+       if c != nil {
+               c.SetHeader("content-type", "text/plain; charset=utf-8");
+               c.Write(buf.Data());
+       }
+
+       return true;
+}
+
+
+func sync(c *http.Conn, r *http.Request) {
+       args := []string{"/bin/sh", "-c", *syncCmd};
+       if !exec(c, args) {
+               *syncMin = 0;  // disable sync
+               return;
+       }
+       syncTime.set();
+}
+
+
+func usage() {
+       fmt.Fprintf(os.Stderr,
+               "usage: godoc package [name ...]\n"
+               "       godoc -http=:6060\n"
+       );
+       flag.PrintDefaults();
+       os.Exit(1);
+}
+
+
+func main() {
+       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);
+               }
+
+               http.Handle(Pkg, http.HandlerFunc(servePkg));
+               if *syncCmd != "" {
+                       http.Handle("/debug/sync", http.HandlerFunc(sync));
+               }
+               http.Handle("/", http.HandlerFunc(serveFile));
+
+               // 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 {
+                       go func() {
+                               if *verbose {
+                                       log.Stderrf("sync every %dmin", *syncMin);
+                               }
+                               for *syncMin > 0 {
+                                       sync(nil, nil);
+                                       time.Sleep(int64(*syncMin) * (60 * 1e9));
+                               }
+                               if *verbose {
+                                       log.Stderrf("periodic sync stopped");
+                               }
+                       }();
+               }
+
+               if err := http.ListenAndServe(*httpaddr, handler); err != nil {
+                       log.Exitf("ListenAndServe %s: %v", *httpaddr, err)
+               }
+               return;
+       }
+
+       if *html {
+               packageText = packageHtml;
+               parseerrorText = parseerrorHtml;
+       }
+
+       _, desc, dirs := findPackage(flag.Arg(0));
+       pdoc, errors := desc.Doc();
+       if errors != nil {
+               err := parseerrorText.Execute(errors, os.Stderr);
+               if err != nil {
+                       log.Stderrf("parseerrorText.Execute: %s", err);
+               }
+               os.Exit(1);
+       }
+
+       if pdoc != nil && flag.NArg() > 1 {
+               args := flag.Args();
+               pdoc.Filter(args[1 : len(args)]);
+       }
+
+       packageText.Execute(PageInfo{pdoc, dirs}, os.Stdout);
+}
index ca4517218e3bf22d5aaa56b3e64242e6f6e38e8f..2019b440ff7dfc04315f6e0e696d4fa0515fb438 100644 (file)
@@ -18,7 +18,7 @@ bash mkenam
 make enam.o
 cd ..
 
-for i in cc ${O}l ${O}a ${O}c gc ${O}g ar db nm acid cov godefs prof gotest
+for i in cc ${O}l ${O}a ${O}c gc ${O}g ar db nm acid cov godefs godoc prof gotest
 do
        echo; echo; echo %%%% making $i %%%%; echo
        cd $i