Commit e97121a1 authored by Russ Cox's avatar Russ Cox

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
parent 73aadff8
...@@ -4,6 +4,26 @@ ...@@ -4,6 +4,26 @@
// godoc: Go Documentation Server // 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 package main
import ( import (
...@@ -17,16 +37,15 @@ import ( ...@@ -17,16 +37,15 @@ import (
"net"; "net";
"os"; "os";
"parser"; "parser";
pathutil "path";
"sort"; "sort";
"tabwriter"; "tabwriter";
"template"; "template";
"time"; "time";
"token"; "token";
"regexp";
"vector"; "vector";
"astprinter"; "astprinter";
"compilation"; // TODO removing this causes link errors - why?
"docprinter"; "docprinter";
) )
...@@ -34,7 +53,12 @@ import ( ...@@ -34,7 +53,12 @@ import (
// TODO // TODO
// - uniform use of path, filename, dirname, pakname, etc. // - uniform use of path, filename, dirname, pakname, etc.
// - fix weirdness with double-/'s in paths // - 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 ( const (
...@@ -43,65 +67,41 @@ const ( ...@@ -43,65 +67,41 @@ const (
) )
func getenv(varname string) string {
value, err := os.Getenv(varname);
return value;
}
var ( var (
GOROOT = getenv("GOROOT"); goroot string;
// server control
verbose = flag.Bool("v", false, "verbose mode"); 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 // layout control
tabwidth = flag.Int("tabwidth", 4, "tab width"); 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");
) )
// ---------------------------------------------------------------------------- func init() {
// Support var err *os.Error;
goroot, err = os.Getenv("GOROOT");
func cleanPath(s string) string { if err != nil {
for i := 0; i < len(s); i++ { goroot = "/home/r/go-build/go";
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)]);
}
}
} }
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). // Support
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;
}
func hasPrefix(s, prefix string) bool { func hasPrefix(s, prefix string) bool {
return len(prefix) <= len(s) && s[0 : len(prefix)] == prefix; return len(prefix) <= len(s) && s[0 : len(prefix)] == prefix;
} }
func hasSuffix(s, postfix string) bool { func hasSuffix(s, suffix string) bool {
pos := len(s) - len(postfix); pos := len(s) - len(suffix);
return pos >= 0 && s[pos : len(s)] == postfix; return pos >= 0 && s[pos : len(s)] == suffix;
} }
...@@ -115,8 +115,20 @@ func isHTMLFile(dir *os.Dir) bool { ...@@ -115,8 +115,20 @@ func isHTMLFile(dir *os.Dir) bool {
} }
func printLink(c *http.Conn, path, name string) { func isDir(name string) bool {
fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", filePrefix + path + name, name); 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, "<a href=\"%s\">%s</a><br />\n", pathutil.Clean(filePrefix + dir + "/" + name), name);
} }
...@@ -130,7 +142,7 @@ func makeTabwriter(writer io.Write) *tabwriter.Writer { ...@@ -130,7 +142,7 @@ func makeTabwriter(writer io.Write) *tabwriter.Writer {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Compilation // Parsing
type parseError struct { type parseError struct {
pos token.Position; pos token.Position;
...@@ -164,14 +176,14 @@ func (h *errorHandler) Error(pos token.Position, msg string) { ...@@ -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. // 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); src, err := os.Open(path, os.O_RDONLY, 0);
defer src.Close(); defer src.Close();
if err != nil { if err != nil {
log.Stdoutf("%s: %v", path, err); log.Stdoutf("open %s: %v", path, err);
var noPos token.Position; var noPos token.Position;
return nil, errorList{parseError{noPos, err.String()}}; return nil, errorList{parseError{noPos, err.String()}};
} }
...@@ -229,7 +241,7 @@ func (p dirArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } ...@@ -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) { 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 { if err1 != nil {
c.WriteHeader(http.StatusNotFound); c.WriteHeader(http.StatusNotFound);
fmt.Fprintf(c, "Error: %v (%s)\n", err1, dirname); fmt.Fprintf(c, "Error: %v (%s)\n", err1, dirname);
...@@ -276,9 +288,9 @@ func serveDir(c *http.Conn, dirname string) { ...@@ -276,9 +288,9 @@ func serveDir(c *http.Conn, dirname string) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Files // Files
func serveCompilationErrors(c *http.Conn, filename string, errors errorList) { func serveParseErrors(c *http.Conn, filename string, errors errorList) {
// open file // open file
path := *root + filename; path := filename;
fd, err1 := os.Open(path, os.O_RDONLY, 0); fd, err1 := os.Open(path, os.O_RDONLY, 0);
defer fd.Close(); defer fd.Close();
if err1 != nil { if err1 != nil {
...@@ -298,10 +310,10 @@ func serveCompilationErrors(c *http.Conn, filename string, errors errorList) { ...@@ -298,10 +310,10 @@ func serveCompilationErrors(c *http.Conn, filename string, errors errorList) {
// TODO handle Apply errors // TODO handle Apply errors
servePage(c, filename, func () { servePage(c, filename, func () {
// section title // section title
fmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename); fmt.Fprintf(c, "<h1>Parse errors in %s</h1>\n", filename);
// handle read errors // 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); fmt.Fprintf(c, "could not read file %s\n", filename);
return; return;
} }
...@@ -329,9 +341,9 @@ func serveCompilationErrors(c *http.Conn, filename string, errors errorList) { ...@@ -329,9 +341,9 @@ func serveCompilationErrors(c *http.Conn, filename string, errors errorList) {
func serveGoSource(c *http.Conn, dirname string, filename string) { func serveGoSource(c *http.Conn, dirname string, filename string) {
path := dirname + "/" + filename; path := dirname + "/" + filename;
prog, errors := compile(*root + "/" + path, parser.ParseComments); prog, errors := parse(path, parser.ParseComments);
if len(errors) > 0 { if len(errors) > 0 {
serveCompilationErrors(c, filename, errors); serveParseErrors(c, filename, errors);
return; return;
} }
...@@ -354,8 +366,7 @@ func serveHTMLFile(c *http.Conn, filename string) { ...@@ -354,8 +366,7 @@ func serveHTMLFile(c *http.Conn, filename string) {
serveError(c, err1.String(), filename); serveError(c, err1.String(), filename);
return return
} }
written, err2 := io.Copy(src, c); if written, err2 := io.Copy(src, c); err2 != nil {
if err2 != nil {
serveError(c, err2.String(), filename); serveError(c, err2.String(), filename);
return return
} }
...@@ -363,7 +374,7 @@ func serveHTMLFile(c *http.Conn, filename string) { ...@@ -363,7 +374,7 @@ func serveHTMLFile(c *http.Conn, filename string) {
func serveFile(c *http.Conn, path string) { func serveFile(c *http.Conn, path string) {
dir, err := os.Stat(*root + path); dir, err := os.Stat(path);
if err != nil { if err != nil {
serveError(c, err.String(), path); serveError(c, err.String(), path);
return; return;
...@@ -373,9 +384,9 @@ func serveFile(c *http.Conn, path string) { ...@@ -373,9 +384,9 @@ func serveFile(c *http.Conn, path string) {
case dir.IsDirectory(): case dir.IsDirectory():
serveDir(c, path); serveDir(c, path);
case isGoFile(dir): case isGoFile(dir):
serveGoSource(c, "", path); serveGoSource(c, ".", path);
case isHTMLFile(dir): case isHTMLFile(dir):
serveHTMLFile(c, *root + path); serveHTMLFile(c, path);
default: default:
serveError(c, "Not a directory or .go file", path); serveError(c, "Not a directory or .go file", path);
} }
...@@ -386,8 +397,8 @@ func serveFile(c *http.Conn, path string) { ...@@ -386,8 +397,8 @@ func serveFile(c *http.Conn, path string) {
// Packages // Packages
type pakDesc struct { type pakDesc struct {
dirname string; // local to *root dirname string; // relative to goroot
pakname string; // local to directory pakname string; // relative to directory
filenames map[string] bool; // set of file (names) belonging to this package 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; } ...@@ -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]; } 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) { func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
if hasSuffix(filename, "_test.go") { if hasSuffix(filename, "_test.go") {
// ignore package tests // ignore package tests
return; return;
} }
// determine package name // determine package name
path := *root + "/" + dirname + "/" + filename; path := dirname + "/" + filename;
prog, errors := compile(path, parser.PackageClauseOnly); prog, errors := parse(path, parser.PackageClauseOnly);
if prog == nil { if prog == nil {
return; return;
} }
...@@ -418,7 +424,7 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) { ...@@ -418,7 +424,7 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
// ignore main packages for now // ignore main packages for now
return; return;
} }
pakname := dirname + "/" + prog.Name.Value; pakname := pathutil.Clean(dirname + "/" + prog.Name.Value);
// find package descriptor // find package descriptor
pakdesc, found := pmap[pakname]; pakdesc, found := pmap[pakname];
...@@ -439,26 +445,21 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) { ...@@ -439,26 +445,21 @@ func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
func addDirectory(pmap map[string]*pakDesc, dirname string) { func addDirectory(pmap map[string]*pakDesc, dirname string) {
// TODO should properly check device and inode to see if we have path := dirname;
// traversed this directory already fd, err1 := os.Open(path, os.O_RDONLY, 0);
fd, err1 := os.Open(*root + dirname, os.O_RDONLY, 0);
if err1 != nil { if err1 != nil {
log.Stdoutf("%s: %v", *root + dirname, err1); log.Stdoutf("open %s: %v", path, err1);
return; return;
} }
list, err2 := fd.Readdir(-1); list, err2 := fd.Readdir(-1);
if err2 != nil { if err2 != nil {
log.Stdoutf("%s: %v", *root + dirname, err2); log.Stdoutf("readdir %s: %v", path, err2);
return; return;
} }
for i, entry := range list { for i, entry := range list {
switch { 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); //fmt.Printf("found %s/%s\n", dirname, entry.Name);
addFile(pmap, dirname, entry.Name); addFile(pmap, dirname, entry.Name);
...@@ -467,12 +468,7 @@ func addDirectory(pmap map[string]*pakDesc, dirname string) { ...@@ -467,12 +468,7 @@ func addDirectory(pmap map[string]*pakDesc, dirname string) {
} }
func makePackageMap() { func mapValues(pmap map[string]*pakDesc) pakArray {
// TODO shold do this under a lock
// populate package map
pmap := make(map[string]*pakDesc);
addDirectory(pmap, "");
// build sorted package list // build sorted package list
plist := make(pakArray, len(pmap)); plist := make(pakArray, len(pmap));
i := 0; i := 0;
...@@ -481,13 +477,7 @@ func makePackageMap() { ...@@ -481,13 +477,7 @@ func makePackageMap() {
i++; i++;
} }
sort.Sort(plist); sort.Sort(plist);
return plist;
// install package list (TODO should do this under a lock)
pakList = plist;
if *verbose {
log.Stdoutf("%d packages found under %s", i, *root);
}
} }
...@@ -503,10 +493,10 @@ func servePackage(c *http.Conn, p *pakDesc) { ...@@ -503,10 +493,10 @@ func servePackage(c *http.Conn, p *pakDesc) {
// compute documentation // compute documentation
var doc docPrinter.PackageDoc; var doc docPrinter.PackageDoc;
for i, filename := range filenames { for i, filename := range filenames {
path := *root + "/" + p.dirname + "/" + filename; path := p.dirname + "/" + filename;
prog, errors := compile(path, parser.ParseComments); prog, errors := parse(path, parser.ParseComments);
if len(errors) > 0 { if len(errors) > 0 {
serveCompilationErrors(c, filename, errors); serveParseErrors(c, filename, errors);
return; return;
} }
...@@ -525,40 +515,73 @@ func servePackage(c *http.Conn, p *pakDesc) { ...@@ -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 () { servePage(c, "Packages", func () {
for i := 0; i < list.Len(); i++ { for i := 0; i < len(list); i++ {
p := list.At(i).(*pakDesc); p := list[i];
link := p.dirname + "/" + p.pakname; link := pathutil.Clean(p.dirname + "/" + p.pakname);
fmt.Fprintf(c, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n", docPrefix + link, p.pakname, link); fmt.Fprintf(c, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n",
p.pakname, p.pakname, link);
} }
}); });
// TODO: show subdirectories
} }
func serveDoc(c *http.Conn, path string) { // Return package or packages named by name.
// make regexp for package matching // Name is either an import string or a directory,
rex, err := regexp.Compile(path); // like you'd see in $GOROOT/pkg/ once the 6g
if err != nil { // tools can handle a hierarchy there.
serveError(c, err.String(), path); //
return; // 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;
} }
// build list of matching packages
list := vector.New(0);
for i, p := range pakList {
if rex.Match(p.dirname + "/" + p.pakname) {
list.Push(p);
} }
return nil, paks;
} }
switch list.Len() { // Otherwise, have parentdir/pak. Look for package pak in dir.
case 0: parentdir, pak := pathutil.Split(dir);
serveError(c, "No packages found", path); addDirectory(pmap, parentdir);
case 1: if p, ok := pmap[dir]; ok {
servePackage(c, list.At(0).(*pakDesc)); return p, nil;
}
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: default:
servePackageList(c, list); serveError(c, "No packages found", path);
} }
} }
...@@ -568,9 +591,7 @@ func serveDoc(c *http.Conn, path string) { ...@@ -568,9 +591,7 @@ func serveDoc(c *http.Conn, path string) {
func makeFixedFileServer(filename string) (func(c *http.Conn, path string)) { func makeFixedFileServer(filename string) (func(c *http.Conn, path string)) {
return func(c *http.Conn, path string) { return func(c *http.Conn, path string) {
// ignore path and always serve the same file serveFile(c, filename);
// TODO this should be serveFile but there are some issues with *root
serveHTMLFile(c, filename);
}; };
} }
...@@ -582,13 +603,7 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) { ...@@ -582,13 +603,7 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) {
if *verbose { if *verbose {
log.Stdoutf("%s\t%s", req.Host, path); log.Stdoutf("%s\t%s", req.Host, path);
} }
if hasPrefix(path, prefix) { handler(c, path[len(prefix) : len(path)]);
path = sanitizePath(path[len(prefix) : len(path)]);
//log.Stdoutf("sanitized path %s", path);
handler(c, path);
} else {
log.Stdoutf("illegal path %s", path);
}
}; };
// install the customized handler // install the customized handler
...@@ -596,32 +611,48 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) { ...@@ -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() { func main() {
flag.Parse(); flag.Parse();
*root = sanitizePath(*root); // Check usage first; get usage message out early.
{ dir, err := os.Stat(*root); switch {
if err != nil || !dir.IsDirectory() { case *httpaddr != "":
log.Exitf("root not found or not a directory: %s", *root); 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);
}
if *httpaddr != "" {
if *verbose { if *verbose {
log.Stdoutf("Go Documentation Server\n"); log.Stdoutf("Go Documentation Server\n");
log.Stdoutf("port = %s\n", *port); log.Stdoutf("address = %s\n", *httpaddr);
log.Stdoutf("root = %s\n", *root); log.Stdoutf("goroot = %s\n", goroot);
} }
makePackageMap(); installHandler("/mem", makeFixedFileServer("doc/go_mem.html"));
installHandler("/spec", makeFixedFileServer("doc/go_spec.html"));
installHandler("/mem", makeFixedFileServer(GOROOT + "/doc/go_mem.html")); installHandler("/pkg/", servePkg);
installHandler("/spec", makeFixedFileServer(GOROOT + "/doc/go_spec.html"));
installHandler(docPrefix, serveDoc);
installHandler(filePrefix, serveFile); installHandler(filePrefix, serveFile);
{ err := http.ListenAndServe(":" + *port, nil); if err := http.ListenAndServe(*httpaddr, nil); err != nil {
if err != nil { log.Exitf("ListenAndServe %s: %v", *httpaddr, err)
log.Exitf("ListenAndServe: %v", err)
} }
return;
} }
log.Exitf("godoc command-line not implemented");
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment