mirror of
https://github.com/golang/go
synced 2024-11-22 00:34:40 -07:00
support for command documentation:
- made package tree handler generic so it can work on any fs tree - cleanups along the way R=rsc CC=r http://go/go-review/1017020
This commit is contained in:
parent
dc62c66c73
commit
4818d346b6
@ -1,5 +1,5 @@
|
|||||||
<table class="layout">
|
<table class="layout">
|
||||||
<tr><td colspan="2"><a href="/pkg/{Path|html}">{Name|html}</a></td></tr>
|
<tr><td colspan="2"><a href="{Path|path}">{Name|html}</a></td></tr>
|
||||||
{.repeated section Dirs}
|
{.repeated section Dirs}
|
||||||
<tr><td width="25em"></td><td>{@|dir}</td></tr>
|
<tr><td width="25em"></td><td>{@|dir}</td></tr>
|
||||||
{.end}
|
{.end}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
<li class="blank"> </li>
|
<li class="blank"> </li>
|
||||||
<li class="navhead">Programming</li>
|
<li class="navhead">Programming</li>
|
||||||
|
<li><a href="/cmd" class="noline">Command documentation</a></li>
|
||||||
<li><a href="/pkg" class="noline">Package documentation</a></li>
|
<li><a href="/pkg" class="noline">Package documentation</a></li>
|
||||||
|
|
||||||
<li class="blank"> </li>
|
<li class="blank"> </li>
|
||||||
|
@ -6,8 +6,11 @@
|
|||||||
|
|
||||||
{.section PDoc}
|
{.section PDoc}
|
||||||
<!-- PackageName is printed as title by the top-level template -->
|
<!-- PackageName is printed as title by the top-level template -->
|
||||||
|
{.section IsPkg}
|
||||||
<p><code>import "{ImportPath|html}"</code></p>
|
<p><code>import "{ImportPath|html}"</code></p>
|
||||||
|
{.end}
|
||||||
{Doc|html-comment}
|
{Doc|html-comment}
|
||||||
|
{.section IsPkg}
|
||||||
{.section Filenames}
|
{.section Filenames}
|
||||||
<p>
|
<p>
|
||||||
<h4>Package files</h4>
|
<h4>Package files</h4>
|
||||||
@ -18,6 +21,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
{.end}
|
{.end}
|
||||||
|
{.end}
|
||||||
{.section Consts}
|
{.section Consts}
|
||||||
<h2>Constants</h2>
|
<h2>Constants</h2>
|
||||||
{.repeated section @}
|
{.repeated section @}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
{.section PDoc}
|
{.section PDoc}
|
||||||
|
{.section IsPkg}
|
||||||
PACKAGE
|
PACKAGE
|
||||||
|
|
||||||
package {PackageName}
|
package {PackageName}
|
||||||
import "{ImportPath}"
|
import "{ImportPath}"
|
||||||
|
{.or}
|
||||||
|
COMMAND DOCUMENTATION
|
||||||
|
{.end}
|
||||||
{.section Doc}
|
{.section Doc}
|
||||||
|
|
||||||
{@}
|
{@}
|
||||||
|
@ -26,9 +26,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const Pkg = "/pkg/" // name for auto-generated package documentation tree
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Support types
|
// Support types
|
||||||
|
|
||||||
@ -80,6 +77,7 @@ var (
|
|||||||
|
|
||||||
// file system roots
|
// file system roots
|
||||||
goroot string;
|
goroot string;
|
||||||
|
cmdroot = flag.String("cmdroot", "src/cmd", "root command source directory (if unrooted, relative to goroot)");
|
||||||
pkgroot = flag.String("pkgroot", "src/pkg", "root package source directory (if unrooted, relative to goroot)");
|
pkgroot = flag.String("pkgroot", "src/pkg", "root package source directory (if unrooted, relative to goroot)");
|
||||||
tmplroot = flag.String("tmplroot", "lib/godoc", "root template directory (if unrooted, relative to goroot)");
|
tmplroot = flag.String("tmplroot", "lib/godoc", "root template directory (if unrooted, relative to goroot)");
|
||||||
|
|
||||||
@ -88,6 +86,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var fsTree RWValue; // *Directory tree of packages, updated with each sync
|
||||||
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
goroot = os.Getenv("GOROOT");
|
goroot = os.Getenv("GOROOT");
|
||||||
if goroot == "" {
|
if goroot == "" {
|
||||||
@ -118,6 +119,15 @@ func isPkgDir(dir *os.Dir) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func pkgName(filename string) string {
|
||||||
|
file, err := parse(filename, parser.PackageClauseOnly);
|
||||||
|
if err != nil || file == nil {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return file.Name.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func htmlEscape(s string) string {
|
func htmlEscape(s string) string {
|
||||||
var buf bytes.Buffer;
|
var buf bytes.Buffer;
|
||||||
template.HtmlEscape(&buf, strings.Bytes(s));
|
template.HtmlEscape(&buf, strings.Bytes(s));
|
||||||
@ -129,7 +139,7 @@ func htmlEscape(s string) string {
|
|||||||
// Package directories
|
// Package directories
|
||||||
|
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
Path string; // relative to *pkgroot, includes Name
|
Path string; // includes Name
|
||||||
Name string;
|
Name string;
|
||||||
Dirs []*Directory;
|
Dirs []*Directory;
|
||||||
}
|
}
|
||||||
@ -143,8 +153,7 @@ func newDirTree(path, name string, depth int) *Directory {
|
|||||||
return &Directory{path, name, nil};
|
return &Directory{path, name, nil};
|
||||||
}
|
}
|
||||||
|
|
||||||
fullpath := pathutil.Join(*pkgroot, path);
|
list, _ := io.ReadDir(path); // ignore errors
|
||||||
list, _ := io.ReadDir(fullpath); // ignore errors
|
|
||||||
|
|
||||||
// determine number of subdirectories and package files
|
// determine number of subdirectories and package files
|
||||||
ndirs := 0;
|
ndirs := 0;
|
||||||
@ -186,13 +195,12 @@ func newDirTree(path, name string, depth int) *Directory {
|
|||||||
|
|
||||||
|
|
||||||
// newDirectory creates a new package directory tree with at most depth
|
// newDirectory creates a new package directory tree with at most depth
|
||||||
// levels, anchored at root which is relative to Pkg. The result tree
|
// levels, anchored at root which is relative to goroot. The result tree
|
||||||
// only contains directories that contain package files or that contain
|
// only contains directories that contain package files or that contain
|
||||||
// subdirectories containing package files (transitively).
|
// subdirectories containing package files (transitively).
|
||||||
//
|
//
|
||||||
func newDirectory(root string, depth int) *Directory {
|
func newDirectory(root string, depth int) *Directory {
|
||||||
fullpath := pathutil.Join(*pkgroot, root);
|
d, err := os.Lstat(root);
|
||||||
d, err := os.Lstat(fullpath);
|
|
||||||
if err != nil || !isPkgDir(d) {
|
if err != nil || !isPkgDir(d) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@ -415,6 +423,23 @@ func dirFmt(w io.Writer, x interface{}, format string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func removePrefix(s, prefix string) string {
|
||||||
|
if strings.HasPrefix(s, prefix) {
|
||||||
|
return s[len(prefix) : len(s)];
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Template formatter for "path" format.
|
||||||
|
func pathFmt(w io.Writer, x interface{}, format string) {
|
||||||
|
// TODO(gri): Need to find a better solution for this.
|
||||||
|
// This will not work correctly if *cmdroot
|
||||||
|
// or *pkgroot change.
|
||||||
|
writeAny(w, removePrefix(x.(string), "src"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Template formatter for "link" format.
|
// Template formatter for "link" format.
|
||||||
func linkFmt(w io.Writer, x interface{}, format string) {
|
func linkFmt(w io.Writer, x interface{}, format string) {
|
||||||
type Positioner interface {
|
type Positioner interface {
|
||||||
@ -481,6 +506,7 @@ var fmap = template.FormatterMap{
|
|||||||
"html": htmlFmt,
|
"html": htmlFmt,
|
||||||
"html-comment": htmlCommentFmt,
|
"html-comment": htmlCommentFmt,
|
||||||
"dir": dirFmt,
|
"dir": dirFmt,
|
||||||
|
"path": pathFmt,
|
||||||
"link": linkFmt,
|
"link": linkFmt,
|
||||||
"infoClass": infoClassFmt,
|
"infoClass": infoClassFmt,
|
||||||
"infoLine": infoLineFmt,
|
"infoLine": infoLineFmt,
|
||||||
@ -528,8 +554,6 @@ func readTemplates() {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Generic HTML wrapper
|
// Generic HTML wrapper
|
||||||
|
|
||||||
var pkgTree RWValue; // *Directory tree of packages, updated with each sync
|
|
||||||
|
|
||||||
func servePage(c *http.Conn, title, query string, content []byte) {
|
func servePage(c *http.Conn, title, query string, content []byte) {
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Title string;
|
Title string;
|
||||||
@ -538,7 +562,7 @@ func servePage(c *http.Conn, title, query string, content []byte) {
|
|||||||
Content []byte;
|
Content []byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ts := pkgTree.get();
|
_, ts := fsTree.get();
|
||||||
d := Data{
|
d := Data{
|
||||||
Title: title,
|
Title: title,
|
||||||
Timestamp: time.SecondsToLocalTime(ts).String(),
|
Timestamp: time.SecondsToLocalTime(ts).String(),
|
||||||
@ -658,18 +682,21 @@ func serveFile(c *http.Conn, r *http.Request) {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Packages
|
// Packages
|
||||||
|
|
||||||
func pkgName(filename string) string {
|
// Package name used for commands that have non-identifier names.
|
||||||
file, err := parse(filename, parser.PackageClauseOnly);
|
const fakePkgName = "documentation"
|
||||||
if err != nil || file == nil {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return file.Name.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
PDoc *doc.PackageDoc; // nil if no package found
|
PDoc *doc.PackageDoc; // nil if no package found
|
||||||
Dirs *Directory; // nil if no directory information found
|
Dirs *Directory; // nil if no directory information found
|
||||||
|
IsPkg bool; // false if this is not documenting a real package
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type httpHandler struct {
|
||||||
|
pattern string; // url pattern; e.g. "/pkg/"
|
||||||
|
fsRoot string; // file system root to which the pattern is mapped
|
||||||
|
isPkg bool; // true if this handler serves real package documentation (as opposed to command documentation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -678,11 +705,12 @@ type PageInfo struct {
|
|||||||
// PageInfo.PDoc is nil. If there are no subdirectories,
|
// PageInfo.PDoc is nil. If there are no subdirectories,
|
||||||
// PageInfo.Dirs is nil.
|
// PageInfo.Dirs is nil.
|
||||||
//
|
//
|
||||||
func getPageInfo(path string) PageInfo {
|
func (h *httpHandler) getPageInfo(path string) PageInfo {
|
||||||
// the path is relative to *pkgroot
|
// the path is relative to h.fsroot
|
||||||
dirname := pathutil.Join(*pkgroot, path);
|
dirname := pathutil.Join(h.fsRoot, path);
|
||||||
|
|
||||||
// the package name is the directory name within its parent
|
// the package name is the directory name within its parent
|
||||||
|
// (use dirname instead of path because dirname is clean; i.e. has no trailing '/')
|
||||||
_, pkgname := pathutil.Split(dirname);
|
_, pkgname := pathutil.Split(dirname);
|
||||||
|
|
||||||
// filter function to select the desired .go files
|
// filter function to select the desired .go files
|
||||||
@ -692,7 +720,10 @@ func getPageInfo(path string) PageInfo {
|
|||||||
// files that belong to the expected package so that
|
// files that belong to the expected package so that
|
||||||
// parser.ParsePackage doesn't return "multiple packages
|
// parser.ParsePackage doesn't return "multiple packages
|
||||||
// found" errors.
|
// found" errors.
|
||||||
return pkgName(dirname + "/" + d.Name) == pkgname;
|
// Additionally, accept the special package name
|
||||||
|
// fakePkgName if we are looking at cmd documentation.
|
||||||
|
name := pkgName(dirname + "/" + d.Name);
|
||||||
|
return name == pkgname || h.fsRoot == *cmdroot && name == fakePkgName;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -713,32 +744,32 @@ func getPageInfo(path string) PageInfo {
|
|||||||
|
|
||||||
// get directory information
|
// get directory information
|
||||||
var dir *Directory;
|
var dir *Directory;
|
||||||
if tree, _ := pkgTree.get(); tree != nil {
|
if tree, _ := fsTree.get(); tree != nil {
|
||||||
// directory tree is present; lookup respective directory
|
// directory tree is present; lookup respective directory
|
||||||
// (may still fail if the file system was updated and the
|
// (may still fail if the file system was updated and the
|
||||||
// new directory tree has not yet beet computed)
|
// new directory tree has not yet beet computed)
|
||||||
dir = tree.(*Directory).lookup(pathutil.Clean(path));
|
dir = tree.(*Directory).lookup(dirname);
|
||||||
} else {
|
} else {
|
||||||
// no directory tree present (either early after startup
|
// no directory tree present (either early after startup
|
||||||
// or command-line mode); compute one level for this page
|
// or command-line mode); compute one level for this page
|
||||||
dir = newDirectory(path, 1);
|
dir = newDirectory(dirname, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PageInfo{pdoc, dir};
|
return PageInfo{pdoc, dir, h.isPkg};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func servePkg(c *http.Conn, r *http.Request) {
|
func (h *httpHandler) ServeHTTP(c *http.Conn, r *http.Request) {
|
||||||
path := r.Url.Path;
|
path := r.Url.Path;
|
||||||
path = path[len(Pkg):len(path)];
|
path = path[len(h.pattern) : len(path)];
|
||||||
|
|
||||||
// canonicalize URL path and redirect if necessary
|
// canonicalize URL path and redirect if necessary
|
||||||
if canonical := pathutil.Clean(Pkg+path) + "/"; r.Url.Path != canonical {
|
if canonical := pathutil.Clean(h.pattern + path) + "/"; r.Url.Path != canonical {
|
||||||
http.Redirect(c, canonical, http.StatusMovedPermanently);
|
http.Redirect(c, canonical, http.StatusMovedPermanently);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
info := getPageInfo(path);
|
info := h.getPageInfo(path);
|
||||||
|
|
||||||
var buf bytes.Buffer;
|
var buf bytes.Buffer;
|
||||||
if r.FormValue("f") == "text" {
|
if r.FormValue("f") == "text" {
|
||||||
@ -758,7 +789,16 @@ func servePkg(c *http.Conn, r *http.Request) {
|
|||||||
}
|
}
|
||||||
title := "Directory " + path;
|
title := "Directory " + path;
|
||||||
if info.PDoc != nil {
|
if info.PDoc != nil {
|
||||||
|
switch {
|
||||||
|
case h.isPkg:
|
||||||
title = "Package " + info.PDoc.PackageName;
|
title = "Package " + info.PDoc.PackageName;
|
||||||
|
case info.PDoc.PackageName == fakePkgName:
|
||||||
|
// assume that the directory name is the command name
|
||||||
|
_, pkgname := pathutil.Split(pathutil.Clean(path));
|
||||||
|
title = "Command " + pkgname;
|
||||||
|
default:
|
||||||
|
title = "Command " + info.PDoc.PackageName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
servePage(c, title, "", buf.Bytes());
|
servePage(c, title, "", buf.Bytes());
|
||||||
@ -785,7 +825,7 @@ func search(c *http.Conn, r *http.Request) {
|
|||||||
if index, timestamp := searchIndex.get(); index != nil {
|
if index, timestamp := searchIndex.get(); index != nil {
|
||||||
result.Query = query;
|
result.Query = query;
|
||||||
result.Hit, result.Alt = index.(*Index).Lookup(query);
|
result.Hit, result.Alt = index.(*Index).Lookup(query);
|
||||||
_, ts := pkgTree.get();
|
_, ts := fsTree.get();
|
||||||
result.Accurate = timestamp >= ts;
|
result.Accurate = timestamp >= ts;
|
||||||
result.Legend = &infoClasses;
|
result.Legend = &infoClasses;
|
||||||
}
|
}
|
||||||
@ -809,8 +849,15 @@ func search(c *http.Conn, r *http.Request) {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Server
|
// Server
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmdHandler = httpHandler{"/cmd/", *cmdroot, false};
|
||||||
|
pkgHandler = httpHandler{"/pkg/", *pkgroot, true};
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func registerPublicHandlers(mux *http.ServeMux) {
|
func registerPublicHandlers(mux *http.ServeMux) {
|
||||||
mux.Handle(Pkg, http.HandlerFunc(servePkg));
|
mux.Handle(cmdHandler.pattern, &cmdHandler);
|
||||||
|
mux.Handle(pkgHandler.pattern, &pkgHandler);
|
||||||
mux.Handle("/search", http.HandlerFunc(search));
|
mux.Handle("/search", http.HandlerFunc(search));
|
||||||
mux.Handle("/", http.HandlerFunc(serveFile));
|
mux.Handle("/", http.HandlerFunc(serveFile));
|
||||||
}
|
}
|
||||||
@ -819,7 +866,7 @@ func registerPublicHandlers(mux *http.ServeMux) {
|
|||||||
// Indexing goroutine.
|
// Indexing goroutine.
|
||||||
func indexer() {
|
func indexer() {
|
||||||
for {
|
for {
|
||||||
_, ts := pkgTree.get();
|
_, ts := fsTree.get();
|
||||||
if _, timestamp := searchIndex.get(); timestamp < ts {
|
if _, timestamp := searchIndex.get(); timestamp < ts {
|
||||||
// index possibly out of date - make a new one
|
// index possibly out of date - make a new one
|
||||||
// (could use a channel to send an explicit signal
|
// (could use a channel to send an explicit signal
|
||||||
|
@ -97,8 +97,8 @@ func exec(c *http.Conn, args []string) (status int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Maximum package directory depth, adjust as needed.
|
// Maximum directory depth, adjust as needed.
|
||||||
const maxPkgDirDepth = 16;
|
const maxDirDepth = 24;
|
||||||
|
|
||||||
func dosync(c *http.Conn, r *http.Request) {
|
func dosync(c *http.Conn, r *http.Request) {
|
||||||
args := []string{"/bin/sh", "-c", *syncCmd};
|
args := []string{"/bin/sh", "-c", *syncCmd};
|
||||||
@ -109,7 +109,7 @@ func dosync(c *http.Conn, r *http.Request) {
|
|||||||
// TODO(gri): The directory tree may be temporarily out-of-sync.
|
// TODO(gri): The directory tree may be temporarily out-of-sync.
|
||||||
// Consider keeping separate time stamps so the web-
|
// Consider keeping separate time stamps so the web-
|
||||||
// page can indicate this discrepancy.
|
// page can indicate this discrepancy.
|
||||||
pkgTree.set(newDirectory(".", maxPkgDirDepth));
|
fsTree.set(newDirectory(".", maxDirDepth));
|
||||||
fallthrough;
|
fallthrough;
|
||||||
case 1:
|
case 1:
|
||||||
// sync failed because no files changed;
|
// sync failed because no files changed;
|
||||||
@ -178,13 +178,13 @@ func main() {
|
|||||||
http.Handle("/debug/sync", http.HandlerFunc(dosync));
|
http.Handle("/debug/sync", http.HandlerFunc(dosync));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize package tree with corresponding timestamp.
|
// Initialize directory tree with corresponding timestamp.
|
||||||
// Do it in two steps:
|
// Do it in two steps:
|
||||||
// 1) set timestamp right away so that the indexer is kicked on
|
// 1) set timestamp right away so that the indexer is kicked on
|
||||||
pkgTree.set(nil);
|
fsTree.set(nil);
|
||||||
// 2) compute initial package tree in a goroutine so that launch is quick
|
// 2) compute initial directory tree in a goroutine so that launch is quick
|
||||||
go func() {
|
go func() {
|
||||||
pkgTree.set(newDirectory(".", maxPkgDirDepth));
|
fsTree.set(newDirectory(".", maxDirDepth));
|
||||||
}();
|
}();
|
||||||
|
|
||||||
// Start sync goroutine, if enabled.
|
// Start sync goroutine, if enabled.
|
||||||
@ -224,7 +224,12 @@ func main() {
|
|||||||
parseerrorText = parseerrorHtml;
|
parseerrorText = parseerrorHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
info := getPageInfo(flag.Arg(0));
|
info := pkgHandler.getPageInfo(flag.Arg(0));
|
||||||
|
|
||||||
|
if info.PDoc == nil && info.Dirs == nil {
|
||||||
|
// try again, this time assume it's a command
|
||||||
|
info = cmdHandler.getPageInfo(flag.Arg(0));
|
||||||
|
}
|
||||||
|
|
||||||
if info.PDoc != nil && flag.NArg() > 1 {
|
if info.PDoc != nil && flag.NArg() > 1 {
|
||||||
args := flag.Args();
|
args := flag.Args();
|
||||||
|
Loading…
Reference in New Issue
Block a user