2009-04-02 16:58:58 -06:00
|
|
|
// 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
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
// 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
|
|
|
|
|
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2009-04-02 19:25:18 -06:00
|
|
|
"ast";
|
2009-04-02 16:58:58 -06:00
|
|
|
"bufio";
|
|
|
|
"flag";
|
|
|
|
"fmt";
|
|
|
|
"http";
|
|
|
|
"io";
|
|
|
|
"log";
|
|
|
|
"net";
|
|
|
|
"os";
|
2009-04-02 19:25:18 -06:00
|
|
|
"parser";
|
2009-04-13 14:28:53 -06:00
|
|
|
pathutil "path";
|
2009-04-02 16:58:58 -06:00
|
|
|
"sort";
|
|
|
|
"tabwriter";
|
|
|
|
"template";
|
2009-04-02 19:25:18 -06:00
|
|
|
"time";
|
|
|
|
"token";
|
2009-04-02 16:58:58 -06:00
|
|
|
"vector";
|
2009-04-02 19:25:18 -06:00
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
"astprinter";
|
2009-04-02 16:58:58 -06:00
|
|
|
"docprinter";
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2009-04-02 19:25:18 -06:00
|
|
|
// TODO
|
|
|
|
// - uniform use of path, filename, dirname, pakname, etc.
|
2009-04-02 22:59:37 -06:00
|
|
|
// - fix weirdness with double-/'s in paths
|
2009-04-13 14:28:53 -06:00
|
|
|
// - split http service into its own source file
|
|
|
|
|
|
|
|
|
|
|
|
const usageString =
|
|
|
|
"usage: godoc package [name ...]\n"
|
|
|
|
" godoc -http=:6060\n"
|
2009-04-02 22:59:37 -06:00
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
docPrefix = "/doc/";
|
2009-04-03 17:19:22 -06:00
|
|
|
filePrefix = "/file/";
|
2009-04-02 22:59:37 -06:00
|
|
|
)
|
2009-04-02 19:25:18 -06:00
|
|
|
|
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
var (
|
2009-04-13 14:28:53 -06:00
|
|
|
goroot string;
|
2009-04-02 19:25:18 -06:00
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
verbose = flag.Bool("v", false, "verbose mode");
|
2009-04-13 14:28:53 -06:00
|
|
|
|
|
|
|
// server control
|
|
|
|
httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')");
|
2009-04-02 16:58:58 -06:00
|
|
|
|
|
|
|
// layout control
|
|
|
|
tabwidth = flag.Int("tabwidth", 4, "tab width");
|
2009-04-13 14:28:53 -06:00
|
|
|
usetabs = flag.Bool("tabs", false, "align with tabs instead of spaces");
|
2009-04-02 16:58:58 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
func init() {
|
|
|
|
var err *os.Error;
|
|
|
|
goroot, err = os.Getenv("GOROOT");
|
|
|
|
if err != nil {
|
|
|
|
goroot = "/home/r/go-build/go";
|
2009-04-02 19:25:18 -06:00
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
flag.StringVar(&goroot, "goroot", goroot, "Go root directory");
|
2009-04-02 19:25:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Support
|
2009-04-02 19:25:18 -06:00
|
|
|
|
2009-04-02 22:59:37 -06:00
|
|
|
func hasPrefix(s, prefix string) bool {
|
|
|
|
return len(prefix) <= len(s) && s[0 : len(prefix)] == prefix;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
func hasSuffix(s, suffix string) bool {
|
|
|
|
pos := len(s) - len(suffix);
|
|
|
|
return pos >= 0 && s[pos : len(s)] == suffix;
|
2009-04-02 19:25:18 -06:00
|
|
|
}
|
2009-04-02 16:58:58 -06:00
|
|
|
|
|
|
|
|
|
|
|
func isGoFile(dir *os.Dir) bool {
|
2009-04-02 23:24:52 -06:00
|
|
|
return dir.IsRegular() && hasSuffix(dir.Name, ".go");
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func isHTMLFile(dir *os.Dir) bool {
|
|
|
|
return dir.IsRegular() && hasSuffix(dir.Name, ".html");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
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, "<a href=\"%s\">%s</a><br />\n", pathutil.Clean(filePrefix + dir + "/" + name), name);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func makeTabwriter(writer io.Write) *tabwriter.Writer {
|
|
|
|
padchar := byte(' ');
|
|
|
|
if *usetabs {
|
|
|
|
padchar = '\t';
|
|
|
|
}
|
|
|
|
return tabwriter.NewWriter(writer, *tabwidth, 1, padchar, tabwriter.FilterHTML);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-02 19:25:18 -06:00
|
|
|
// ----------------------------------------------------------------------------
|
2009-04-13 14:28:53 -06:00
|
|
|
// Parsing
|
2009-04-02 19:25:18 -06:00
|
|
|
|
|
|
|
type parseError struct {
|
|
|
|
pos token.Position;
|
|
|
|
msg string;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type errorList []parseError
|
|
|
|
func (list errorList) Len() int { return len(list); }
|
|
|
|
func (list errorList) Less(i, j int) bool { return list[i].pos.Offset < list[j].pos.Offset; }
|
|
|
|
func (list errorList) Swap(i, j int) { list[i], list[j] = list[j], list[i]; }
|
|
|
|
|
|
|
|
|
|
|
|
type errorHandler struct {
|
|
|
|
lastLine int;
|
|
|
|
errors *vector.Vector;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (h *errorHandler) Error(pos token.Position, msg string) {
|
2009-04-13 14:28:53 -06:00
|
|
|
// only collect errors that are on a new line
|
2009-04-02 19:25:18 -06:00
|
|
|
// in the hope to avoid most follow-up errors
|
|
|
|
if pos.Line != h.lastLine {
|
|
|
|
h.lastLine = pos.Line;
|
|
|
|
if h.errors == nil {
|
|
|
|
// lazy initialize - most of the time there are no errors
|
|
|
|
h.errors = vector.New(0);
|
|
|
|
}
|
|
|
|
h.errors.Push(parseError{pos, msg});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
// Parses a file (path) and returns the corresponding AST and
|
2009-04-02 19:25:18 -06:00
|
|
|
// a sorted list (by file position) of errors, if any.
|
|
|
|
//
|
2009-04-13 14:28:53 -06:00
|
|
|
func parse(path string, mode uint) (*ast.Program, errorList) {
|
2009-04-02 19:25:18 -06:00
|
|
|
src, err := os.Open(path, os.O_RDONLY, 0);
|
|
|
|
defer src.Close();
|
|
|
|
if err != nil {
|
2009-04-13 14:28:53 -06:00
|
|
|
log.Stdoutf("open %s: %v", path, err);
|
2009-04-02 19:25:18 -06:00
|
|
|
var noPos token.Position;
|
|
|
|
return nil, errorList{parseError{noPos, err.String()}};
|
|
|
|
}
|
|
|
|
|
|
|
|
var handler errorHandler;
|
|
|
|
prog, ok := parser.Parse(src, &handler, mode);
|
|
|
|
if !ok {
|
|
|
|
// convert error list and sort it
|
|
|
|
errors := make(errorList, handler.errors.Len());
|
|
|
|
for i := 0; i < handler.errors.Len(); i++ {
|
|
|
|
errors[i] = handler.errors.At(i).(parseError);
|
|
|
|
}
|
|
|
|
sort.Sort(errors);
|
|
|
|
return nil, errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
return prog, nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Templates
|
|
|
|
|
|
|
|
// html template
|
|
|
|
// TODO initialize only if needed (i.e. if run as a server)
|
|
|
|
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");
|
2009-04-13 14:28:53 -06:00
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
// TODO handle Apply errors
|
|
|
|
godoc_html.Apply(c, "<!--", template.Substitution {
|
|
|
|
"TITLE-->" : func() { fmt.Fprint(c, title); },
|
|
|
|
"HEADER-->" : func() { fmt.Fprint(c, title); },
|
|
|
|
"TIMESTAMP-->" : func() { fmt.Fprint(c, time.UTC().String()); },
|
|
|
|
"CONTENTS-->" : contents
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func serveError(c *http.Conn, err, arg string) {
|
|
|
|
servePage(c, "Error", func () {
|
|
|
|
fmt.Fprintf(c, "%v (%s)\n", err, arg);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Directories
|
|
|
|
|
2009-04-02 19:25:18 -06:00
|
|
|
type dirArray []os.Dir
|
|
|
|
func (p dirArray) Len() int { return len(p); }
|
|
|
|
func (p dirArray) Less(i, j int) bool { return p[i].Name < p[j].Name; }
|
|
|
|
func (p dirArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; }
|
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
|
|
|
|
func serveDir(c *http.Conn, dirname string) {
|
2009-04-13 14:28:53 -06:00
|
|
|
fd, err1 := os.Open(dirname, os.O_RDONLY, 0);
|
2009-04-02 16:58:58 -06:00
|
|
|
if err1 != nil {
|
|
|
|
c.WriteHeader(http.StatusNotFound);
|
|
|
|
fmt.Fprintf(c, "Error: %v (%s)\n", err1, dirname);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
list, err2 := fd.Readdir(-1);
|
|
|
|
if err2 != nil {
|
|
|
|
c.WriteHeader(http.StatusNotFound);
|
|
|
|
fmt.Fprintf(c, "Error: %v (%s)\n", err2, dirname);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(dirArray(list));
|
|
|
|
|
|
|
|
path := dirname + "/";
|
|
|
|
|
|
|
|
// Print contents in 3 sections: directories, go files, everything else
|
2009-04-03 17:19:22 -06:00
|
|
|
servePage(c, dirname + " - Contents", func () {
|
|
|
|
fmt.Fprintln(c, "<h2>Directories</h2>");
|
|
|
|
for i, entry := range list {
|
|
|
|
if entry.IsDirectory() {
|
|
|
|
printLink(c, path, entry.Name);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
2009-04-03 17:19:22 -06:00
|
|
|
}
|
2009-04-02 16:58:58 -06:00
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
fmt.Fprintln(c, "<h2>Go files</h2>");
|
|
|
|
for i, entry := range list {
|
|
|
|
if isGoFile(&entry) {
|
|
|
|
printLink(c, path, entry.Name);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
2009-04-03 17:19:22 -06:00
|
|
|
}
|
2009-04-02 16:58:58 -06:00
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
fmt.Fprintln(c, "<h2>Other files</h2>");
|
|
|
|
for i, entry := range list {
|
|
|
|
if !entry.IsDirectory() && !isGoFile(&entry) {
|
|
|
|
fmt.Fprintf(c, "%s<br />\n", entry.Name);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Files
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
func serveParseErrors(c *http.Conn, filename string, errors errorList) {
|
2009-04-02 19:25:18 -06:00
|
|
|
// open file
|
2009-04-13 14:28:53 -06:00
|
|
|
path := filename;
|
2009-04-02 19:25:18 -06:00
|
|
|
fd, err1 := os.Open(path, os.O_RDONLY, 0);
|
|
|
|
defer fd.Close();
|
|
|
|
if err1 != nil {
|
2009-04-03 17:19:22 -06:00
|
|
|
serveError(c, err1.String(), path);
|
|
|
|
return;
|
2009-04-02 19:25:18 -06:00
|
|
|
}
|
2009-04-02 16:58:58 -06:00
|
|
|
|
2009-04-02 19:25:18 -06:00
|
|
|
// read source
|
|
|
|
var buf io.ByteBuffer;
|
|
|
|
n, err2 := io.Copy(fd, &buf);
|
|
|
|
if err2 != nil {
|
2009-04-03 17:19:22 -06:00
|
|
|
serveError(c, err2.String(), path);
|
|
|
|
return;
|
2009-04-02 19:25:18 -06:00
|
|
|
}
|
|
|
|
src := buf.Data();
|
2009-04-02 16:58:58 -06:00
|
|
|
|
|
|
|
// TODO handle Apply errors
|
2009-04-03 17:19:22 -06:00
|
|
|
servePage(c, filename, func () {
|
|
|
|
// section title
|
2009-04-13 14:28:53 -06:00
|
|
|
fmt.Fprintf(c, "<h1>Parse errors in %s</h1>\n", filename);
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
// handle read errors
|
2009-04-13 14:28:53 -06:00
|
|
|
if err1 != nil || err2 != nil {
|
2009-04-03 17:19:22 -06:00
|
|
|
fmt.Fprintf(c, "could not read file %s\n", filename);
|
|
|
|
return;
|
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
// write source with error messages interspersed
|
|
|
|
fmt.Fprintln(c, "<pre>");
|
|
|
|
offs := 0;
|
|
|
|
for i, e := range errors {
|
|
|
|
if 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
|
|
|
|
// TODO handle Write errors
|
|
|
|
c.Write(src[offs : e.pos.Offset]);
|
|
|
|
// TODO this should be done using a .css file
|
|
|
|
fmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.msg);
|
|
|
|
offs = e.pos.Offset;
|
|
|
|
} else {
|
|
|
|
log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
}
|
2009-04-03 17:19:22 -06:00
|
|
|
// TODO handle Write errors
|
|
|
|
c.Write(src[offs : len(src)]);
|
|
|
|
fmt.Fprintln(c, "</pre>");
|
2009-04-02 16:58:58 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func serveGoSource(c *http.Conn, dirname string, filename string) {
|
|
|
|
path := dirname + "/" + filename;
|
2009-04-13 14:28:53 -06:00
|
|
|
prog, errors := parse(path, parser.ParseComments);
|
2009-04-03 17:19:22 -06:00
|
|
|
if len(errors) > 0 {
|
2009-04-13 14:28:53 -06:00
|
|
|
serveParseErrors(c, filename, errors);
|
2009-04-03 17:19:22 -06:00
|
|
|
return;
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
servePage(c, path + " - Go source", func () {
|
|
|
|
fmt.Fprintln(c, "<pre>");
|
|
|
|
var p astPrinter.Printer;
|
|
|
|
writer := makeTabwriter(c); // for nicely formatted output
|
|
|
|
p.Init(writer, nil, nil, true);
|
|
|
|
p.DoProgram(prog);
|
|
|
|
writer.Flush(); // ignore errors
|
|
|
|
fmt.Fprintln(c, "</pre>");
|
2009-04-02 19:25:18 -06:00
|
|
|
});
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func serveHTMLFile(c *http.Conn, filename string) {
|
|
|
|
src, err1 := os.Open(filename, os.O_RDONLY, 0);
|
|
|
|
defer src.Close();
|
|
|
|
if err1 != nil {
|
|
|
|
serveError(c, err1.String(), filename);
|
|
|
|
return
|
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
if written, err2 := io.Copy(src, c); err2 != nil {
|
2009-04-03 17:19:22 -06:00
|
|
|
serveError(c, err2.String(), filename);
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func serveFile(c *http.Conn, path string) {
|
2009-04-13 14:28:53 -06:00
|
|
|
dir, err := os.Stat(path);
|
2009-04-02 16:58:58 -06:00
|
|
|
if err != nil {
|
2009-04-03 17:19:22 -06:00
|
|
|
serveError(c, err.String(), path);
|
2009-04-02 16:58:58 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case dir.IsDirectory():
|
|
|
|
serveDir(c, path);
|
|
|
|
case isGoFile(dir):
|
2009-04-13 14:28:53 -06:00
|
|
|
serveGoSource(c, ".", path);
|
2009-04-03 17:19:22 -06:00
|
|
|
case isHTMLFile(dir):
|
2009-04-13 14:28:53 -06:00
|
|
|
serveHTMLFile(c, path);
|
2009-04-02 16:58:58 -06:00
|
|
|
default:
|
2009-04-03 17:19:22 -06:00
|
|
|
serveError(c, "Not a directory or .go file", path);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Packages
|
|
|
|
|
|
|
|
type pakDesc struct {
|
2009-04-13 14:28:53 -06:00
|
|
|
dirname string; // relative to goroot
|
|
|
|
pakname string; // relative to directory
|
2009-04-02 16:58:58 -06:00
|
|
|
filenames map[string] bool; // set of file (names) belonging to this package
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type pakArray []*pakDesc
|
|
|
|
func (p pakArray) Len() int { return len(p); }
|
|
|
|
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]; }
|
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
|
2009-04-02 23:24:52 -06:00
|
|
|
if hasSuffix(filename, "_test.go") {
|
2009-04-02 22:59:37 -06:00
|
|
|
// ignore package tests
|
|
|
|
return;
|
|
|
|
}
|
2009-04-02 16:58:58 -06:00
|
|
|
// determine package name
|
2009-04-13 14:28:53 -06:00
|
|
|
path := dirname + "/" + filename;
|
|
|
|
prog, errors := parse(path, parser.PackageClauseOnly);
|
2009-04-02 16:58:58 -06:00
|
|
|
if prog == nil {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if prog.Name.Value == "main" {
|
|
|
|
// ignore main packages for now
|
|
|
|
return;
|
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
pakname := pathutil.Clean(dirname + "/" + prog.Name.Value);
|
2009-04-02 16:58:58 -06:00
|
|
|
|
|
|
|
// find package descriptor
|
2009-04-03 17:19:22 -06:00
|
|
|
pakdesc, found := pmap[pakname];
|
2009-04-02 16:58:58 -06:00
|
|
|
if !found {
|
|
|
|
// add a new descriptor
|
|
|
|
pakdesc = &pakDesc{dirname, prog.Name.Value, make(map[string]bool)};
|
2009-04-03 17:19:22 -06:00
|
|
|
pmap[pakname] = pakdesc;
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
//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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func addDirectory(pmap map[string]*pakDesc, dirname string) {
|
2009-04-13 14:28:53 -06:00
|
|
|
path := dirname;
|
|
|
|
fd, err1 := os.Open(path, os.O_RDONLY, 0);
|
2009-04-02 16:58:58 -06:00
|
|
|
if err1 != nil {
|
2009-04-13 14:28:53 -06:00
|
|
|
log.Stdoutf("open %s: %v", path, err1);
|
2009-04-02 16:58:58 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
list, err2 := fd.Readdir(-1);
|
|
|
|
if err2 != nil {
|
2009-04-13 14:28:53 -06:00
|
|
|
log.Stdoutf("readdir %s: %v", path, err2);
|
2009-04-02 16:58:58 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, entry := range list {
|
|
|
|
switch {
|
2009-04-13 14:28:53 -06:00
|
|
|
case isGoFile(&entry):
|
2009-04-02 16:58:58 -06:00
|
|
|
//fmt.Printf("found %s/%s\n", dirname, entry.Name);
|
2009-04-03 17:19:22 -06:00
|
|
|
addFile(pmap, dirname, entry.Name);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
func mapValues(pmap map[string]*pakDesc) pakArray {
|
2009-04-02 16:58:58 -06:00
|
|
|
// build sorted package list
|
2009-04-03 17:19:22 -06:00
|
|
|
plist := make(pakArray, len(pmap));
|
2009-04-02 16:58:58 -06:00
|
|
|
i := 0;
|
2009-04-03 17:19:22 -06:00
|
|
|
for tmp, pakdesc := range pmap {
|
|
|
|
plist[i] = pakdesc;
|
2009-04-02 16:58:58 -06:00
|
|
|
i++;
|
|
|
|
}
|
2009-04-03 17:19:22 -06:00
|
|
|
sort.Sort(plist);
|
2009-04-13 14:28:53 -06:00
|
|
|
return plist;
|
2009-04-02 19:25:18 -06:00
|
|
|
}
|
2009-04-02 16:58:58 -06:00
|
|
|
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func servePackage(c *http.Conn, p *pakDesc) {
|
2009-04-02 16:58:58 -06:00
|
|
|
// make a filename list
|
2009-04-03 17:19:22 -06:00
|
|
|
filenames := make([]string, len(p.filenames));
|
2009-04-02 16:58:58 -06:00
|
|
|
i := 0;
|
|
|
|
for filename, tmp := range p.filenames {
|
2009-04-03 17:19:22 -06:00
|
|
|
filenames[i] = filename;
|
2009-04-02 16:58:58 -06:00
|
|
|
i++;
|
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
// compute documentation
|
|
|
|
var doc docPrinter.PackageDoc;
|
|
|
|
for i, filename := range filenames {
|
2009-04-13 14:28:53 -06:00
|
|
|
path := p.dirname + "/" + filename;
|
|
|
|
prog, errors := parse(path, parser.ParseComments);
|
2009-04-03 17:19:22 -06:00
|
|
|
if len(errors) > 0 {
|
2009-04-13 14:28:53 -06:00
|
|
|
serveParseErrors(c, filename, errors);
|
2009-04-03 17:19:22 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if i == 0 {
|
|
|
|
// first package - initialize docPrinter
|
|
|
|
doc.Init(prog.Name.Value);
|
|
|
|
}
|
|
|
|
doc.AddProgram(prog);
|
|
|
|
}
|
|
|
|
|
|
|
|
servePage(c, doc.PackageName() + " - Go package documentation", func () {
|
|
|
|
writer := makeTabwriter(c); // for nicely formatted output
|
|
|
|
doc.Print(writer);
|
|
|
|
writer.Flush(); // ignore errors
|
|
|
|
});
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
func servePackageList(c *http.Conn, list pakArray) {
|
2009-04-03 17:19:22 -06:00
|
|
|
servePage(c, "Packages", func () {
|
2009-04-13 14:28:53 -06:00
|
|
|
for i := 0; i < len(list); i++ {
|
|
|
|
p := list[i];
|
|
|
|
link := pathutil.Clean(p.dirname + "/" + p.pakname);
|
|
|
|
fmt.Fprintf(c, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n",
|
|
|
|
p.pakname, p.pakname, link);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
});
|
2009-04-13 14:28:53 -06:00
|
|
|
|
|
|
|
// TODO: show subdirectories
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
// 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;
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
// 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;
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
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);
|
2009-04-03 17:19:22 -06:00
|
|
|
default:
|
2009-04-13 14:28:53 -06:00
|
|
|
serveError(c, "No packages found", path);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Server
|
|
|
|
|
2009-04-03 17:19:22 -06:00
|
|
|
func makeFixedFileServer(filename string) (func(c *http.Conn, path string)) {
|
|
|
|
return func(c *http.Conn, path string) {
|
2009-04-13 14:28:53 -06:00
|
|
|
serveFile(c, filename);
|
2009-04-03 17:19:22 -06:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-02 22:59:37 -06:00
|
|
|
func installHandler(prefix string, handler func(c *http.Conn, path string)) {
|
2009-04-03 17:19:22 -06:00
|
|
|
// create a handler customized with prefix
|
2009-04-02 22:59:37 -06:00
|
|
|
f := func(c *http.Conn, req *http.Request) {
|
|
|
|
path := req.Url.Path;
|
|
|
|
if *verbose {
|
|
|
|
log.Stdoutf("%s\t%s", req.Host, path);
|
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
handler(c, path[len(prefix) : len(path)]);
|
2009-04-02 22:59:37 -06:00
|
|
|
};
|
2009-04-02 16:58:58 -06:00
|
|
|
|
2009-04-02 22:59:37 -06:00
|
|
|
// install the customized handler
|
|
|
|
http.Handle(prefix, http.HandlerFunc(f));
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
func usage() {
|
|
|
|
fmt.Fprintf(os.Stderr, usageString);
|
|
|
|
sys.Exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-02 16:58:58 -06:00
|
|
|
func main() {
|
|
|
|
flag.Parse();
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
// Check usage first; get usage message out early.
|
|
|
|
switch {
|
|
|
|
case *httpaddr != "":
|
|
|
|
if flag.NArg() != 0 {
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if flag.NArg() == 0 {
|
|
|
|
usage();
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
if err := os.Chdir(goroot); err != nil {
|
|
|
|
log.Exitf("chdir %s: %v", goroot, err);
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
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);
|
2009-04-03 17:19:22 -06:00
|
|
|
|
2009-04-13 14:28:53 -06:00
|
|
|
if err := http.ListenAndServe(*httpaddr, nil); err != nil {
|
|
|
|
log.Exitf("ListenAndServe %s: %v", *httpaddr, err)
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
return;
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|
2009-04-13 14:28:53 -06:00
|
|
|
|
|
|
|
log.Exitf("godoc command-line not implemented");
|
2009-04-02 16:58:58 -06:00
|
|
|
}
|