From e97121a187992db5db900c412e2b0c47864f0837 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 13 Apr 2009 13:28:53 -0700 Subject: [PATCH] working checkpoint. add comment describing new web server tree. make room for command line interface. use new path package to get rid of doubled slashes. use new Chdir function to avoid goroot + everything. implement new /pkg/ tree instead of using regexps. R=gri DELTA=267 (103 added, 72 deleted, 92 changed) OCL=27150 CL=27367 --- usr/gri/pretty/godoc.go | 337 ++++++++++++++++++++++------------------ 1 file changed, 184 insertions(+), 153 deletions(-) diff --git a/usr/gri/pretty/godoc.go b/usr/gri/pretty/godoc.go index afedd79491..f3903e8a9b 100644 --- a/usr/gri/pretty/godoc.go +++ b/usr/gri/pretty/godoc.go @@ -4,6 +4,26 @@ // godoc: Go Documentation Server +// Web server tree: +// +// http://godoc/ main landing page (TODO) +// http://godoc/doc/ serve from $GOROOT/doc - spec, mem, tutorial, etc. (TODO) +// 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 proto +// godoc compress/zlib Cipher NewCMAC +// - prints doc for Cipher and NewCMAC in package crypto/block + + package main import ( @@ -17,16 +37,15 @@ import ( "net"; "os"; "parser"; + pathutil "path"; "sort"; "tabwriter"; "template"; "time"; "token"; - "regexp"; "vector"; "astprinter"; - "compilation"; // TODO removing this causes link errors - why? "docprinter"; ) @@ -34,7 +53,12 @@ import ( // TODO // - uniform use of path, filename, dirname, pakname, etc. // - fix weirdness with double-/'s in paths -// - cleanup uses of *root, GOROOT, etc. (quite a mess at the moment) +// - split http service into its own source file + + +const usageString = + "usage: godoc package [name ...]\n" + " godoc -http=:6060\n" const ( @@ -43,65 +67,41 @@ const ( ) -func getenv(varname string) string { - value, err := os.Getenv(varname); - return value; -} - - var ( - GOROOT = getenv("GOROOT"); + goroot string; - // server control verbose = flag.Bool("v", false, "verbose mode"); - port = flag.String("port", "6060", "server port"); - root = flag.String("root", GOROOT, "root directory"); + + // server control + httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')"); // layout control tabwidth = flag.Int("tabwidth", 4, "tab width"); - usetabs = flag.Bool("usetabs", false, "align with tabs instead of blanks"); + usetabs = flag.Bool("tabs", false, "align with tabs instead of spaces"); ) -// ---------------------------------------------------------------------------- -// Support - -func cleanPath(s string) string { - for i := 0; i < len(s); i++ { - if s[i] == '/' { - i++; - j := i; - for j < len(s) && s[j] == '/' { - j++; - } - if j > i { // more then one '/' - return s[0 : i] + cleanPath(s[j : len(s)]); - } - } +func init() { + var err *os.Error; + goroot, err = os.Getenv("GOROOT"); + if err != nil { + goroot = "/home/r/go-build/go"; } - return s; + flag.StringVar(&goroot, "goroot", goroot, "Go root directory"); } -// Reduce sequences of multiple '/'s into a single '/' and -// strip any trailing '/' (may result in the empty string). -func sanitizePath(s string) string { - s = cleanPath(s); - if len(s) > 0 && s[len(s)-1] == '/' { // strip trailing '/' - s = s[0 : len(s)-1]; - } - return s; -} - +// ---------------------------------------------------------------------------- +// Support func hasPrefix(s, prefix string) bool { return len(prefix) <= len(s) && s[0 : len(prefix)] == prefix; } -func hasSuffix(s, postfix string) bool { - pos := len(s) - len(postfix); - return pos >= 0 && s[pos : len(s)] == postfix; +func hasSuffix(s, suffix string) bool { + pos := len(s) - len(suffix); + return pos >= 0 && s[pos : len(s)] == suffix; } @@ -115,8 +115,20 @@ func isHTMLFile(dir *os.Dir) bool { } -func printLink(c *http.Conn, path, name string) { - fmt.Fprintf(c, "%s
\n", filePrefix + path + name, name); +func isDir(name string) bool { + d, err := os.Stat(name); + return err == nil && d.IsDirectory(); +} + + +func isFile(name string) bool { + d, err := os.Stat(name); + return err == nil && d.IsRegular(); +} + + +func printLink(c *http.Conn, dir, name string) { + fmt.Fprintf(c, "%s
\n", pathutil.Clean(filePrefix + dir + "/" + name), name); } @@ -130,7 +142,7 @@ func makeTabwriter(writer io.Write) *tabwriter.Writer { // ---------------------------------------------------------------------------- -// Compilation +// Parsing type parseError struct { pos token.Position; @@ -151,7 +163,7 @@ type errorHandler struct { func (h *errorHandler) Error(pos token.Position, msg string) { - // only collect errors that are on a new line + // only collect errors that are on a new line // in the hope to avoid most follow-up errors if pos.Line != h.lastLine { h.lastLine = pos.Line; @@ -164,14 +176,14 @@ func (h *errorHandler) Error(pos token.Position, msg string) { } -// Compiles a file (path) and returns the corresponding AST and +// Parses a file (path) and returns the corresponding AST and // a sorted list (by file position) of errors, if any. // -func compile(path string, mode uint) (*ast.Program, errorList) { +func parse(path string, mode uint) (*ast.Program, errorList) { src, err := os.Open(path, os.O_RDONLY, 0); defer src.Close(); if err != nil { - log.Stdoutf("%s: %v", path, err); + log.Stdoutf("open %s: %v", path, err); var noPos token.Position; return nil, errorList{parseError{noPos, err.String()}}; } @@ -201,7 +213,7 @@ var godoc_html = template.NewTemplateOrDie("godoc.html"); func servePage(c *http.Conn, title string, contents func()) { c.SetHeader("content-type", "text/html; charset=utf-8"); - + // TODO handle Apply errors godoc_html.Apply(c, "" : func() { fmt.Fprint(c, title); }, @@ -229,7 +241,7 @@ func (p dirArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } func serveDir(c *http.Conn, dirname string) { - fd, err1 := os.Open(*root + dirname, os.O_RDONLY, 0); + fd, err1 := os.Open(dirname, os.O_RDONLY, 0); if err1 != nil { c.WriteHeader(http.StatusNotFound); fmt.Fprintf(c, "Error: %v (%s)\n", err1, dirname); @@ -276,9 +288,9 @@ func serveDir(c *http.Conn, dirname string) { // ---------------------------------------------------------------------------- // Files -func serveCompilationErrors(c *http.Conn, filename string, errors errorList) { +func serveParseErrors(c *http.Conn, filename string, errors errorList) { // open file - path := *root + filename; + path := filename; fd, err1 := os.Open(path, os.O_RDONLY, 0); defer fd.Close(); if err1 != nil { @@ -298,14 +310,14 @@ func serveCompilationErrors(c *http.Conn, filename string, errors errorList) { // TODO handle Apply errors servePage(c, filename, func () { // section title - fmt.Fprintf(c, "

Compilation errors in %s

\n", filename); - + fmt.Fprintf(c, "

Parse errors in %s

\n", filename); + // handle read errors - if err1 != nil || err2 != nil /* 6g bug139 */ { + if err1 != nil || err2 != nil { fmt.Fprintf(c, "could not read file %s\n", filename); return; } - + // write source with error messages interspersed fmt.Fprintln(c, "
");
 		offs := 0;
@@ -329,9 +341,9 @@ func serveCompilationErrors(c *http.Conn, filename string, errors errorList) {
 
 func serveGoSource(c *http.Conn, dirname string, filename string) {
 	path := dirname + "/" + filename;
-	prog, errors := compile(*root + "/" + path, parser.ParseComments);
+	prog, errors := parse(path, parser.ParseComments);
 	if len(errors) > 0 {
-		serveCompilationErrors(c, filename, errors);
+		serveParseErrors(c, filename, errors);
 		return;
 	}
 
@@ -354,8 +366,7 @@ func serveHTMLFile(c *http.Conn, filename string) {
 		serveError(c, err1.String(), filename);
 		return
 	}
-	written, err2 := io.Copy(src, c);
-	if err2 != nil {
+	if written, err2 := io.Copy(src, c); err2 != nil {
 		serveError(c, err2.String(), filename);
 		return
 	}
@@ -363,7 +374,7 @@ func serveHTMLFile(c *http.Conn, filename string) {
 
 
 func serveFile(c *http.Conn, path string) {
-	dir, err := os.Stat(*root + path);
+	dir, err := os.Stat(path);
 	if err != nil {
 		serveError(c, err.String(), path);
 		return;
@@ -373,9 +384,9 @@ func serveFile(c *http.Conn, path string) {
 	case dir.IsDirectory():
 		serveDir(c, path);
 	case isGoFile(dir):
-		serveGoSource(c, "", path);
+		serveGoSource(c, ".", path);
 	case isHTMLFile(dir):
-		serveHTMLFile(c, *root + path);
+		serveHTMLFile(c, path);
 	default:
 		serveError(c, "Not a directory or .go file", path);
 	}
@@ -386,8 +397,8 @@ func serveFile(c *http.Conn, path string) {
 // Packages
 
 type pakDesc struct {
-	dirname string;  // local to *root
-	pakname string;  // local to directory
+	dirname string;  // relative to goroot
+	pakname string;  // relative to directory
 	filenames map[string] bool;  // set of file (names) belonging to this package
 }
 
@@ -398,19 +409,14 @@ func (p pakArray) Less(i, j int) bool  { return p[i].pakname < p[j].pakname; }
 func (p pakArray) Swap(i, j int)       { p[i], p[j] = p[j], p[i]; }
 
 
-// The global list of packages (sorted)
-// TODO should be accessed under a lock
-var pakList pakArray;
-
-
 func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
 	if hasSuffix(filename, "_test.go") {
 		// ignore package tests
 		return;
 	}
 	// determine package name
-	path := *root + "/" + dirname + "/" + filename;
-	prog, errors := compile(path, parser.PackageClauseOnly);
+	path := dirname + "/" + filename;
+	prog, errors := parse(path, parser.PackageClauseOnly);
 	if prog == nil {
 		return;
 	}
@@ -418,7 +424,7 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
 		// ignore main packages for now
 		return;
 	}
-	pakname := dirname + "/" + prog.Name.Value;
+	pakname := pathutil.Clean(dirname + "/" + prog.Name.Value);
 
 	// find package descriptor
 	pakdesc, found := pmap[pakname];
@@ -427,7 +433,7 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
 		pakdesc = &pakDesc{dirname, prog.Name.Value, make(map[string]bool)};
 		pmap[pakname] = pakdesc;
 	}
-	
+
 	//fmt.Printf("pak = %s, file = %s\n", pakname, filename);
 
 	// add file to package desc
@@ -439,27 +445,22 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
 
 
 func addDirectory(pmap map[string]*pakDesc, dirname string) {
-	// TODO should properly check device and inode to see if we have
-	//      traversed this directory already
-	fd, err1 := os.Open(*root + dirname, os.O_RDONLY, 0);
+	path := dirname;
+	fd, err1 := os.Open(path, os.O_RDONLY, 0);
 	if err1 != nil {
-		log.Stdoutf("%s: %v", *root + dirname, err1);
+		log.Stdoutf("open %s: %v", path, err1);
 		return;
 	}
 
 	list, err2 := fd.Readdir(-1);
 	if err2 != nil {
-		log.Stdoutf("%s: %v", *root + dirname, err2);
+		log.Stdoutf("readdir %s: %v", path, err2);
 		return;
 	}
 
 	for i, entry := range list {
 		switch {
-		case entry.IsDirectory():
-			if entry.Name != "." && entry.Name != ".." {
-				addDirectory(pmap, dirname + "/" + entry.Name);
-			}
-		case isGoFile(&entry):	
+		case isGoFile(&entry):
 			//fmt.Printf("found %s/%s\n", dirname, entry.Name);
 			addFile(pmap, dirname, entry.Name);
 		}
@@ -467,12 +468,7 @@ func addDirectory(pmap map[string]*pakDesc, dirname string) {
 }
 
 
-func makePackageMap() {
-	// TODO shold do this under a lock
-	// populate package map
-	pmap := make(map[string]*pakDesc);
-	addDirectory(pmap, "");
-	
+func mapValues(pmap map[string]*pakDesc) pakArray {
 	// build sorted package list
 	plist := make(pakArray, len(pmap));
 	i := 0;
@@ -481,13 +477,7 @@ func makePackageMap() {
 		i++;
 	}
 	sort.Sort(plist);
-
-	// install package list (TODO should do this under a lock)
-	pakList = plist;
-
-	if *verbose {
-		log.Stdoutf("%d packages found under %s", i, *root);
-	}
+	return plist;
 }
 
 
@@ -499,14 +489,14 @@ func servePackage(c *http.Conn, p *pakDesc) {
 		filenames[i] = filename;
 		i++;
 	}
-	
+
 	// compute documentation
 	var doc docPrinter.PackageDoc;
 	for i, filename := range filenames {
-		path := *root + "/" + p.dirname + "/" + filename;
-		prog, errors := compile(path, parser.ParseComments);
+		path := p.dirname + "/" + filename;
+		prog, errors := parse(path, parser.ParseComments);
 		if len(errors) > 0 {
-			serveCompilationErrors(c, filename, errors);
+			serveParseErrors(c, filename, errors);
 			return;
 		}
 
@@ -525,40 +515,73 @@ func servePackage(c *http.Conn, p *pakDesc) {
 }
 
 
-func servePackageList(c *http.Conn, list *vector.Vector) {
+func servePackageList(c *http.Conn, list pakArray) {
 	servePage(c, "Packages", func () {
-		for i := 0; i < list.Len(); i++ {
-			p := list.At(i).(*pakDesc);
-			link := p.dirname + "/" + p.pakname;
-			fmt.Fprintf(c, "%s (%s)
\n", docPrefix + link, p.pakname, link); + for i := 0; i < len(list); i++ { + p := list[i]; + link := pathutil.Clean(p.dirname + "/" + p.pakname); + fmt.Fprintf(c, "%s (%s)
\n", + p.pakname, p.pakname, link); } }); + + // TODO: show subdirectories } -func serveDoc(c *http.Conn, path string) { - // make regexp for package matching - rex, err := regexp.Compile(path); - if err != nil { - serveError(c, err.String(), path); - return; +// Return package or packages named by name. +// Name is either an import string or a directory, +// like you'd see in $GOROOT/pkg/ once the 6g +// tools can handle a hierarchy there. +// +// Examples: +// "math" - single package made up of directory +// "container" - directory listing +// "container/vector" - single package in container directory +func findPackages(name string) (*pakDesc, pakArray) { + // Build list of packages. + // If the path names a directory, scan that directory + // for a package with the name matching the directory name. + // Otherwise assume it is a package name inside + // a directory, so scan the parent. + pmap := make(map[string]*pakDesc); + dir := pathutil.Clean("src/lib/" + name); + if isDir(dir) { + parent, pak := pathutil.Split(dir); + addDirectory(pmap, dir); + paks := mapValues(pmap); + if len(paks) == 1 { + p := paks[0]; + if p.dirname == dir && p.pakname == pak { + return p, nil; + } + } + return nil, paks; } - // build list of matching packages - list := vector.New(0); - for i, p := range pakList { - if rex.Match(p.dirname + "/" + p.pakname) { - list.Push(p); - } + // Otherwise, have parentdir/pak. Look for package pak in dir. + parentdir, pak := pathutil.Split(dir); + addDirectory(pmap, parentdir); + if p, ok := pmap[dir]; ok { + return p, nil; } - switch list.Len() { - case 0: - serveError(c, "No packages found", path); - case 1: - servePackage(c, list.At(0).(*pakDesc)); + return nil, nil; +} + + +func servePkg(c *http.Conn, path string) { + pak, paks := findPackages(path); + + // TODO: canonicalize path and redirect if needed. + + switch { + case pak != nil: + servePackage(c, pak); + case len(paks) > 0: + servePackageList(c, paks); default: - servePackageList(c, list); + serveError(c, "No packages found", path); } } @@ -568,9 +591,7 @@ func serveDoc(c *http.Conn, path string) { func makeFixedFileServer(filename string) (func(c *http.Conn, path string)) { return func(c *http.Conn, path string) { - // ignore path and always serve the same file - // TODO this should be serveFile but there are some issues with *root - serveHTMLFile(c, filename); + serveFile(c, filename); }; } @@ -582,13 +603,7 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) { if *verbose { log.Stdoutf("%s\t%s", req.Host, path); } - if hasPrefix(path, prefix) { - path = sanitizePath(path[len(prefix) : len(path)]); - //log.Stdoutf("sanitized path %s", path); - handler(c, path); - } else { - log.Stdoutf("illegal path %s", path); - } + handler(c, path[len(prefix) : len(path)]); }; // install the customized handler @@ -596,32 +611,48 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) { } +func usage() { + fmt.Fprintf(os.Stderr, usageString); + sys.Exit(1); +} + + func main() { flag.Parse(); - *root = sanitizePath(*root); - { dir, err := os.Stat(*root); - if err != nil || !dir.IsDirectory() { - log.Exitf("root not found or not a directory: %s", *root); + // Check usage first; get usage message out early. + switch { + case *httpaddr != "": + if flag.NArg() != 0 { + usage(); + } + default: + if flag.NArg() == 0 { + usage(); } } - if *verbose { - log.Stdoutf("Go Documentation Server\n"); - log.Stdoutf("port = %s\n", *port); - log.Stdoutf("root = %s\n", *root); + if err := os.Chdir(goroot); err != nil { + log.Exitf("chdir %s: %v", goroot, err); } - makePackageMap(); - - installHandler("/mem", makeFixedFileServer(GOROOT + "/doc/go_mem.html")); - installHandler("/spec", makeFixedFileServer(GOROOT + "/doc/go_spec.html")); - installHandler(docPrefix, serveDoc); - installHandler(filePrefix, serveFile); + if *httpaddr != "" { + if *verbose { + log.Stdoutf("Go Documentation Server\n"); + log.Stdoutf("address = %s\n", *httpaddr); + log.Stdoutf("goroot = %s\n", goroot); + } + + installHandler("/mem", makeFixedFileServer("doc/go_mem.html")); + installHandler("/spec", makeFixedFileServer("doc/go_spec.html")); + installHandler("/pkg/", servePkg); + installHandler(filePrefix, serveFile); - { err := http.ListenAndServe(":" + *port, nil); - if err != nil { - log.Exitf("ListenAndServe: %v", err) + if err := http.ListenAndServe(*httpaddr, nil); err != nil { + log.Exitf("ListenAndServe %s: %v", *httpaddr, err) } + return; } + + log.Exitf("godoc command-line not implemented"); } -- 2.48.1