mirror of
				https://github.com/golang/go.git
				synced 2025-10-31 00:30:57 +00:00 
			
		
		
		
	 1f6463f823
			
		
	
	
		1f6463f823
		
	
	
	
	
		
			
			import ( "vector" -> "container/vector" "ast" -> "go/ast" "sha1" -> "hash/sha1" etc. ) and update Makefiles. Because I did the conversion semi-automatically, I sorted all the import blocks as a post-processing. Some files have therefore changed that didn't strictly need to. Rename local packages to lower case. The upper/lower distinction doesn't work on OS X and complicates the "single-package directories with the same package name as directory name" heuristic used by gobuild and godoc to create the correlation between source and binary locations. Now that we have a plan to avoid globally unique names, the upper/lower is unnecessary. The renamings will cause trouble for a few users, but so will the change in import paths. This way, the two maintenance fixes are rolled into one inconvenience. R=r OCL=27573 CL=27575
		
			
				
	
	
		
			778 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			778 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 proto
 | |
| //	godoc compress/zlib Cipher NewCMAC
 | |
| //		- prints doc for Cipher and NewCMAC in package crypto/block
 | |
| 
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"bufio";
 | |
| 	"container/vector";
 | |
| 	"flag";
 | |
| 	"fmt";
 | |
| 	"go/ast";
 | |
| 	"go/parser";
 | |
| 	"go/token";
 | |
| 	"http";
 | |
| 	"io";
 | |
| 	"log";
 | |
| 	"net";
 | |
| 	"once";
 | |
| 	"os";
 | |
| 	pathutil "path";
 | |
| 	"sort";
 | |
| 	"strings";
 | |
| 	"tabwriter";
 | |
| 	"template";
 | |
| 	"time";
 | |
| 
 | |
| 	"astprinter";
 | |
| 	"comment";
 | |
| 	"docprinter";	// TODO: "doc"
 | |
| )
 | |
| 
 | |
| 
 | |
| // TODO
 | |
| // - uniform use of path, filename, dirname, pakname, etc.
 | |
| // - fix weirdness with double-/'s in paths
 | |
| // - split http service into its own source file
 | |
| 
 | |
| // TODO: tell flag package about usage string
 | |
| const usageString =
 | |
| 	"usage: godoc package [name ...]\n"
 | |
| 	"	godoc -http=:6060\n"
 | |
| 
 | |
| var (
 | |
| 	goroot string;
 | |
| 
 | |
| 	verbose = flag.Bool("v", false, "verbose mode");
 | |
| 
 | |
| 	// server control
 | |
| 	httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')");
 | |
| 
 | |
| 	// layout control
 | |
| 	tabwidth = flag.Int("tabwidth", 4, "tab width");
 | |
| 	usetabs = flag.Bool("tabs", false, "align with tabs instead of spaces");
 | |
| 
 | |
| 	html = flag.Bool("html", false, "print HTML in command-line mode");
 | |
| 
 | |
| 	pkgroot = flag.String("pkgroot", "src/lib", "root package source directory (if unrooted, relative to goroot)");
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	Pkg = "/pkg/"	// name for auto-generated package documentation tree
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	var err *os.Error;
 | |
| 	goroot, err = os.Getenv("GOROOT");
 | |
| 	if err != nil {
 | |
| 		goroot = "/home/r/go-build/go";
 | |
| 	}
 | |
| 	flag.StringVar(&goroot, "goroot", goroot, "Go root directory");
 | |
| }
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Support
 | |
| 
 | |
| func isGoFile(dir *os.Dir) bool {
 | |
| 	return dir.IsRegular() && strings.HasSuffix(dir.Name, ".go");
 | |
| }
 | |
| 
 | |
| 
 | |
| func isDir(name string) bool {
 | |
| 	d, err := os.Stat(name);
 | |
| 	return err == nil && d.IsDirectory();
 | |
| }
 | |
| 
 | |
| 
 | |
| func makeTabwriter(writer io.Write) *tabwriter.Writer {
 | |
| 	padchar := byte(' ');
 | |
| 	if *usetabs {
 | |
| 		padchar = '\t';
 | |
| 	}
 | |
| 	return tabwriter.NewWriter(writer, *tabwidth, 1, padchar, tabwriter.FilterHTML);
 | |
| }
 | |
| 
 | |
| 
 | |
| // TODO(rsc): this belongs in a library somewhere, maybe os
 | |
| func ReadFile(name string) ([]byte, *os.Error) {
 | |
| 	f, err := os.Open(name, os.O_RDONLY, 0);
 | |
| 	if err != nil {
 | |
| 		return nil, err;
 | |
| 	}
 | |
| 	defer f.Close();
 | |
| 	var b io.ByteBuffer;
 | |
| 	if n, err := io.Copy(f, &b); err != nil {
 | |
| 		return nil, err;
 | |
| 	}
 | |
| 	return b.Data(), nil;
 | |
| }
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Parsing
 | |
| 
 | |
| type rawError struct {
 | |
| 	pos token.Position;
 | |
| 	msg string;
 | |
| }
 | |
| 
 | |
| type rawErrorVector struct {
 | |
| 	vector.Vector;
 | |
| }
 | |
| 
 | |
| func (v *rawErrorVector) At(i int) rawError { return v.Vector.At(i).(rawError) }
 | |
| func (v *rawErrorVector) Less(i, j int) bool { return v.At(i).pos.Offset < v.At(j).pos.Offset; }
 | |
| 
 | |
| func (v *rawErrorVector) Error(pos token.Position, msg string) {
 | |
| 	// only collect errors that are on a new line
 | |
| 	// in the hope to avoid most follow-up errors
 | |
| 	lastLine := 0;
 | |
| 	if n := v.Len(); n > 0 {
 | |
| 		lastLine = v.At(n - 1).pos.Line;
 | |
| 	}
 | |
| 	if lastLine != pos.Line {
 | |
| 		v.Push(rawError{pos, msg});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // 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.
 | |
| //
 | |
| func parse(filename string, mode uint) (*ast.Program, *parseErrors) {
 | |
| 	src, err := ReadFile(filename);
 | |
| 	if err != nil {
 | |
| 		log.Stderrf("ReadFile %s: %v", filename, err);
 | |
| 		errs := []parseError{parseError{nil, 0, err.String()}};
 | |
| 		return nil, &parseErrors{filename, errs, nil};
 | |
| 	}
 | |
| 
 | |
| 	var raw rawErrorVector;
 | |
| 	prog, ok := parser.Parse(src, &raw, mode);
 | |
| 	if !ok {
 | |
| 		// sort and convert error list
 | |
| 		sort.Sort(&raw);
 | |
| 		errs := make([]parseError, raw.Len() + 1);	// +1 for final fragment of source
 | |
| 		offs := 0;
 | |
| 		for i := 0; i < raw.Len(); i++ {
 | |
| 			r := raw.At(i);
 | |
| 			// 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[raw.Len()].src = src[offs : len(src)];
 | |
| 		return nil, &parseErrors{filename, errs, src};
 | |
| 	}
 | |
| 
 | |
| 	return prog, nil;
 | |
| }
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Templates
 | |
| 
 | |
| // Return text for decl.
 | |
| func DeclText(d ast.Decl) []byte {
 | |
| 	var b io.ByteBuffer;
 | |
| 	var p astPrinter.Printer;
 | |
| 	p.Init(&b, nil, nil, false);
 | |
| 	d.Visit(&p);
 | |
| 	return b.Data();
 | |
| }
 | |
| 
 | |
| 
 | |
| // Return text for expr.
 | |
| func ExprText(d ast.Expr) []byte {
 | |
| 	var b io.ByteBuffer;
 | |
| 	var p astPrinter.Printer;
 | |
| 	p.Init(&b, nil, nil, false);
 | |
| 	d.Visit(&p);
 | |
| 	return b.Data();
 | |
| }
 | |
| 
 | |
| 
 | |
| // Convert x, whatever it is, to text form.
 | |
| func toText(x interface{}) []byte {
 | |
| 	type String interface { String() string }
 | |
| 
 | |
| 	switch v := x.(type) {
 | |
| 	case []byte:
 | |
| 		return v;
 | |
| 	case string:
 | |
| 		return io.StringBytes(v);
 | |
| 	case String:
 | |
| 		return io.StringBytes(v.String());
 | |
| 	case ast.Decl:
 | |
| 		return DeclText(v);
 | |
| 	case ast.Expr:
 | |
| 		return ExprText(v);
 | |
| 	}
 | |
| 	var b io.ByteBuffer;
 | |
| 	fmt.Fprint(&b, x);
 | |
| 	return b.Data();
 | |
| }
 | |
| 
 | |
| 
 | |
| // Template formatter for "html" format.
 | |
| func htmlFmt(w io.Write, x interface{}, format string) {
 | |
| 	// Can do better than text in some cases.
 | |
| 	switch v := x.(type) {
 | |
| 	case ast.Decl:
 | |
| 		var p astPrinter.Printer;
 | |
| 		tw := makeTabwriter(w);
 | |
| 		p.Init(tw, nil, nil, true);
 | |
| 		v.Visit(&p);
 | |
| 		tw.Flush();
 | |
| 	case ast.Expr:
 | |
| 		var p astPrinter.Printer;
 | |
| 		tw := makeTabwriter(w);
 | |
| 		p.Init(tw, nil, nil, true);
 | |
| 		v.Visit(&p);
 | |
| 		tw.Flush();
 | |
| 	default:
 | |
| 		template.HtmlEscape(w, toText(x));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // Template formatter for "html-comment" format.
 | |
| func htmlCommentFmt(w io.Write, x interface{}, format string) {
 | |
| 	comment.ToHtml(w, toText(x));
 | |
| }
 | |
| 
 | |
| 
 | |
| // Template formatter for "" (default) format.
 | |
| func textFmt(w io.Write, x interface{}, format string) {
 | |
| 	w.Write(toText(x));
 | |
| }
 | |
| 
 | |
| 
 | |
| // Template formatter for "dir/" format.
 | |
| // Writes out "/" if the os.Dir argument is a directory.
 | |
| var slash = io.StringBytes("/");
 | |
| 
 | |
| func dirSlashFmt(w io.Write, x interface{}, format string) {
 | |
| 	d := x.(os.Dir);	// TODO(rsc): want *os.Dir
 | |
| 	if d.IsDirectory() {
 | |
| 		w.Write(slash);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| var fmap = template.FormatterMap{
 | |
| 	"": textFmt,
 | |
| 	"html": htmlFmt,
 | |
| 	"html-comment": htmlCommentFmt,
 | |
| 	"dir/": dirSlashFmt,
 | |
| }
 | |
| 
 | |
| 
 | |
| // TODO: const templateDir = "lib/godoc"
 | |
| const templateDir = "usr/gri/pretty"
 | |
| 
 | |
| func ReadTemplate(name string) *template.Template {
 | |
| 	data, err := ReadFile(templateDir + "/" + name);
 | |
| 	if err != nil {
 | |
| 		log.Exitf("ReadFile %s: %v", name, err);
 | |
| 	}
 | |
| 	t, err1, line := template.Parse(string(data), fmap);
 | |
| 	if err1 != nil {
 | |
| 		log.Exitf("%s:%d: %v", name, line, err);
 | |
| 	}
 | |
| 	return t;
 | |
| }
 | |
| 
 | |
| 
 | |
| var godocHtml *template.Template
 | |
| var packageHtml *template.Template
 | |
| var packageText *template.Template
 | |
| var packagelistHtml *template.Template;
 | |
| var packagelistText *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");
 | |
| 	packagelistHtml = ReadTemplate("packagelist.html");
 | |
| 	packagelistText = ReadTemplate("packagelist.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{};
 | |
| 		header interface{};
 | |
| 		timestamp string;
 | |
| 		content interface{};
 | |
| 	}
 | |
| 
 | |
| 	var d Data;
 | |
| 	d.title = title;
 | |
| 	d.header = title;
 | |
| 	d.timestamp = time.UTC().String();
 | |
| 	d.content = content;
 | |
| 	godocHtml.Execute(&d, c);
 | |
| }
 | |
| 
 | |
| 
 | |
| func serveText(c *http.Conn, text []byte) {
 | |
| 	c.SetHeader("content-type", "text/plain; charset=utf-8");
 | |
| 	c.Write(text);
 | |
| }
 | |
| 
 | |
| 
 | |
| func serveError(c *http.Conn, err, arg string) {
 | |
| 	servePage(c, "Error", fmt.Sprintf("%v (%s)\n", err, arg));
 | |
| }
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Files
 | |
| 
 | |
| func serveParseErrors(c *http.Conn, errors *parseErrors) {
 | |
| 	// format errors
 | |
| 	var b io.ByteBuffer;
 | |
| 	parseerrorHtml.Execute(errors, &b);
 | |
| 	servePage(c, errors.filename + " - Parse Errors", b.Data());
 | |
| }
 | |
| 
 | |
| 
 | |
| func serveGoSource(c *http.Conn, name string) {
 | |
| 	prog, errors := parse(name, parser.ParseComments);
 | |
| 	if errors != nil {
 | |
| 		serveParseErrors(c, errors);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	var b io.ByteBuffer;
 | |
| 	fmt.Fprintln(&b, "<pre>");
 | |
| 	var p astPrinter.Printer;
 | |
| 	writer := makeTabwriter(&b);  // for nicely formatted output
 | |
| 	p.Init(writer, nil, nil, true);
 | |
| 	p.DoProgram(prog);
 | |
| 	writer.Flush();  // ignore errors
 | |
| 	fmt.Fprintln(&b, "</pre>");
 | |
| 
 | |
| 	servePage(c, name + " - Go source", b.Data());
 | |
| }
 | |
| 
 | |
| 
 | |
| var fileServer = http.FileServer(".", "");
 | |
| 
 | |
| func serveFile(c *http.Conn, req *http.Request) {
 | |
| 	// pick off special cases and hand the rest to the standard file server
 | |
| 	switch {
 | |
| 	case req.Url.Path == "/":
 | |
| 		// serve landing page.
 | |
| 		// TODO: hide page from ordinary file serving.
 | |
| 		// writing doc/index.html will take care of that.
 | |
| 		http.ServeFile(c, req, "doc/root.html");
 | |
| 
 | |
| 	case req.Url.Path == "/doc/root.html":
 | |
| 		// hide landing page from its real name
 | |
| 		http.NotFound(c, req);
 | |
| 
 | |
| 	case pathutil.Ext(req.Url.Path) == ".go":
 | |
| 		serveGoSource(c, req.Url.Path[1:len(req.Url.Path)]);
 | |
| 
 | |
| 	default:
 | |
| 		fileServer.ServeHTTP(c, req);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Packages
 | |
| 
 | |
| type pakDesc struct {
 | |
| 	dirname string;  // relative to goroot
 | |
| 	pakname string;  // relative to directory
 | |
| 	importpath string;	// import "___"
 | |
| 	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]; }
 | |
| 
 | |
| 
 | |
| func addFile(pmap map[string]*pakDesc, dirname, filename, importprefix string) {
 | |
| 	if strings.HasSuffix(filename, "_test.go") {
 | |
| 		// ignore package tests
 | |
| 		return;
 | |
| 	}
 | |
| 	// determine package name
 | |
| 	path := dirname + "/" + filename;
 | |
| 	prog, errors := parse(path, parser.PackageClauseOnly);
 | |
| 	if prog == nil {
 | |
| 		return;
 | |
| 	}
 | |
| 	if prog.Name.Value == "main" {
 | |
| 		// ignore main packages for now
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	var importpath string;
 | |
| 	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) {
 | |
| 	path := dirname;
 | |
| 	fd, err1 := os.Open(path, os.O_RDONLY, 0);
 | |
| 	if err1 != nil {
 | |
| 		log.Stderrf("open %s: %v", path, err1);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	list, err2 := fd.Readdir(-1);
 | |
| 	if err2 != nil {
 | |
| 		log.Stderrf("readdir %s: %v", path, err2);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	nsub := 0;
 | |
| 	for i, entry := range list {
 | |
| 		switch {
 | |
| 		case isGoFile(&entry):
 | |
| 			addFile(pmap, dirname, entry.Name, importprefix);
 | |
| 		case entry.IsDirectory():
 | |
| 			nsub++;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if subdirs != nil && nsub > 0 {
 | |
| 		*subdirs = make([]os.Dir, nsub);
 | |
| 		nsub = 0;
 | |
| 		for i, entry := range list {
 | |
| 			if entry.IsDirectory() {
 | |
| 				subdirs[nsub] = entry;
 | |
| 				nsub++;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| func mapValues(pmap map[string]*pakDesc) pakArray {
 | |
| 	// build sorted package list
 | |
| 	plist := make(pakArray, len(pmap));
 | |
| 	i := 0;
 | |
| 	for tmp, pakdesc := range pmap {
 | |
| 		plist[i] = pakdesc;
 | |
| 		i++;
 | |
| 	}
 | |
| 	sort.Sort(plist);
 | |
| 	return plist;
 | |
| }
 | |
| 
 | |
| 
 | |
| func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) {
 | |
| 	// compute documentation
 | |
| 	var r doc.DocReader;
 | |
| 	i := 0;
 | |
| 	for filename := range p.filenames {
 | |
| 		path := p.dirname + "/" + filename;
 | |
| 		prog, err := parse(path, parser.ParseComments);
 | |
| 		if err != nil {
 | |
| 			return nil, err;
 | |
| 		}
 | |
| 
 | |
| 		if i == 0 {
 | |
| 			// first file - initialize doc
 | |
| 			r.Init(prog.Name.Value, p.importpath);
 | |
| 		}
 | |
| 		i++;
 | |
| 		r.AddProgram(prog);
 | |
| 	}
 | |
| 	return r.Doc(), nil;
 | |
| }
 | |
| 
 | |
| 
 | |
| func servePackage(c *http.Conn, p *pakDesc) {
 | |
| 	doc, errors := p.Doc();
 | |
| 	if errors != nil {
 | |
| 		serveParseErrors(c, errors);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	var b io.ByteBuffer;
 | |
| 	if false {	// TODO req.Params["format"] == "text"
 | |
| 		err := packageText.Execute(doc, &b);
 | |
| 		if err != nil {
 | |
| 			log.Stderrf("packageText.Execute: %s", err);
 | |
| 		}
 | |
| 		serveText(c, b.Data());
 | |
| 		return;
 | |
| 	}
 | |
| 	err := packageHtml.Execute(doc, &b);
 | |
| 	if err != nil {
 | |
| 		log.Stderrf("packageHtml.Execute: %s", err);
 | |
| 	}
 | |
| 	servePage(c, doc.ImportPath + " - Go package documentation", b.Data());
 | |
| }
 | |
| 
 | |
| 
 | |
| type pakInfo struct {
 | |
| 	Path string;
 | |
| 	Package *pakDesc;
 | |
| 	Packages pakArray;
 | |
| 	Subdirs []os.Dir;	// TODO(rsc): []*os.Dir
 | |
| }
 | |
| 
 | |
| 
 | |
| func servePackageList(c *http.Conn, info *pakInfo) {
 | |
| 	var b io.ByteBuffer;
 | |
| 	err := packagelistHtml.Execute(info, &b);
 | |
| 	if err != nil {
 | |
| 		log.Stderrf("packagelistHtml.Execute: %s", err);
 | |
| 	}
 | |
| 	servePage(c, info.Path + " - Go packages", b.Data());
 | |
| }
 | |
| 
 | |
| 
 | |
| // 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) *pakInfo {
 | |
| 	info := new(pakInfo);
 | |
| 
 | |
| 	// 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);
 | |
| 	cname := pathutil.Clean(name);
 | |
| 	if cname == "" {
 | |
| 		cname = "."
 | |
| 	}
 | |
| 	dir := pathutil.Join(*pkgroot, cname);
 | |
| 	url := pathutil.Join(Pkg, cname);
 | |
| 	if isDir(dir) {
 | |
| 		parent, pak := pathutil.Split(dir);
 | |
| 		addDirectory(pmap, dir, cname, &info.Subdirs);
 | |
| 		paks := mapValues(pmap);
 | |
| 		if len(paks) == 1 {
 | |
| 			p := paks[0];
 | |
| 			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 dir.
 | |
| 	parentdir, pak := pathutil.Split(dir);
 | |
| 	parentname, nam := 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) {
 | |
| 	path := r.Url.Path;
 | |
| 	path = path[len(Pkg) : len(path)];
 | |
| 	info := findPackages(path);
 | |
| 	if r.Url.Path != Pkg + info.Path {
 | |
| 		http.Redirect(c, info.Path);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if info.Package != nil {
 | |
| 		servePackage(c, info.Package);
 | |
| 	} else {
 | |
| 		servePackageList(c, info);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Server
 | |
| 
 | |
| func LoggingHandler(h http.Handler) http.Handler {
 | |
| 	return http.HandlerFunc(func(c *http.Conn, req *http.Request) {
 | |
| 		log.Stderrf("%s\t%s", req.Host, req.Url.Path);
 | |
| 		h.ServeHTTP(c, req);
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 
 | |
| func usage() {
 | |
| 	fmt.Fprintf(os.Stderr, usageString);
 | |
| 	sys.Exit(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| func main() {
 | |
| 	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);
 | |
| 			handler = LoggingHandler(handler);
 | |
| 		}
 | |
| 
 | |
| 		http.Handle(Pkg, http.HandlerFunc(servePkg));
 | |
| 		http.Handle("/", http.HandlerFunc(serveFile));
 | |
| 
 | |
| 		if err := http.ListenAndServe(*httpaddr, handler); err != nil {
 | |
| 			log.Exitf("ListenAndServe %s: %v", *httpaddr, err)
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if *html {
 | |
| 		packageText = packageHtml;
 | |
| 		packagelistText = packagelistHtml;
 | |
| 		parseerrorText = parseerrorHtml;
 | |
| 	}
 | |
| 
 | |
| 	info := findPackages(flag.Arg(0));
 | |
| 	if info.Package == nil {
 | |
| 		err := packagelistText.Execute(info, os.Stderr);
 | |
| 		if err != nil {
 | |
| 			log.Stderrf("packagelistText.Execute: %s", err);
 | |
| 		}
 | |
| 		sys.Exit(1);
 | |
| 	}
 | |
| 
 | |
| 	doc, errors := info.Package.Doc();
 | |
| 	if errors != nil {
 | |
| 		err := parseerrorText.Execute(errors, os.Stderr);
 | |
| 		if err != nil {
 | |
| 			log.Stderrf("parseerrorText.Execute: %s", err);
 | |
| 		}
 | |
| 		sys.Exit(1);
 | |
| 	}
 | |
| 
 | |
| 	if flag.NArg() > 1 {
 | |
| 		args := flag.Args();
 | |
| 		doc.Filter(args[1:len(args)]);
 | |
| 	}
 | |
| 
 | |
| 	packageText.Execute(doc, os.Stdout);
 | |
| }
 |