diff options
| author | Jonathan Amsterdam <jba@google.com> | 2026-03-27 15:18:20 -0400 |
|---|---|---|
| committer | Jonathan Amsterdam <jba@google.com> | 2026-04-06 07:29:20 -0700 |
| commit | f7258b211d2eab96a43600ece95d591f3973ae69 (patch) | |
| tree | 97f1c8d21108973533054f33a01f17e8d5d26269 /internal/api/render.go | |
| parent | 2d0d60d0e456af02dfc52d79053d5a3a20fb11ff (diff) | |
| download | go-x-pkgsite-f7258b211d2eab96a43600ece95d591f3973ae69.tar.xz | |
internal/api: add support for examples
renderDoc now includes examples.
It has an option to omit examples, if we decide to omit them
in order to reduce the context load.
Change-Id: Ieaa411fe09b439f51aac7d49195cd5791a20d0d8
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/761000
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Ethan Lee <ethanalee@google.com>
TryBot-Bypass: Jonathan Amsterdam <jba@google.com>
Diffstat (limited to 'internal/api/render.go')
| -rw-r--r-- | internal/api/render.go | 167 |
1 files changed, 138 insertions, 29 deletions
diff --git a/internal/api/render.go b/internal/api/render.go index 8654de2c..f8d9dbc7 100644 --- a/internal/api/render.go +++ b/internal/api/render.go @@ -37,7 +37,9 @@ type renderer interface { // emit prints documentation for particular node, like a const // or function. emit(comment string, node ast.Node) - // TODO(jba): support examples + + // emitExample prints an example. + emitExample(ex *doc.Example) } type textRenderer struct { @@ -62,8 +64,7 @@ func (r *textRenderer) start(pkg *doc.Package) { if pkg.Doc != "" { r.printf("\n") // The package doc is not indented, so don't use r.printer. - _, err := r.w.Write(pkg.Text(pkg.Doc)) - if err != nil { + if _, err := r.w.Write(pkg.Text(pkg.Doc)); err != nil { r.err = err } } @@ -90,8 +91,7 @@ func (r *textRenderer) emit(comment string, node ast.Node) { r.printf("\n") formatted := r.printer.Text(r.parser.Parse(comment)) if len(formatted) > 0 { - _, err = r.w.Write(formatted) - if err != nil { + if _, err = r.w.Write(formatted); err != nil { r.err = err return } @@ -99,13 +99,54 @@ func (r *textRenderer) emit(comment string, node ast.Node) { r.printf("\n") } +func (r *textRenderer) emitExample(ex *doc.Example) { + if r.err != nil { + return + } + r.printf("Example") + if ex.Suffix != "" { + r.printf(" (%s)", ex.Suffix) + } + r.printf(":\n") + if ex.Doc != "" { + formatted := r.printer.Text(r.parser.Parse(ex.Doc)) + if len(formatted) > 0 { + if _, err := r.w.Write(formatted); err != nil { + r.err = err + return + } + r.printf("\n") + } + } + var buf strings.Builder + if err := format.Node(&buf, r.fset, ex.Code); err != nil { + r.err = err + return + } + // Indent the code and output. + lines := strings.Split(strings.TrimSpace(buf.String()), "\n") + for i, line := range lines { + // Omit blank line before close brace. + if i == len(lines)-2 && line == "" { + continue + } + r.printf("\t%s\n", line) + } + if ex.Output != "" { + r.printf("\n\tOutput:\n") + for _, line := range strings.Split(strings.TrimSpace(ex.Output), "\n") { + r.printf("\t%s\n", line) + } + } + r.printf("\n") +} + // TODO(jba): consolidate this function to avoid duplication. func (r *textRenderer) printf(format string, args ...any) { if r.err != nil { return } - _, err := fmt.Fprintf(r.w, format, args...) - if err != nil { + if _, err := fmt.Fprintf(r.w, format, args...); err != nil { r.err = err } } @@ -130,8 +171,7 @@ func (r *markdownRenderer) start(pkg *doc.Package) { r.printf("# package %s\n", pkg.Name) if pkg.Doc != "" { r.printf("\n") - _, err := r.w.Write(r.printer.Markdown(r.parser.Parse(pkg.Doc))) - if err != nil { + if _, err := r.w.Write(r.printer.Markdown(r.parser.Parse(pkg.Doc))); err != nil { r.err = err } } @@ -153,7 +193,7 @@ func (r *markdownRenderer) emit(comment string, node ast.Node) { if r.err != nil { return } - r.printf("```\n") + r.printf("```go\n") err := format.Node(r.w, r.fset, node) if err != nil { r.err = err @@ -162,8 +202,7 @@ func (r *markdownRenderer) emit(comment string, node ast.Node) { r.printf("\n```\n") formatted := r.printer.Markdown(r.parser.Parse(comment)) if len(formatted) > 0 { - _, err = r.w.Write(formatted) - if err != nil { + if _, err = r.w.Write(formatted); err != nil { r.err = err return } @@ -171,13 +210,41 @@ func (r *markdownRenderer) emit(comment string, node ast.Node) { r.printf("\n") } -func (r *markdownRenderer) printf(format string, args ...any) { +func (r *markdownRenderer) emitExample(ex *doc.Example) { if r.err != nil { return } - _, err := fmt.Fprintf(r.w, format, args...) + r.printf("#### Example") + if ex.Suffix != "" { + r.printf(" (%s)", ex.Suffix) + } + r.printf("\n\n") + if ex.Doc != "" { + if _, err := r.w.Write(r.printer.Markdown(r.parser.Parse(ex.Doc))); err != nil { + r.err = err + return + } + r.printf("\n") + } + r.printf("```go\n") + err := format.Node(r.w, r.fset, ex.Code) if err != nil { r.err = err + return + } + r.printf("\n```\n") + if ex.Output != "" { + r.printf("Output:\n\n```\n%s\n```\n", ex.Output) + } + r.printf("\n") +} + +func (r *markdownRenderer) printf(format string, args ...any) { + if r.err != nil { + return + } + if _, err := fmt.Fprintf(r.w, format, args...); err != nil { + r.err = err } } @@ -188,6 +255,7 @@ type htmlRenderer struct { parser *comment.Parser printer *comment.Printer caser cases.Caser + buf strings.Builder err error } @@ -201,8 +269,7 @@ func (r *htmlRenderer) start(pkg *doc.Package) { r.printf("<h1>package %s</h1>\n", pkg.Name) if pkg.Doc != "" { r.printf("\n") - _, err := r.w.Write(r.printer.HTML(r.parser.Parse(pkg.Doc))) - if err != nil { + if _, err := r.w.Write(r.printer.HTML(r.parser.Parse(pkg.Doc))); err != nil { r.err = err } } @@ -224,17 +291,16 @@ func (r *htmlRenderer) emit(comment string, node ast.Node) { if r.err != nil { return } - var buf strings.Builder - err := format.Node(&buf, r.fset, node) + r.buf.Reset() + err := format.Node(&r.buf, r.fset, node) if err != nil { r.err = err return } - r.printf("<pre><code>%s</code></pre>\n", html.EscapeString(buf.String())) + r.printf("<pre><code>%s</code></pre>\n", html.EscapeString(r.buf.String())) formatted := r.printer.HTML(r.parser.Parse(comment)) if len(formatted) > 0 { - _, err = r.w.Write(formatted) - if err != nil { + if _, err = r.w.Write(formatted); err != nil { r.err = err return } @@ -242,24 +308,57 @@ func (r *htmlRenderer) emit(comment string, node ast.Node) { r.printf("\n") } -func (r *htmlRenderer) printf(format string, args ...any) { +func (r *htmlRenderer) emitExample(ex *doc.Example) { if r.err != nil { return } - _, err := fmt.Fprintf(r.w, format, args...) + r.printf("<h4>Example") + if ex.Suffix != "" { + r.printf(" (%s)", ex.Suffix) + } + r.printf("</h4>\n") + r.printf("\n") + if ex.Doc != "" { + if _, err := r.w.Write(r.printer.Markdown(r.parser.Parse(ex.Doc))); err != nil { + r.err = err + return + } + r.printf("\n") + } + r.printf("<pre><code>\n") + err := format.Node(r.w, r.fset, ex.Code) if err != nil { r.err = err + return + } + r.printf("\n</code></pre>\n") + if ex.Output != "" { + r.printf("Output:\n\n<pre><code>\n%s\n</code></pre>\n", html.EscapeString(ex.Output)) + } + r.printf("\n") +} + +func (r *htmlRenderer) printf(format string, args ...any) { + if r.err != nil { + return + } + if _, err := fmt.Fprintf(r.w, format, args...); err != nil { + r.err = err } } // renderDoc renders the documentation for dpkg using the given renderer. -// TODO(jba): support examples. -func renderDoc(dpkg *doc.Package, r renderer) error { +func renderDoc(dpkg *doc.Package, r renderer, examples bool) error { r.start(dpkg) + if examples { + for _, ex := range dpkg.Examples { + r.emitExample(ex) + } + } renderValues(dpkg.Consts, r, "constants") renderValues(dpkg.Vars, r, "variables") - renderFuncs(dpkg.Funcs, r, "functions") + renderFuncs(dpkg.Funcs, r, "functions", examples) started := false for _, t := range dpkg.Types { @@ -271,10 +370,15 @@ func renderDoc(dpkg *doc.Package, r renderer) error { started = true } r.emit(t.Doc, t.Decl) + if examples { + for _, ex := range t.Examples { + r.emitExample(ex) + } + } renderValues(t.Consts, r, "") renderValues(t.Vars, r, "") - renderFuncs(t.Funcs, r, "") - renderFuncs(t.Methods, r, "") + renderFuncs(t.Funcs, r, "", examples) + renderFuncs(t.Methods, r, "", examples) } if started { r.endSection() @@ -301,7 +405,7 @@ func renderValues(vals []*doc.Value, r renderer, section string) { } } -func renderFuncs(funcs []*doc.Func, r renderer, section string) { +func renderFuncs(funcs []*doc.Func, r renderer, section string, examples bool) { started := false for _, f := range funcs { if !ast.IsExported(f.Name) { @@ -314,6 +418,11 @@ func renderFuncs(funcs []*doc.Func, r renderer, section string) { started = true } r.emit(f.Doc, f.Decl) + if examples { + for _, ex := range f.Examples { + r.emitExample(ex) + } + } } if started && section != "" { r.endSection() |
