blob: 1287522b6b3e9aae4f2dc48cd45d9e7de39c7f81 [file] [log] [blame]
// Copyright 2017 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.
// Package render formats Go documentation as HTML.
// It is an internal component that powers dochtml.
package render
import (
"context"
"go/ast"
"go/token"
"regexp"
"strings"
"github.com/google/safehtml"
"github.com/google/safehtml/template"
"golang.org/x/pkgsite/internal/godoc/internal/doc"
)
var (
// Regexp for example outputs.
exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
)
type Renderer struct {
fset *token.FileSet
pids *packageIDs
packageURL func(string) string
enableCommandTOC bool
ctx context.Context
docTmpl *template.Template
exampleTmpl *template.Template
links []Link // Links removed from package overview to be displayed elsewhere.
}
type Options struct {
// RelatedPackages is a list of related packages to use for hotlinking.
// A recommended heuristic is to include all packages imported by the
// given package, its tests, and its example tests.
//
// Only relevant for HTML formatting.
RelatedPackages []*doc.Package
// PackageURL is a function that given a package path,
// returns a URL for navigating to the godoc for that package.
//
// Only relevant for HTML formatting.
PackageURL func(pkgPath string) (url string)
// EnableCommandTOC turns on the table of contents for the overview section
// of command pages.
//
// Only relevant for HTML formatting.
EnableCommandTOC bool
}
// docDataTmpl renders documentation. It expects a docData.
var docDataTmpl = template.Must(template.New("").Parse(`
{{- if and .EnableCommandTOC .Headings -}}
<div role="navigation" aria-label="Table of Contents">
<ul class="Documentation-toc{{if gt (len .Headings) 5}} Documentation-toc-columns{{end}}">
{{range .Headings -}}
<li class="Documentation-tocItem">
<a href="#{{.ID}}">{{.Title}}</a>
</li>
{{end -}}
</ul>
</div>
{{end -}}
{{- range .Elements -}}
{{- if .IsHeading -}}
<h4 id="{{.ID}}">{{.Title}} <a class="Documentation-idLink" href="#{{.ID}}">ΒΆ</a></h4>
{{- else if .IsPreformat -}}
<pre>{{.Body}}</pre>
{{- else -}}
<p>{{.Body}}</p>
{{- end -}}
{{- end -}}`))
// exampleTmpl renders code for an example. It expect an Example.
var exampleTmpl = template.Must(template.New("").Parse(`
<pre class="Documentation-exampleCode">
{{range .}}
{{- .Text -}}
{{end}}
</pre>
`))
func New(ctx context.Context, fset *token.FileSet, pkg *doc.Package, opts *Options) *Renderer {
var others []*doc.Package
var packageURL func(string) string
var enableCommandTOC bool
if opts != nil {
if len(opts.RelatedPackages) > 0 {
others = opts.RelatedPackages
}
if opts.PackageURL != nil {
packageURL = opts.PackageURL
}
enableCommandTOC = opts.EnableCommandTOC
}
pids := newPackageIDs(pkg, others...)
return &Renderer{
fset: fset,
pids: pids,
packageURL: packageURL,
enableCommandTOC: enableCommandTOC,
docTmpl: docDataTmpl,
exampleTmpl: exampleTmpl,
ctx: ctx,
}
}
const maxSynopsisNodeDepth = 10
// ShortSynopsis returns a very short, one-line summary of the given input node.
// It currently only supports *ast.FuncDecl nodes and will return a non-nil
// error otherwise.
func (r *Renderer) ShortSynopsis(n ast.Node) (string, error) {
return shortOneLineNodeDepth(r.fset, n, 0)
}
// Synopsis returns a one-line summary of the given input node.
func (r *Renderer) Synopsis(n ast.Node) string {
return OneLineNodeDepth(r.fset, n, 0)
}
// DocHTML formats documentation text as HTML.
//
// Each span of unindented non-blank lines is converted into a single paragraph.
// There is one exception to the rule: a span that consists of a
// single line, is followed by another paragraph span, begins with a capital
// letter, and contains no punctuation is formatted as a heading.
//
// A span of indented lines is converted into a <pre> block, with the common
// indent prefix removed.
//
// URLs in the comment text are converted into links. Any word that matches
// an exported top-level identifier in the package is automatically converted
// into a hyperlink to the declaration of that identifier.
//
// This returns formatted HTML with:
//
// <p> elements for plain documentation text
// <pre> elements for preformatted text
// <h3 id="hdr-XXX"> elements for headings with the "id" attribute
// <a href="XXX"> elements for URL hyperlinks
//
// DocHTML is intended for documentation for the package and examples.
func (r *Renderer) DocHTML(doc string) safehtml.HTML {
return r.declHTML(doc, nil, false).Doc
}
// DocHTMLExtractLinks is like DocHTML, but as a side-effect, the "Links"
// heading of doc is removed and its links are extracted.
func (r *Renderer) DocHTMLExtractLinks(doc string) safehtml.HTML {
return r.declHTML(doc, nil, true).Doc
}
// Links returns the links extracted by DocHTMLExtractLinks.
func (r *Renderer) Links() []Link {
return r.links
}
// DeclHTML formats the doc and decl and returns a tuple of
// strings corresponding to each input argument.
//
// This formats documentation HTML according to the same rules as DocHTML.
//
// This formats declaration HTML with:
//
// <pre> element wrapping the entire declaration
// <span id="X" data-kind="K"> elements for many top-level declarations
// <span class="comment"> elements for every Go comment
// <a href="XXX"> elements for URL hyperlinks
//
// DeclHTML is intended for top-level package declarations.
func (r *Renderer) DeclHTML(doc string, decl ast.Decl) (out struct{ Doc, Decl safehtml.HTML }) {
// This returns an anonymous struct instead of multiple return values since
// the template package only allows single return values.
return r.declHTML(doc, decl, false)
}
// CodeHTML formats example code. If the code is a single block statement,
// the outer braces are stripped and the code unindented. If the example code
// contains an output comment, that will stripped as well.
//
// The code type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt
// or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
//
// This returns formatted HTML with:
//
// <pre> element wrapping entire block
// <span class="comment"> elements for every Go comment
//
// CodeHTML is intended for use with example code snippets.
func (r *Renderer) CodeHTML(ex *doc.Example) safehtml.HTML {
return r.codeHTML(ex)
}
func indentLength(s string) int {
return len(s) - len(trimIndent(s))
}
func trimIndent(s string) string {
return strings.TrimLeft(s, " \t")
}