2009-06-16 10:14:06 -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
|
|
|
|
|
|
|
|
// 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 (
|
2009-06-29 16:24:23 -06:00
|
|
|
"bytes";
|
2009-06-16 10:14:06 -06:00
|
|
|
"container/vector";
|
|
|
|
"flag";
|
|
|
|
"fmt";
|
|
|
|
"go/ast";
|
|
|
|
"go/doc";
|
|
|
|
"go/parser";
|
|
|
|
"go/printer";
|
2009-07-14 11:46:18 -06:00
|
|
|
"go/scanner";
|
2009-06-16 10:14:06 -06:00
|
|
|
"go/token";
|
|
|
|
"http";
|
|
|
|
"io";
|
|
|
|
"log";
|
|
|
|
"os";
|
|
|
|
pathutil "path";
|
|
|
|
"sort";
|
|
|
|
"strings";
|
|
|
|
"sync";
|
|
|
|
"template";
|
|
|
|
"time";
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
const Pkg = "/pkg/"; // name for auto-generated package documentation tree
|
2009-06-16 10:14:06 -06:00
|
|
|
|
|
|
|
|
2009-08-28 12:28:05 -06:00
|
|
|
type delayTime struct {
|
|
|
|
mutex sync.RWMutex;
|
|
|
|
minutes int;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (dt *delayTime) set(minutes int) {
|
|
|
|
dt.mutex.Lock();
|
|
|
|
dt.minutes = minutes;
|
|
|
|
dt.mutex.Unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (dt *delayTime) backoff(max int) {
|
|
|
|
dt.mutex.Lock();
|
|
|
|
dt.minutes *= 2;
|
|
|
|
if dt.minutes > max {
|
|
|
|
dt.minutes = max
|
|
|
|
}
|
|
|
|
dt.mutex.Unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (dt *delayTime) get() int {
|
|
|
|
dt.mutex.RLock();
|
|
|
|
defer dt.mutex.RUnlock();
|
|
|
|
return dt.minutes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-16 10:14:06 -06:00
|
|
|
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)");
|
2009-06-16 10:30:16 -06:00
|
|
|
tmplroot = flag.String("tmplroot", "lib/godoc", "root template directory (if unrooted, relative to goroot)");
|
2009-06-16 10:14:06 -06:00
|
|
|
|
|
|
|
// periodic sync
|
|
|
|
syncCmd = flag.String("sync", "", "sync command; disabled if empty");
|
|
|
|
syncMin = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0");
|
2009-08-28 12:28:05 -06:00
|
|
|
syncDelay delayTime; // actual sync delay in minutes; usually syncDelay == syncMin, but delay may back off exponentially
|
2009-06-16 10:14:06 -06:00
|
|
|
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() {
|
2009-06-26 21:28:41 -06:00
|
|
|
goroot = os.Getenv("GOROOT");
|
2009-07-02 10:47:25 -06:00
|
|
|
if goroot == "" {
|
2009-06-16 10:14:06 -06:00
|
|
|
goroot = "/home/r/go-release/go";
|
|
|
|
}
|
|
|
|
flag.StringVar(&goroot, "goroot", goroot, "Go root directory");
|
|
|
|
syncTime.set(); // have a reasonable initial value
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Support
|
|
|
|
|
|
|
|
func isGoFile(dir *os.Dir) bool {
|
2009-07-29 18:01:09 -06:00
|
|
|
return
|
|
|
|
dir.IsRegular() &&
|
|
|
|
!strings.HasPrefix(dir.Name, ".") && // ignore .files
|
|
|
|
pathutil.Ext(dir.Name) == ".go" &&
|
|
|
|
!strings.HasSuffix(dir.Name, "_test.go"); // ignore test files
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func isPkgDir(dir *os.Dir) bool {
|
2009-08-20 10:49:05 -06:00
|
|
|
return dir.IsDirectory() && len(dir.Name) > 0 && dir.Name[0] != '_';
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// 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.
|
|
|
|
//
|
2009-07-16 18:11:18 -06:00
|
|
|
func parse(path string, mode uint) (*ast.File, *parseErrors) {
|
2009-06-16 10:14:06 -06:00
|
|
|
src, err := io.ReadFile(path);
|
|
|
|
if err != nil {
|
2009-10-01 15:08:00 -06:00
|
|
|
log.Stderrf("%v", err);
|
2009-06-16 10:14:06 -06:00
|
|
|
errs := []parseError{parseError{nil, 0, err.String()}};
|
|
|
|
return nil, &parseErrors{path, errs, nil};
|
|
|
|
}
|
|
|
|
|
2009-07-16 18:11:18 -06:00
|
|
|
prog, err := parser.ParseFile(path, src, mode);
|
2009-06-16 10:14:06 -06:00
|
|
|
if err != nil {
|
2009-07-14 17:30:06 -06:00
|
|
|
var errs []parseError;
|
2009-07-14 11:46:18 -06:00
|
|
|
if errors, ok := err.(scanner.ErrorList); ok {
|
2009-07-14 17:30:06 -06:00
|
|
|
// convert error list (already sorted)
|
|
|
|
// TODO(gri) If the file contains //line comments, the errors
|
|
|
|
// may not be sorted in increasing file offset value
|
|
|
|
// which will lead to incorrect output.
|
|
|
|
errs = make([]parseError, len(errors) + 1); // +1 for final fragment of source
|
2009-06-16 10:14:06 -06:00
|
|
|
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)];
|
|
|
|
} else {
|
2009-07-14 17:30:06 -06:00
|
|
|
// single error of unspecified type
|
|
|
|
errs = make([]parseError, 2);
|
|
|
|
errs[0] = parseError{[]byte{}, 0, err.String()};
|
|
|
|
errs[1].src = src;
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
2009-07-14 17:30:06 -06:00
|
|
|
return nil, &parseErrors{path, errs, src};
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return prog, nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Templates
|
|
|
|
|
2009-07-31 19:04:53 -06:00
|
|
|
// Write an AST-node to w; optionally html-escaped.
|
|
|
|
func writeNode(w io.Writer, node interface{}, html bool) {
|
|
|
|
mode := printer.UseSpaces;
|
|
|
|
if html {
|
|
|
|
mode |= printer.GenHTML;
|
|
|
|
}
|
|
|
|
printer.Fprint(w, node, mode, *tabwidth);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-31 19:04:53 -06:00
|
|
|
// Write text to w; optionally html-escaped.
|
|
|
|
func writeText(w io.Writer, text []byte, html bool) {
|
|
|
|
if html {
|
|
|
|
template.HtmlEscape(w, text);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
w.Write(text);
|
|
|
|
}
|
|
|
|
|
2009-06-16 10:14:06 -06:00
|
|
|
|
2009-07-31 19:04:53 -06:00
|
|
|
// Write anything to w; optionally html-escaped.
|
|
|
|
func writeAny(w io.Writer, x interface{}, html bool) {
|
2009-06-16 10:14:06 -06:00
|
|
|
switch v := x.(type) {
|
|
|
|
case []byte:
|
2009-07-31 19:04:53 -06:00
|
|
|
writeText(w, v, html);
|
2009-06-16 10:14:06 -06:00
|
|
|
case string:
|
2009-07-31 19:04:53 -06:00
|
|
|
writeText(w, strings.Bytes(v), html);
|
2009-06-16 10:14:06 -06:00
|
|
|
case ast.Decl:
|
2009-07-31 19:04:53 -06:00
|
|
|
writeNode(w, v, html);
|
2009-06-16 10:14:06 -06:00
|
|
|
case ast.Expr:
|
2009-07-31 19:04:53 -06:00
|
|
|
writeNode(w, v, html);
|
|
|
|
default:
|
|
|
|
if html {
|
|
|
|
var buf bytes.Buffer;
|
|
|
|
fmt.Fprint(&buf, x);
|
2009-09-16 16:15:00 -06:00
|
|
|
writeText(w, buf.Bytes(), true);
|
2009-07-31 19:04:53 -06:00
|
|
|
} else {
|
|
|
|
fmt.Fprint(w, x);
|
|
|
|
}
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Template formatter for "html" format.
|
|
|
|
func htmlFmt(w io.Writer, x interface{}, format string) {
|
2009-07-31 19:04:53 -06:00
|
|
|
writeAny(w, x, true);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Template formatter for "html-comment" format.
|
|
|
|
func htmlCommentFmt(w io.Writer, x interface{}, format string) {
|
2009-07-31 19:04:53 -06:00
|
|
|
var buf bytes.Buffer;
|
|
|
|
writeAny(&buf, x, false);
|
2009-09-16 16:15:00 -06:00
|
|
|
doc.ToHtml(w, buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Template formatter for "" (default) format.
|
|
|
|
func textFmt(w io.Writer, x interface{}, format string) {
|
2009-07-31 19:04:53 -06:00
|
|
|
writeAny(w, x, false);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-08-03 10:53:00 -06:00
|
|
|
// Template formatter for "link" format.
|
|
|
|
func linkFmt(w io.Writer, x interface{}, format string) {
|
|
|
|
type Positioner interface { Pos() token.Position }
|
|
|
|
if node, ok := x.(Positioner); ok {
|
|
|
|
pos := node.Pos();
|
|
|
|
if pos.IsValid() {
|
|
|
|
// line id's in html-printed source are of the
|
|
|
|
// form "L%d" where %d stands for the line number
|
|
|
|
fmt.Fprintf(w, "/%s#L%d", pos.Filename, pos.Line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-16 10:14:06 -06:00
|
|
|
var fmap = template.FormatterMap{
|
|
|
|
"": textFmt,
|
|
|
|
"html": htmlFmt,
|
|
|
|
"html-comment": htmlCommentFmt,
|
2009-08-03 10:53:00 -06:00
|
|
|
"link": linkFmt,
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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{};
|
|
|
|
timestamp string;
|
|
|
|
content interface{};
|
|
|
|
}
|
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
d := Data{
|
|
|
|
title: title,
|
|
|
|
timestamp: time.SecondsToLocalTime(syncTime.get()).String(),
|
|
|
|
content: content,
|
|
|
|
};
|
|
|
|
|
2009-08-03 10:53:00 -06:00
|
|
|
if err := godocHtml.Execute(&d, c); err != nil {
|
|
|
|
log.Stderrf("godocHtml.Execute: %s", err);
|
|
|
|
}
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func serveText(c *http.Conn, text []byte) {
|
|
|
|
c.SetHeader("content-type", "text/plain; charset=utf-8");
|
|
|
|
c.Write(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Files
|
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
var (
|
|
|
|
tagBegin = strings.Bytes("<!--");
|
|
|
|
tagEnd = strings.Bytes("-->");
|
|
|
|
)
|
|
|
|
|
|
|
|
// commentText returns the text of the first HTML comment in src.
|
|
|
|
func commentText(src []byte) (text string) {
|
|
|
|
i := bytes.Index(src, tagBegin);
|
|
|
|
j := bytes.Index(src, tagEnd);
|
|
|
|
if i >= 0 && j >= i+len(tagBegin) {
|
|
|
|
text = string(bytes.TrimSpace(src[i+len(tagBegin) : j]));
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func serveHtmlDoc(c *http.Conn, r *http.Request, filename string) {
|
|
|
|
// get HTML body contents
|
|
|
|
path := pathutil.Join(goroot, filename);
|
|
|
|
src, err := io.ReadFile(path);
|
|
|
|
if err != nil {
|
|
|
|
log.Stderrf("%v", err);
|
|
|
|
http.NotFound(c, r);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it's the language spec, add tags to EBNF productions
|
|
|
|
if strings.HasSuffix(path, "go_spec.html") {
|
|
|
|
var buf bytes.Buffer;
|
|
|
|
linkify(&buf, src);
|
|
|
|
src = buf.Bytes();
|
|
|
|
}
|
|
|
|
|
|
|
|
title := commentText(src);
|
|
|
|
servePage(c, title, src);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-16 10:14:06 -06:00
|
|
|
func serveParseErrors(c *http.Conn, errors *parseErrors) {
|
|
|
|
// format errors
|
2009-06-29 16:24:23 -06:00
|
|
|
var buf bytes.Buffer;
|
2009-08-03 10:53:00 -06:00
|
|
|
if err := parseerrorHtml.Execute(errors, &buf); err != nil {
|
|
|
|
log.Stderrf("parseerrorHtml.Execute: %s", err);
|
|
|
|
}
|
2009-10-01 15:08:00 -06:00
|
|
|
servePage(c, "Parse errors in source file " + errors.filename, buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
func serveGoSource(c *http.Conn, filename string) {
|
|
|
|
path := pathutil.Join(goroot, filename);
|
|
|
|
prog, errors := parse(path, parser.ParseComments);
|
2009-06-16 10:14:06 -06:00
|
|
|
if errors != nil {
|
|
|
|
serveParseErrors(c, errors);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-06-29 16:24:23 -06:00
|
|
|
var buf bytes.Buffer;
|
2009-06-16 10:14:06 -06:00
|
|
|
fmt.Fprintln(&buf, "<pre>");
|
2009-07-31 19:04:53 -06:00
|
|
|
writeNode(&buf, prog, true);
|
2009-06-16 10:14:06 -06:00
|
|
|
fmt.Fprintln(&buf, "</pre>");
|
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
servePage(c, "Source file " + filename, buf.Bytes());
|
2009-09-03 10:58:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-16 10:14:06 -06:00
|
|
|
var fileServer = http.FileServer(".", "");
|
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
func serveFile(c *http.Conn, r *http.Request) {
|
|
|
|
path := r.Url.Path;
|
|
|
|
|
2009-06-16 10:14:06 -06:00
|
|
|
// pick off special cases and hand the rest to the standard file server
|
2009-10-01 15:08:00 -06:00
|
|
|
switch ext := pathutil.Ext(path); {
|
|
|
|
case path == "/":
|
|
|
|
serveHtmlDoc(c, r, "doc/root.html");
|
2009-06-16 10:14:06 -06:00
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
case r.Url.Path == "/doc/root.html":
|
2009-06-16 10:14:06 -06:00
|
|
|
// hide landing page from its real name
|
2009-10-01 15:08:00 -06:00
|
|
|
http.NotFound(c, r);
|
|
|
|
|
|
|
|
case ext == ".html":
|
|
|
|
serveHtmlDoc(c, r, path);
|
2009-06-16 10:14:06 -06:00
|
|
|
|
2009-10-01 15:08:00 -06:00
|
|
|
case ext == ".go":
|
|
|
|
serveGoSource(c, path);
|
2009-06-16 10:14:06 -06:00
|
|
|
|
|
|
|
default:
|
2009-10-01 15:08:00 -06:00
|
|
|
// TODO:
|
|
|
|
// - need to decide what to serve and what not to serve
|
|
|
|
// - don't want to download files, want to see them
|
|
|
|
fileServer.ServeHTTP(c, r);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Packages
|
|
|
|
|
|
|
|
// 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] }
|
|
|
|
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
func pkgName(filename string) string {
|
|
|
|
file, err := parse(filename, parser.PackageClauseOnly);
|
|
|
|
if err != nil || file == nil {
|
|
|
|
return "";
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
2009-07-29 18:01:09 -06:00
|
|
|
return file.Name.Value;
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
type PageInfo struct {
|
|
|
|
PDoc *doc.PackageDoc; // nil if no package found
|
|
|
|
Dirs dirList; // nil if no subdirectories found
|
|
|
|
}
|
2009-06-16 10:14:06 -06:00
|
|
|
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
// getPageInfo returns the PageInfo for a given package directory.
|
|
|
|
// If there is no corresponding package in the directory,
|
|
|
|
// PageInfo.PDoc is nil. If there are no subdirectories,
|
|
|
|
// PageInfo.Dirs is nil.
|
|
|
|
//
|
|
|
|
func getPageInfo(path string) PageInfo {
|
|
|
|
// the path is relative to *pkgroot
|
|
|
|
dirname := pathutil.Join(*pkgroot, path);
|
2009-06-16 10:14:06 -06:00
|
|
|
|
2009-07-16 18:11:18 -06:00
|
|
|
// the package name is the directory name within its parent
|
2009-07-29 18:01:09 -06:00
|
|
|
_, pkgname := pathutil.Split(dirname);
|
|
|
|
|
|
|
|
// filter function to select the desired .go files and
|
|
|
|
// collect subdirectories
|
|
|
|
var subdirlist vector.Vector;
|
|
|
|
subdirlist.Init(0);
|
|
|
|
filter := func(d *os.Dir) bool {
|
|
|
|
if isGoFile(d) {
|
|
|
|
// Some directories contain main packages: Only accept
|
|
|
|
// files that belong to the expected package so that
|
|
|
|
// parser.ParsePackage doesn't return "multiple packages
|
|
|
|
// found" errors.
|
|
|
|
return pkgName(dirname + "/" + d.Name) == pkgname;
|
|
|
|
}
|
|
|
|
if isPkgDir(d) {
|
|
|
|
subdirlist.Push(d);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
2009-07-29 18:01:09 -06:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// get package AST
|
|
|
|
pkg, err := parser.ParsePackage(dirname, filter, parser.ParseComments);
|
|
|
|
if err != nil {
|
2009-10-01 15:08:00 -06:00
|
|
|
// TODO: parse errors should be shown instead of an empty directory
|
2009-07-29 18:01:09 -06:00
|
|
|
log.Stderr(err);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
// convert and sort subdirectory list, if any
|
2009-06-16 10:14:06 -06:00
|
|
|
var subdirs dirList;
|
2009-07-29 18:01:09 -06:00
|
|
|
if subdirlist.Len() > 0 {
|
|
|
|
subdirs = make(dirList, subdirlist.Len());
|
|
|
|
for i := 0; i < subdirlist.Len(); i++ {
|
|
|
|
subdirs[i] = subdirlist.At(i).(*os.Dir);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
sort.Sort(subdirs);
|
|
|
|
}
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
// compute package documentation
|
|
|
|
var pdoc *doc.PackageDoc;
|
|
|
|
if pkg != nil {
|
2009-07-30 19:13:55 -06:00
|
|
|
ast.PackageExports(pkg);
|
|
|
|
pdoc = doc.NewPackageDoc(pkg, pathutil.Clean(path)); // no trailing '/' in importpath
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
return PageInfo{pdoc, subdirs};
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func servePkg(c *http.Conn, r *http.Request) {
|
|
|
|
path := r.Url.Path;
|
|
|
|
path = path[len(Pkg) : len(path)];
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
// canonicalize URL path and redirect if necessary
|
|
|
|
if canonical := pathutil.Clean(Pkg + path) + "/"; r.Url.Path != canonical {
|
2009-06-16 10:14:06 -06:00
|
|
|
http.Redirect(c, canonical, http.StatusMovedPermanently);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
info := getPageInfo(path);
|
2009-06-16 10:14:06 -06:00
|
|
|
|
2009-06-29 16:24:23 -06:00
|
|
|
var buf bytes.Buffer;
|
2009-06-16 10:14:06 -06:00
|
|
|
if false { // TODO req.Params["format"] == "text"
|
2009-08-03 10:53:00 -06:00
|
|
|
if err := packageText.Execute(info, &buf); err != nil {
|
2009-06-16 10:14:06 -06:00
|
|
|
log.Stderrf("packageText.Execute: %s", err);
|
|
|
|
}
|
2009-09-16 16:15:00 -06:00
|
|
|
serveText(c, buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-08-03 10:53:00 -06:00
|
|
|
if err := packageHtml.Execute(info, &buf); err != nil {
|
2009-06-16 10:14:06 -06:00
|
|
|
log.Stderrf("packageHtml.Execute: %s", err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if path == "" {
|
|
|
|
path = "."; // don't display an empty path
|
|
|
|
}
|
2009-10-01 15:08:00 -06:00
|
|
|
title := "Directory " + path;
|
|
|
|
if info.PDoc != nil {
|
|
|
|
title = "Package " + info.PDoc.PackageName;
|
|
|
|
}
|
|
|
|
|
|
|
|
servePage(c, title, buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2009-06-29 16:24:23 -06:00
|
|
|
var buf bytes.Buffer;
|
2009-06-16 10:14:06 -06:00
|
|
|
io.Copy(r, &buf);
|
|
|
|
wait, err := os.Wait(pid, 0);
|
|
|
|
if err != nil {
|
2009-09-16 16:15:00 -06:00
|
|
|
os.Stderr.Write(buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
log.Stderrf("os.Wait(%d, 0): %v\n", pid, err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if !wait.Exited() || wait.ExitStatus() != 0 {
|
2009-09-16 16:15:00 -06:00
|
|
|
os.Stderr.Write(buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
log.Stderrf("executing %v failed (exit status = %d)", args, wait.ExitStatus());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if *verbose {
|
2009-09-16 16:15:00 -06:00
|
|
|
os.Stderr.Write(buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
if c != nil {
|
|
|
|
c.SetHeader("content-type", "text/plain; charset=utf-8");
|
2009-09-16 16:15:00 -06:00
|
|
|
c.Write(buf.Bytes());
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-08-19 18:27:08 -06:00
|
|
|
func dosync(c *http.Conn, r *http.Request) {
|
2009-06-16 10:14:06 -06:00
|
|
|
args := []string{"/bin/sh", "-c", *syncCmd};
|
2009-08-28 12:28:05 -06:00
|
|
|
if exec(c, args) {
|
|
|
|
// sync succeeded
|
|
|
|
syncTime.set();
|
|
|
|
syncDelay.set(*syncMin); // revert to regular sync schedule
|
|
|
|
} else {
|
|
|
|
// sync failed - back off exponentially, but try at least once a day
|
|
|
|
syncDelay.backoff(24*60);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func usage() {
|
|
|
|
fmt.Fprintf(os.Stderr,
|
|
|
|
"usage: godoc package [name ...]\n"
|
|
|
|
" godoc -http=:6060\n"
|
|
|
|
);
|
|
|
|
flag.PrintDefaults();
|
2009-07-16 18:11:18 -06:00
|
|
|
os.Exit(2);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func main() {
|
2009-07-16 18:11:18 -06:00
|
|
|
flag.Usage = usage;
|
2009-06-16 10:14:06 -06:00
|
|
|
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 != "" {
|
2009-08-19 18:27:08 -06:00
|
|
|
http.Handle("/debug/sync", http.HandlerFunc(dosync));
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
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 {
|
2009-08-28 12:28:05 -06:00
|
|
|
syncDelay.set(*syncMin); // initial sync delay
|
2009-06-16 10:14:06 -06:00
|
|
|
go func() {
|
2009-08-28 12:28:05 -06:00
|
|
|
for {
|
2009-08-19 18:27:08 -06:00
|
|
|
dosync(nil, nil);
|
2009-08-28 12:28:05 -06:00
|
|
|
if *verbose {
|
|
|
|
log.Stderrf("next sync in %dmin", syncDelay.get());
|
|
|
|
}
|
|
|
|
time.Sleep(int64(syncDelay.get()) * (60 * 1e9));
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
}();
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := http.ListenAndServe(*httpaddr, handler); err != nil {
|
|
|
|
log.Exitf("ListenAndServe %s: %v", *httpaddr, err)
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if *html {
|
|
|
|
packageText = packageHtml;
|
|
|
|
parseerrorText = parseerrorHtml;
|
|
|
|
}
|
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
info := getPageInfo(flag.Arg(0));
|
2009-06-16 10:14:06 -06:00
|
|
|
|
2009-07-29 18:01:09 -06:00
|
|
|
if info.PDoc != nil && flag.NArg() > 1 {
|
2009-06-16 10:14:06 -06:00
|
|
|
args := flag.Args();
|
2009-07-29 18:01:09 -06:00
|
|
|
info.PDoc.Filter(args[1 : len(args)]);
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|
|
|
|
|
2009-08-03 10:53:00 -06:00
|
|
|
if err := packageText.Execute(info, os.Stdout); err != nil {
|
|
|
|
log.Stderrf("packageText.Execute: %s", err);
|
|
|
|
}
|
2009-06-16 10:14:06 -06:00
|
|
|
}
|