weekly snapshot

- godoc updated to work w/new directory structure

TBR=r
OCL=28624
CL=28624
This commit is contained in:
Robert Griesemer 2009-05-11 09:47:53 -07:00
parent 9a861b6aaa
commit 5a11a46e2d
3 changed files with 120 additions and 207 deletions

View File

@ -0,0 +1,3 @@
{.repeated section Dirs}
<a href="{Path|html}/{Name|html}">{Name|html}</a><br />
{.end}

View File

@ -0,0 +1,4 @@
Directories:
{.repeated section @}
{Name}
{.end}

View File

@ -86,7 +86,7 @@ func init() {
// Support // Support
func isGoFile(dir *os.Dir) bool { func isGoFile(dir *os.Dir) bool {
return dir.IsRegular() && strings.HasSuffix(dir.Name, ".go"); return dir.IsRegular() && pathutil.Ext(dir.Name) == ".go";
} }
@ -112,11 +112,11 @@ func ReadFile(name string) ([]byte, os.Error) {
return nil, err; return nil, err;
} }
defer f.Close(); defer f.Close();
var b io.ByteBuffer; var buf io.ByteBuffer;
if n, err := io.Copy(f, &b); err != nil { if n, err := io.Copy(f, &buf); err != nil {
return nil, err; return nil, err;
} }
return b.Data(), nil; return buf.Data(), nil;
} }
@ -175,12 +175,12 @@ type parseErrors struct {
// Parses 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 parse(filename string, mode uint) (*ast.Program, *parseErrors) { func parse(path string, mode uint) (*ast.Program, *parseErrors) {
src, err := ReadFile(filename); src, err := ReadFile(path);
if err != nil { if err != nil {
log.Stderrf("ReadFile %s: %v", filename, err); log.Stderrf("ReadFile %s: %v", path, err);
errs := []parseError{parseError{nil, 0, err.String()}}; errs := []parseError{parseError{nil, 0, err.String()}};
return nil, &parseErrors{filename, errs, nil}; return nil, &parseErrors{path, errs, nil};
} }
var raw rawErrorVector; var raw rawErrorVector;
@ -201,7 +201,7 @@ func parse(filename string, mode uint) (*ast.Program, *parseErrors) {
errs[i].msg = r.msg; errs[i].msg = r.msg;
} }
errs[raw.Len()].src = src[offs : len(src)]; errs[raw.Len()].src = src[offs : len(src)];
return nil, &parseErrors{filename, errs, src}; return nil, &parseErrors{path, errs, src};
} }
return prog, nil; return prog, nil;
@ -213,21 +213,21 @@ func parse(filename string, mode uint) (*ast.Program, *parseErrors) {
// Return text for decl. // Return text for decl.
func DeclText(d ast.Decl) []byte { func DeclText(d ast.Decl) []byte {
var b io.ByteBuffer; var buf io.ByteBuffer;
var p astPrinter.Printer; var p astPrinter.Printer;
p.Init(&b, nil, nil, false); p.Init(&buf, nil, nil, false);
d.Visit(&p); d.Visit(&p);
return b.Data(); return buf.Data();
} }
// Return text for expr. // Return text for expr.
func ExprText(d ast.Expr) []byte { func ExprText(d ast.Expr) []byte {
var b io.ByteBuffer; var buf io.ByteBuffer;
var p astPrinter.Printer; var p astPrinter.Printer;
p.Init(&b, nil, nil, false); p.Init(&buf, nil, nil, false);
d.Visit(&p); d.Visit(&p);
return b.Data(); return buf.Data();
} }
@ -247,9 +247,9 @@ func toText(x interface{}) []byte {
case ast.Expr: case ast.Expr:
return ExprText(v); return ExprText(v);
} }
var b io.ByteBuffer; var buf io.ByteBuffer;
fmt.Fprint(&b, x); fmt.Fprint(&buf, x);
return b.Data(); return buf.Data();
} }
@ -287,23 +287,10 @@ func textFmt(w io.Writer, x interface{}, format string) {
} }
// Template formatter for "dir/" format.
// Writes out "/" if the os.Dir argument is a directory.
var slash = io.StringBytes("/");
func dirSlashFmt(w io.Writer, x interface{}, format string) {
d := x.(os.Dir); // TODO(rsc): want *os.Dir
if d.IsDirectory() {
w.Write(slash);
}
}
var fmap = template.FormatterMap{ var fmap = template.FormatterMap{
"": textFmt, "": textFmt,
"html": htmlFmt, "html": htmlFmt,
"html-comment": htmlCommentFmt, "html-comment": htmlCommentFmt,
"dir/": dirSlashFmt,
} }
@ -324,8 +311,8 @@ func readTemplate(name string) *template.Template {
var godocHtml *template.Template var godocHtml *template.Template
var packageHtml *template.Template var packageHtml *template.Template
var packageText *template.Template var packageText *template.Template
var packagelistHtml *template.Template; var dirlistHtml *template.Template;
var packagelistText *template.Template; var dirlistText *template.Template;
var parseerrorHtml *template.Template; var parseerrorHtml *template.Template;
var parseerrorText *template.Template; var parseerrorText *template.Template;
@ -335,8 +322,8 @@ func readTemplates() {
godocHtml = readTemplate("godoc.html"); godocHtml = readTemplate("godoc.html");
packageHtml = readTemplate("package.html"); packageHtml = readTemplate("package.html");
packageText = readTemplate("package.txt"); packageText = readTemplate("package.txt");
packagelistHtml = readTemplate("packagelist.html"); dirlistHtml = readTemplate("dirlist.html");
packagelistText = readTemplate("packagelist.txt"); dirlistText = readTemplate("dirlist.txt");
parseerrorHtml = readTemplate("parseerror.html"); parseerrorHtml = readTemplate("parseerror.html");
parseerrorText = readTemplate("parseerror.txt"); parseerrorText = readTemplate("parseerror.txt");
} }
@ -368,19 +355,14 @@ func serveText(c *http.Conn, text []byte) {
} }
func serveError(c *http.Conn, err, arg string) {
servePage(c, "Error", fmt.Sprintf("%v (%s)\n", err, arg));
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Files // Files
func serveParseErrors(c *http.Conn, errors *parseErrors) { func serveParseErrors(c *http.Conn, errors *parseErrors) {
// format errors // format errors
var b io.ByteBuffer; var buf io.ByteBuffer;
parseerrorHtml.Execute(errors, &b); parseerrorHtml.Execute(errors, &buf);
servePage(c, errors.filename + " - Parse Errors", b.Data()); servePage(c, errors.filename + " - Parse Errors", buf.Data());
} }
@ -391,16 +373,16 @@ func serveGoSource(c *http.Conn, name string) {
return; return;
} }
var b io.ByteBuffer; var buf io.ByteBuffer;
fmt.Fprintln(&b, "<pre>"); fmt.Fprintln(&buf, "<pre>");
var p astPrinter.Printer; var p astPrinter.Printer;
writer := makeTabwriter(&b); // for nicely formatted output writer := makeTabwriter(&buf); // for nicely formatted output
p.Init(writer, nil, nil, true); p.Init(writer, nil, nil, true);
p.DoProgram(prog); p.DoProgram(prog);
writer.Flush(); // ignore errors writer.Flush(); // ignore errors
fmt.Fprintln(&b, "</pre>"); fmt.Fprintln(&buf, "</pre>");
servePage(c, name + " - Go source", b.Data()); servePage(c, name + " - Go source", buf.Data());
} }
@ -436,86 +418,75 @@ func serveFile(c *http.Conn, req *http.Request) {
type pakDesc struct { type pakDesc struct {
dirname string; // relative to goroot dirname string; // relative to goroot
pakname string; // relative to directory pakname string; // same as last component of importpath
importpath string; // import "___" importpath string; // import "___"
filenames map[string] bool; // set of file (names) belonging to this package filenames map[string] bool; // set of file (names) belonging to this package
} }
type pakArray []*pakDesc func isPackageFile(dirname, filename, pakname string) bool {
func (p pakArray) Len() int { return len(p); } // ignore test files
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 addFile(pmap map[string]*pakDesc, dirname, filename, importprefix string) {
if strings.HasSuffix(filename, "_test.go") { if strings.HasSuffix(filename, "_test.go") {
// ignore package tests return false;
return;
} }
// determine package name // determine package name
path := pathutil.Join(dirname, filename); prog, errors := parse(dirname + "/" + filename, parser.PackageClauseOnly);
prog, errors := parse(path, parser.PackageClauseOnly);
if prog == nil { if prog == nil {
return; return false;
}
if prog.Name.Value == "main" {
// ignore main packages for now
return;
} }
var importpath string; return prog != nil && prog.Name.Value == pakname;
dir, name := pathutil.Split(importprefix);
if name == prog.Name.Value { // package math in directory "math"
importpath = importprefix;
} else {
importpath = pathutil.Clean(importprefix + "/" + prog.Name.Value);
}
// find package descriptor
pakdesc, found := pmap[importpath];
if !found {
// add a new descriptor
pakdesc = &pakDesc{dirname, prog.Name.Value, importpath, make(map[string]bool)};
pmap[importpath] = pakdesc;
}
//fmt.Printf("pak = %s, file = %s\n", pakname, filename);
// add file to package desc
if tmp, found := pakdesc.filenames[filename]; found {
panic("internal error: same file added more then once: " + filename);
}
pakdesc.filenames[filename] = true;
} }
func addDirectory(pmap map[string]*pakDesc, dirname, importprefix string, subdirs *[]os.Dir) { // Returns the package denoted by importpath and the list of
path := dirname; // sub-directories in the corresponding package directory.
fd, err1 := os.Open(path, os.O_RDONLY, 0); // If there is no such package, the first result is nil. If
// there are no sub-directories, that list is nil.
func findPackage(importpath string) (*pakDesc, []os.Dir) {
// get directory contents, if possible
dirname := pathutil.Join(*pkgroot, importpath);
if !isDir(dirname) {
return nil, nil;
}
fd, err1 := os.Open(dirname, os.O_RDONLY, 0);
if err1 != nil { if err1 != nil {
log.Stderrf("open %s: %v", path, err1); log.Stderrf("open %s: %v", dirname, err1);
return; return nil, nil;
} }
list, err2 := fd.Readdir(-1); list, err2 := fd.Readdir(-1);
if err2 != nil { if err2 != nil {
log.Stderrf("readdir %s: %v", path, err2); log.Stderrf("readdir %s: %v", dirname, err2);
return; return nil, nil;
} }
// the package name is is the directory name within its parent
_, pakname := pathutil.Split(importpath);
// collect all files belonging to the package and count the
// number of sub-directories
filenames := make(map[string]bool);
nsub := 0; nsub := 0;
for i, entry := range list { for i, entry := range list {
switch { switch {
case isGoFile(&entry): case isGoFile(&entry) && isPackageFile(dirname, entry.Name, pakname):
addFile(pmap, dirname, entry.Name, importprefix); // add file to package desc
if tmp, found := filenames[entry.Name]; found {
panic("internal error: same file added more then once: " + entry.Name);
}
filenames[entry.Name] = true;
case entry.IsDirectory(): case entry.IsDirectory():
nsub++; nsub++;
} }
} }
if subdirs != nil && nsub > 0 { // make the list of sub-directories, if any
*subdirs = make([]os.Dir, nsub); var subdirs []os.Dir;
if nsub > 0 {
subdirs = make([]os.Dir, nsub);
nsub = 0; nsub = 0;
for i, entry := range list { for i, entry := range list {
if entry.IsDirectory() { if entry.IsDirectory() {
@ -524,19 +495,13 @@ func addDirectory(pmap map[string]*pakDesc, dirname, importprefix string, subdir
} }
} }
} }
}
// if there are no package files, then there is no package
func mapValues(pmap map[string]*pakDesc) pakArray { if len(filenames) == 0 {
// build sorted package list return nil, subdirs;
plist := make(pakArray, len(pmap));
i := 0;
for tmp, pakdesc := range pmap {
plist[i] = pakdesc;
i++;
} }
sort.Sort(plist);
return plist; return &pakDesc{dirname, pakname, importpath, filenames}, subdirs;
} }
@ -545,12 +510,10 @@ func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) {
var r doc.DocReader; var r doc.DocReader;
i := 0; i := 0;
for filename := range p.filenames { for filename := range p.filenames {
path := p.dirname + "/" + filename; prog, err := parse(p.dirname + "/" + filename, parser.ParseComments);
prog, err := parse(path, parser.ParseComments);
if err != nil { if err != nil {
return nil, err; return nil, err;
} }
if i == 0 { if i == 0 {
// first file - initialize doc // first file - initialize doc
r.Init(prog.Name.Value, p.importpath); r.Init(prog.Name.Value, p.importpath);
@ -562,127 +525,70 @@ func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) {
} }
func servePackage(c *http.Conn, p *pakDesc) { func servePackage(c *http.Conn, desc *pakDesc) {
doc, errors := p.Doc(); doc, errors := desc.Doc();
if errors != nil { if errors != nil {
serveParseErrors(c, errors); serveParseErrors(c, errors);
return; return;
} }
var b io.ByteBuffer; var buf io.ByteBuffer;
if false { // TODO req.Params["format"] == "text" if false { // TODO req.Params["format"] == "text"
err := packageText.Execute(doc, &b); err := packageText.Execute(doc, &buf);
if err != nil { if err != nil {
log.Stderrf("packageText.Execute: %s", err); log.Stderrf("packageText.Execute: %s", err);
} }
serveText(c, b.Data()); serveText(c, buf.Data());
return; return;
} }
err := packageHtml.Execute(doc, &b); err := packageHtml.Execute(doc, &buf);
if err != nil { if err != nil {
log.Stderrf("packageHtml.Execute: %s", err); log.Stderrf("packageHtml.Execute: %s", err);
} }
servePage(c, doc.ImportPath + " - Go package documentation", b.Data()); servePage(c, doc.ImportPath + " - Go package documentation", buf.Data());
} }
type pakInfo struct { // TODO like to use []*os.Dir instead of []os.Dir - template.go doesn't
// automatically indirect pointers it seems, so this would require
// custom formatters at the moment
type Dirs struct {
Path string; Path string;
Package *pakDesc; Dirs []os.Dir;
Packages pakArray;
Subdirs []os.Dir; // TODO(rsc): []*os.Dir
} }
func servePackageList(c *http.Conn, info *pakInfo) { func serveDirList(c *http.Conn, path string, dirs []os.Dir) {
var b io.ByteBuffer; var buf io.ByteBuffer;
err := packagelistHtml.Execute(info, &b); err := dirlistHtml.Execute(Dirs{path, dirs}, &buf);
if err != nil { if err != nil {
log.Stderrf("packagelistHtml.Execute: %s", err); log.Stderrf("dirlist.Execute: %s", err);
} }
servePage(c, info.Path + " - Go packages", b.Data()); servePage(c, path + " - Directories", buf.Data());
}
// Return package or packages named by name.
// Name is either an import string or a directory,
// like you'd see in $GOROOT/pkg/.
//
// Examples:
// "math" - single package made up of directory
// "container" - directory listing
// "container/vector" - single package in container directory
//
func findPackages(name string) *pakInfo {
info := new(pakInfo);
// Build list of packages.
pmap := make(map[string]*pakDesc);
// 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.
cname := pathutil.Clean(name);
if cname == "" {
cname = "."
}
dir := pathutil.Join(*pkgroot, cname);
if isDir(dir) {
addDirectory(pmap, dir, cname, &info.Subdirs);
paks := mapValues(pmap);
if len(paks) == 1 {
p := paks[0];
_, pak := pathutil.Split(dir);
if p.dirname == dir && p.pakname == pak {
info.Package = p;
info.Path = cname;
return info;
}
}
info.Packages = paks;
if cname == "." {
info.Path = "";
} else {
info.Path = cname + "/";
}
return info;
}
// Otherwise, have parentdir/pak. Look for package pak in parentdir.
parentdir, _ := pathutil.Split(dir);
parentname, _ := pathutil.Split(cname);
if parentname == "" {
parentname = "."
}
addDirectory(pmap, parentdir, parentname, nil);
if p, ok := pmap[cname]; ok {
info.Package = p;
info.Path = cname;
return info;
}
info.Path = name; // original, uncleaned name
return info;
} }
func servePkg(c *http.Conn, r *http.Request) { func servePkg(c *http.Conn, r *http.Request) {
path := r.Url.Path; path := r.Url.Path;
path = path[len(Pkg) : len(path)]; path = path[len(Pkg) : len(path)];
info := findPackages(path); desc, dirs := findPackage(path);
/*
// TODO do we still need this?
if r.Url.Path != Pkg + info.Path { if r.Url.Path != Pkg + info.Path {
http.Redirect(c, info.Path); http.Redirect(c, info.Path);
return; return;
} }
*/
if info.Package != nil { if desc != nil {
servePackage(c, info.Package); servePackage(c, desc);
// TODO should also serve sub-directories if there are any
} else { } else {
servePackageList(c, info); // make sure path is not empty otherwise html links become rooted
// and won't work correctly
if path == "" {
path = ".";
}
serveDirList(c, path, dirs);
} }
} }
@ -751,20 +657,20 @@ func main() {
if *html { if *html {
packageText = packageHtml; packageText = packageHtml;
packagelistText = packagelistHtml; dirlistText = dirlistHtml;
parseerrorText = parseerrorHtml; parseerrorText = parseerrorHtml;
} }
info := findPackages(flag.Arg(0)); desc, dirs := findPackage(flag.Arg(0));
if info.Package == nil { if desc == nil {
err := packagelistText.Execute(info, os.Stderr); err := dirlistText.Execute(dirs, os.Stdout);
if err != nil { if err != nil {
log.Stderrf("packagelistText.Execute: %s", err); log.Stderrf("dirlistText.Execute: %s", err);
} }
os.Exit(1); os.Exit(0);
} }
doc, errors := info.Package.Doc(); doc, errors := desc.Doc();
if errors != nil { if errors != nil {
err := parseerrorText.Execute(errors, os.Stderr); err := parseerrorText.Execute(errors, os.Stderr);
if err != nil { if err != nil {