aboutsummaryrefslogtreecommitdiff
path: root/internal/api/render.go
diff options
context:
space:
mode:
authorJonathan Amsterdam <jba@google.com>2026-03-27 15:18:20 -0400
committerJonathan Amsterdam <jba@google.com>2026-04-06 07:29:20 -0700
commitf7258b211d2eab96a43600ece95d591f3973ae69 (patch)
tree97f1c8d21108973533054f33a01f17e8d5d26269 /internal/api/render.go
parent2d0d60d0e456af02dfc52d79053d5a3a20fb11ff (diff)
downloadgo-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.go167
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()