diff options
Diffstat (limited to 'src/cmd/godoc/godoc.go')
-rw-r--r-- | src/cmd/godoc/godoc.go | 584 |
1 files changed, 344 insertions, 240 deletions
diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index f6dc678b4..872b0dc1e 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -12,8 +12,10 @@ import ( "go/ast" "go/build" "go/doc" + "go/format" "go/printer" "go/token" + htmlpkg "html" "io" "io/ioutil" "log" @@ -57,12 +59,13 @@ var ( // TODO(gri) consider the invariant that goroot always end in '/' goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory") testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)") - pkgPath = flag.String("path", "", "additional package directories (colon-separated)") // layout control tabwidth = flag.Int("tabwidth", 4, "tab width") showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings") templateDir = flag.String("templates", "", "directory containing alternate template files") + showPlayground = flag.Bool("play", false, "enable playground in web interface") + showExamples = flag.Bool("ex", false, "show examples in command line mode") // search index indexEnabled = flag.Bool("index", false, "enable search index") @@ -80,22 +83,19 @@ var ( fileServer http.Handler // default file server cmdHandler docServer pkgHandler docServer + + // which code 'Notes' to show + notes = flag.String("notes", "BUG", "comma separated list of Note markers as per pkg:go/doc") + // list of 'Notes' to show + notesToShow []string ) func initHandlers() { - // Add named directories in -path argument as - // subdirectories of src/pkg. - for _, p := range filepath.SplitList(*pkgPath) { - _, elem := filepath.Split(p) - if elem == "" { - log.Fatalf("invalid -path argument: %q has no final element", p) - } - fs.Bind("/src/pkg/"+elem, OS(p), "/", bindReplace) - } + notesToShow = strings.Split(*notes, ",") fileServer = http.FileServer(&httpFS{fs}) - cmdHandler = docServer{"/cmd/", "/src/cmd", false} - pkgHandler = docServer{"/pkg/", "/src/pkg", true} + cmdHandler = docServer{"/cmd/", "/src/cmd"} + pkgHandler = docServer{"/pkg/", "/src/pkg"} } func registerPublicHandlers(mux *http.ServeMux) { @@ -326,18 +326,62 @@ func startsWithUppercase(s string) bool { var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`) -func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string { +// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name +// while keeping uppercase Braz in Foo_Braz. +func stripExampleSuffix(name string) string { + if i := strings.LastIndex(name, "_"); i != -1 { + if i < len(name)-1 && !startsWithUppercase(name[i+1:]) { + name = name[:i] + } + } + return name +} + +func example_textFunc(funcName string, examples []*doc.Example, fset *token.FileSet, indent string) string { + if !*showExamples { + return "" + } + var buf bytes.Buffer + first := true for _, eg := range examples { - name := eg.Name + name := stripExampleSuffix(eg.Name) + if name != funcName { + continue + } - // strip lowercase braz in Foo_braz or Foo_Bar_braz from name - // while keeping uppercase Braz in Foo_Braz - if i := strings.LastIndex(name, "_"); i != -1 { - if i < len(name)-1 && !startsWithUppercase(name[i+1:]) { - name = name[:i] - } + if !first { + buf.WriteString("\n") } + first = false + + // print code + cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} + var buf1 bytes.Buffer + writeNode(&buf1, fset, cnode) + code := buf1.String() + // Additional formatting if this is a function body. + if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' { + // remove surrounding braces + code = code[1 : n-1] + // unindent + code = strings.Replace(code, "\n ", "\n", -1) + } + code = strings.Trim(code, "\n") + code = strings.Replace(code, "\n", "\n\t", -1) + + buf.WriteString(indent) + buf.WriteString("Example:\n\t") + buf.WriteString(code) + buf.WriteString("\n") + } + return buf.String() +} + +func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string { + var buf bytes.Buffer + for _, eg := range examples { + name := stripExampleSuffix(eg.Name) if name != funcName { continue @@ -347,9 +391,11 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments} code := node_htmlFunc(cnode, fset) out := eg.Output + wholeFile := true - // additional formatting if this is a function body + // Additional formatting if this is a function body. if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' { + wholeFile = false // remove surrounding braces code = code[1 : n-1] // unindent @@ -358,14 +404,28 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File if loc := exampleOutputRx.FindStringIndex(code); loc != nil { code = strings.TrimSpace(code[:loc[0]]) } - } else { - // drop output, as the output comment will appear in the code + } + + // Write out the playground code in standard Go style + // (use tabs, no comment highlight, etc). + play := "" + if eg.Play != nil && *showPlayground { + var buf bytes.Buffer + if err := format.Node(&buf, fset, eg.Play); err != nil { + log.Print(err) + } else { + play = buf.String() + } + } + + // Drop output, as the output comment will appear in the code. + if wholeFile && play == "" { out = "" } err := exampleHTML.Execute(&buf, struct { - Name, Doc, Code, Output string - }{eg.Name, eg.Doc, code, out}) + Name, Doc, Code, Play, Output string + }{eg.Name, eg.Doc, code, play, out}) if err != nil { log.Print(err) } @@ -393,6 +453,10 @@ func example_suffixFunc(name string) string { return suffix } +func noteTitle(note string) string { + return strings.Title(strings.ToLower(note)) +} + func splitExampleName(s string) (name, suffix string) { i := strings.LastIndex(s, "_") if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) { @@ -408,9 +472,7 @@ func pkgLinkFunc(path string) string { relpath := path[1:] // because of the irregular mapping under goroot // we need to correct certain relative paths - if strings.HasPrefix(relpath, "src/pkg/") { - relpath = relpath[len("src/pkg/"):] - } + relpath = strings.TrimPrefix(relpath, "src/pkg/") return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL } @@ -485,8 +547,12 @@ var fmap = template.FuncMap{ // formatting of Examples "example_html": example_htmlFunc, + "example_text": example_textFunc, "example_name": example_nameFunc, "example_suffix": example_suffixFunc, + + // formatting of Notes + "noteTitle": noteTitle, } func readTemplate(name string) *template.Template { @@ -538,31 +604,28 @@ func readTemplates() { // ---------------------------------------------------------------------------- // Generic HTML wrapper -func servePage(w http.ResponseWriter, tabtitle, title, subtitle, query string, content []byte) { - if tabtitle == "" { - tabtitle = title - } - d := struct { - Tabtitle string - Title string - Subtitle string - SearchBox bool - Query string - Version string - Menu []byte - Content []byte - }{ - tabtitle, - title, - subtitle, - *indexEnabled, - query, - runtime.Version(), - nil, - content, - } - - if err := godocHTML.Execute(w, &d); err != nil { +// Page describes the contents of the top-level godoc webpage. +type Page struct { + Title string + Tabtitle string + Subtitle string + Query string + Body []byte + + // filled in by servePage + SearchBox bool + Playground bool + Version string +} + +func servePage(w http.ResponseWriter, page Page) { + if page.Tabtitle == "" { + page.Tabtitle = page.Title + } + page.SearchBox = *indexEnabled + page.Playground = *showPlayground + page.Version = runtime.Version() + if err := godocHTML.Execute(w, page); err != nil { log.Printf("godocHTML.Execute: %s", err) } } @@ -627,7 +690,11 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin src = buf.Bytes() } - servePage(w, "", meta.Title, meta.Subtitle, "", src) + servePage(w, Page{ + Title: meta.Title, + Subtitle: meta.Subtitle, + Body: src, + }) } func applyTemplate(t *template.Template, name string, data interface{}) []byte { @@ -640,11 +707,25 @@ func applyTemplate(t *template.Template, name string, data interface{}) []byte { func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { canonical := pathpkg.Clean(r.URL.Path) - if !strings.HasSuffix("/", canonical) { + if !strings.HasSuffix(canonical, "/") { canonical += "/" } if r.URL.Path != canonical { - http.Redirect(w, r, canonical, http.StatusMovedPermanently) + url := *r.URL + url.Path = canonical + http.Redirect(w, r, url.String(), http.StatusMovedPermanently) + redirected = true + } + return +} + +func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) { + c := pathpkg.Clean(r.URL.Path) + c = strings.TrimRight(c, "/") + if r.URL.Path != c { + url := *r.URL + url.Path = c + http.Redirect(w, r, url.String(), http.StatusMovedPermanently) redirected = true } return @@ -658,12 +739,22 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit return } + if r.FormValue("m") == "text" { + serveText(w, src) + return + } + var buf bytes.Buffer buf.WriteString("<pre>") FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s"))) buf.WriteString("</pre>") + fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath)) - servePage(w, relpath, title+" "+relpath, "", "", buf.Bytes()) + servePage(w, Page{ + Title: title + " " + relpath, + Tabtitle: relpath, + Body: buf.Bytes(), + }) } func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) { @@ -677,8 +768,11 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str return } - contents := applyTemplate(dirlistHTML, "dirlistHTML", list) - servePage(w, relpath, "Directory "+relpath, "", "", contents) + servePage(w, Page{ + Title: "Directory " + relpath, + Tabtitle: relpath, + Body: applyTemplate(dirlistHTML, "dirlistHTML", list), + }) } func serveFile(w http.ResponseWriter, r *http.Request) { @@ -734,6 +828,9 @@ func serveFile(w http.ResponseWriter, r *http.Request) { } if isTextFile(abspath) { + if redirectFile(w, r) { + return + } serveTextFile(w, r, abspath, relpath, "Text file") return } @@ -754,10 +851,6 @@ func serveSearchDesc(w http.ResponseWriter, r *http.Request) { // ---------------------------------------------------------------------------- // Packages -// Fake package file and name for commands. Contains the command documentation. -const fakePkgFile = "doc.go" -const fakePkgName = "documentation" - // Fake relative package path for built-ins. Documentation for all globals // (not just exported ones) will be shown for packages in this directory. const builtinPkgPath = "builtin" @@ -814,17 +907,21 @@ func remoteSearchURL(query string, html bool) string { } type PageInfo struct { - Dirname string // directory containing the package - PList []string // list of package names found - FSet *token.FileSet // corresponding file set - PAst *ast.File // nil if no single AST with package exports - PDoc *doc.Package // nil if no single package documentation - Examples []*doc.Example // nil if no example code - Dirs *DirList // nil if no directory information - DirTime time.Time // directory time stamp - DirFlat bool // if set, show directory in a flat (non-indented) manner - IsPkg bool // false if this is not documenting a real package - Err error // I/O error or nil + Dirname string // directory containing the package + Err error // error or nil + + // package info + FSet *token.FileSet // nil if no package documentation + PDoc *doc.Package // nil if no package documentation + Examples []*doc.Example // nil if no example code + Notes map[string][]string // nil if no package Notes + PAst *ast.File // nil if no AST with package exports + IsMain bool // true for package main + + // directory info + Dirs *DirList // nil if no directory information + DirTime time.Time // directory time stamp + DirFlat bool // if set, show directory in a flat (non-indented) manner } func (info *PageInfo) IsEmpty() bool { @@ -834,30 +931,97 @@ func (info *PageInfo) IsEmpty() bool { type docServer 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) } // fsReadDir implements ReadDir for the go/build package. func fsReadDir(dir string) ([]os.FileInfo, error) { - return fs.ReadDir(dir) + return fs.ReadDir(filepath.ToSlash(dir)) } // fsOpenFile implements OpenFile for the go/build package. func fsOpenFile(name string) (r io.ReadCloser, err error) { - data, err := ReadFile(fs, name) + data, err := ReadFile(fs, filepath.ToSlash(name)) if err != nil { return nil, err } return ioutil.NopCloser(bytes.NewReader(data)), nil } -func inList(name string, list []string) bool { - for _, l := range list { - if name == l { - return true +// packageExports is a local implementation of ast.PackageExports +// which correctly updates each package file's comment list. +// (The ast.PackageExports signature is frozen, hence the local +// implementation). +// +func packageExports(fset *token.FileSet, pkg *ast.Package) { + for _, src := range pkg.Files { + cmap := ast.NewCommentMap(fset, src, src.Comments) + ast.FileExports(src) + src.Comments = cmap.Filter(src).Comments() + } +} + +// addNames adds the names declared by decl to the names set. +// Method names are added in the form ReceiverTypeName_Method. +func addNames(names map[string]bool, decl ast.Decl) { + switch d := decl.(type) { + case *ast.FuncDecl: + name := d.Name.Name + if d.Recv != nil { + var typeName string + switch r := d.Recv.List[0].Type.(type) { + case *ast.StarExpr: + typeName = r.X.(*ast.Ident).Name + case *ast.Ident: + typeName = r.Name + } + name = typeName + "_" + name + } + names[name] = true + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + names[s.Name.Name] = true + case *ast.ValueSpec: + for _, id := range s.Names { + names[id.Name] = true + } + } + } + } +} + +// globalNames returns a set of the names declared by all package-level +// declarations. Method names are returned in the form Receiver_Method. +func globalNames(pkg *ast.Package) map[string]bool { + names := make(map[string]bool) + for _, file := range pkg.Files { + for _, decl := range file.Decls { + addNames(names, decl) } } - return false + return names +} + +// collectExamples collects examples for pkg from testfiles. +func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example { + var files []*ast.File + for _, f := range testfiles { + files = append(files, f) + } + + var examples []*doc.Example + globals := globalNames(pkg) + for _, e := range doc.Examples(files...) { + name := stripExampleSuffix(e.Name) + if name == "" || globals[name] { + examples = append(examples, e) + } else { + log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name) + } + } + + return examples } // getPageInfo returns the PageInfo for a package directory abspath. If the @@ -865,132 +1029,56 @@ func inList(name string, list []string) bool { // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) // is extracted from the AST. If there is no corresponding package in the // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- -// directories, PageInfo.Dirs is nil. If a directory read error occurred, -// PageInfo.Err is set to the respective error but the error is not logged. +// directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is +// set to the respective error but the error is not logged. // -func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo { - var pkgFiles []string +func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (info PageInfo) { + info.Dirname = abspath - // If we're showing the default package, restrict to the ones - // that would be used when building the package on this - // system. This makes sure that if there are separate - // implementations for, say, Windows vs Unix, we don't + // Restrict to the package files that would be used when building + // the package on this system. This makes sure that if there are + // separate implementations for, say, Windows vs Unix, we don't // jumble them all together. - if pkgname == "" { - // Note: Uses current binary's GOOS/GOARCH. - // To use different pair, such as if we allowed the user - // to choose, set ctxt.GOOS and ctxt.GOARCH before - // calling ctxt.ScanDir. - ctxt := build.Default - ctxt.IsAbsPath = pathpkg.IsAbs - ctxt.ReadDir = fsReadDir - ctxt.OpenFile = fsOpenFile - dir, err := ctxt.ImportDir(abspath, 0) - if err == nil { - pkgFiles = append(dir.GoFiles, dir.CgoFiles...) - } - } - - // filter function to select the desired .go files - filter := func(d os.FileInfo) bool { - // Only Go files. - if !isPkgFile(d) { - return false - } - // If we are looking at cmd documentation, only accept - // the special fakePkgFile containing the documentation. - if !h.isPkg { - return d.Name() == fakePkgFile - } - // Also restrict file list to pkgFiles. - return pkgFiles == nil || inList(d.Name(), pkgFiles) - } - - // get package ASTs - fset := token.NewFileSet() - pkgs, err := parseDir(fset, abspath, filter) - if err != nil && pkgs == nil { - // only report directory read errors, ignore parse errors - // (may be able to extract partial package information) - return PageInfo{Dirname: abspath, Err: err} - } - - // select package - var pkg *ast.Package // selected package - var plist []string // list of other package (names), if any - if len(pkgs) == 1 { - // Exactly one package - select it. - for _, p := range pkgs { - pkg = p - } - - } else if len(pkgs) > 1 { - // Multiple packages - select the best matching package: The - // 1st choice is the package with pkgname, the 2nd choice is - // the package with dirname, and the 3rd choice is a package - // that is not called "main" if there is exactly one such - // package. Otherwise, don't select a package. - dirpath, dirname := pathpkg.Split(abspath) - - // If the dirname is "go" we might be in a sub-directory for - // .go files - use the outer directory name instead for better - // results. - if dirname == "go" { - _, dirname = pathpkg.Split(pathpkg.Clean(dirpath)) - } - - var choice3 *ast.Package - loop: - for _, p := range pkgs { - switch { - case p.Name == pkgname: - pkg = p - break loop // 1st choice; we are done - case p.Name == dirname: - pkg = p // 2nd choice - case p.Name != "main": - choice3 = p - } - } - if pkg == nil && len(pkgs) == 2 { - pkg = choice3 - } - - // Compute the list of other packages - // (excluding the selected package, if any). - plist = make([]string, len(pkgs)) - i := 0 - for name := range pkgs { - if pkg == nil || name != pkg.Name { - plist[i] = name - i++ - } - } - plist = plist[0:i] - sort.Strings(plist) + // Note: Uses current binary's GOOS/GOARCH. + // To use different pair, such as if we allowed the user to choose, + // set ctxt.GOOS and ctxt.GOARCH before calling ctxt.ImportDir. + ctxt := build.Default + ctxt.IsAbsPath = pathpkg.IsAbs + ctxt.ReadDir = fsReadDir + ctxt.OpenFile = fsOpenFile + pkginfo, err := ctxt.ImportDir(abspath, 0) + // continue if there are no Go source files; we still want the directory info + if _, nogo := err.(*build.NoGoError); err != nil && !nogo { + info.Err = err + return } - // get examples from *_test.go files - var examples []*doc.Example - filter = func(d os.FileInfo) bool { - return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go") - } - if testpkgs, err := parseDir(fset, abspath, filter); err != nil { - log.Println("parsing test files:", err) - } else { - for _, testpkg := range testpkgs { - var files []*ast.File - for _, f := range testpkg.Files { - files = append(files, f) - } - examples = append(examples, doc.Examples(files...)...) + // collect package files + pkgname := pkginfo.Name + pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...) + if len(pkgfiles) == 0 { + // Commands written in C have no .go files in the build. + // Instead, documentation may be found in an ignored file. + // The file may be ignored via an explicit +build ignore + // constraint (recommended), or by defining the package + // documentation (historic). + pkgname = "main" // assume package main since pkginfo.Name == "" + pkgfiles = pkginfo.IgnoredGoFiles + } + + // get package information, if any + if len(pkgfiles) > 0 { + // build package AST + fset := token.NewFileSet() + files, err := parseFiles(fset, abspath, pkgfiles) + if err != nil { + info.Err = err + return } - } + pkg := &ast.Package{Name: pkgname, Files: files} - // compute package documentation - var past *ast.File - var pdoc *doc.Package - if pkg != nil { + // extract package documentation + info.FSet = fset if mode&showSource == 0 { // show extracted documentation var m doc.Mode @@ -1000,19 +1088,39 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM if mode&allMethods != 0 { m |= doc.AllMethods } - pdoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath + info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath + + // collect examples + testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...) + files, err = parseFiles(fset, abspath, testfiles) + if err != nil { + log.Println("parsing examples:", err) + } + info.Examples = collectExamples(pkg, files) + + // collect any notes that we want to show + if info.PDoc.Notes != nil { + info.Notes = make(map[string][]string) + for _, m := range notesToShow { + if n := info.PDoc.Notes[m]; n != nil { + info.Notes[m] = n + } + } + } + } else { // show source code // TODO(gri) Consider eliminating export filtering in this mode, // or perhaps eliminating the mode altogether. if mode&noFiltering == 0 { - ast.PackageExports(pkg) + packageExports(fset, pkg) } - past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments) + info.PAst = ast.MergePackageFiles(pkg, 0) } + info.IsMain = pkgname == "main" } - // get directory information + // get directory information, if any var dir *Directory var timestamp time.Time if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil { @@ -1030,20 +1138,11 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM dir = newDirectory(abspath, 1) timestamp = time.Now() } + info.Dirs = dir.listing(true) + info.DirTime = timestamp + info.DirFlat = mode&flatDir != 0 - return PageInfo{ - Dirname: abspath, - PList: plist, - FSet: fset, - PAst: past, - PDoc: pdoc, - Examples: examples, - Dirs: dir.listing(true), - DirTime: timestamp, - DirFlat: mode&flatDir != 0, - IsPkg: h.isPkg, - Err: nil, - } + return } func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -1057,7 +1156,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { if relpath == builtinPkgPath { mode = noFiltering } - info := h.getPageInfo(abspath, relpath, r.FormValue("p"), mode) + info := h.getPageInfo(abspath, relpath, mode) if info.Err != nil { log.Print(info.Err) serveError(w, r, relpath, info.Err) @@ -1065,8 +1164,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if mode&noHtml != 0 { - contents := applyTemplate(packageText, "packageText", info) - serveText(w, contents) + serveText(w, applyTemplate(packageText, "packageText", info)) return } @@ -1074,26 +1172,25 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case info.PAst != nil: tabtitle = info.PAst.Name.Name - title = "Package " + tabtitle case info.PDoc != nil: - if info.PDoc.Name == fakePkgName { - // assume that the directory name is the command name - _, tabtitle = pathpkg.Split(relpath) - } else { - tabtitle = info.PDoc.Name - } - if info.IsPkg { - title = "Package " + tabtitle - } else { - title = "Command " + tabtitle - } + tabtitle = info.PDoc.Name default: tabtitle = info.Dirname - title = "Directory " + tabtitle + title = "Directory " if *showTimestamps { subtitle = "Last update: " + info.DirTime.String() } } + if title == "" { + if info.IsMain { + // assume that the directory name is the command name + _, tabtitle = pathpkg.Split(relpath) + title = "Command " + } else { + title = "Package " + } + } + title += tabtitle // special cases for top-level package/command directories switch tabtitle { @@ -1103,8 +1200,12 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { tabtitle = "Commands" } - contents := applyTemplate(packageHTML, "packageHTML", info) - servePage(w, tabtitle, title, subtitle, "", contents) + servePage(w, Page{ + Title: title, + Tabtitle: tabtitle, + Subtitle: subtitle, + Body: applyTemplate(packageHTML, "packageHTML", info), + }) } // ---------------------------------------------------------------------------- @@ -1181,8 +1282,7 @@ func search(w http.ResponseWriter, r *http.Request) { result := lookup(query) if getPageInfoMode(r)&noHtml != 0 { - contents := applyTemplate(searchText, "searchText", result) - serveText(w, contents) + serveText(w, applyTemplate(searchText, "searchText", result)) return } @@ -1193,8 +1293,12 @@ func search(w http.ResponseWriter, r *http.Request) { title = fmt.Sprintf(`No results found for query %q`, query) } - contents := applyTemplate(searchHTML, "searchHTML", result) - servePage(w, query, title, "", query, contents) + servePage(w, Page{ + Title: title, + Tabtitle: query, + Query: query, + Body: applyTemplate(searchHTML, "searchHTML", result), + }) } // ---------------------------------------------------------------------------- |