diff options
| author | Jonathan Amsterdam <jba@google.com> | 2026-03-27 16:00:47 -0400 |
|---|---|---|
| committer | Jonathan Amsterdam <jba@google.com> | 2026-03-30 13:24:30 -0700 |
| commit | 46b63f3ffddc9657bc2c45fb116dc49c0b381a34 (patch) | |
| tree | 972d64dd1124f3ec1b4cbfd3afc2f1b8aba44f4c /internal/api | |
| parent | 764f79e85197be12b4ab14db0a118c4931d50d2a (diff) | |
| download | go-x-pkgsite-46b63f3ffddc9657bc2c45fb116dc49c0b381a34.tar.xz | |
internal/api: add htmlRenderer
Change-Id: Ib25c913ce7641dea58e2eefb6d65f5cc7dec813a
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/760820
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Ethan Lee <ethanalee@google.com>
kokoro-CI: kokoro <noreply+kokoro@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/render.go | 79 | ||||
| -rw-r--r-- | internal/api/render_test.go | 1 | ||||
| -rw-r--r-- | internal/api/testdata/html.golden | 75 |
3 files changed, 154 insertions, 1 deletions
diff --git a/internal/api/render.go b/internal/api/render.go index 656a8405..8654de2c 100644 --- a/internal/api/render.go +++ b/internal/api/render.go @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// TODO(jba)? Consider rendering notes separately. Now they appear +// with the doc for each symbol, which is probably better for LLMs, +// but be open to evidence to the contrary. + package api import ( @@ -11,6 +15,7 @@ import ( "go/doc/comment" "go/format" "go/token" + "html" "io" "slices" "strings" @@ -25,7 +30,7 @@ import ( type renderer interface { start(*doc.Package) end() error - // startSection start a section, like the one for functions. + // startSection starts a section, like the one for functions. startSection(name string) endSection() @@ -94,6 +99,7 @@ func (r *textRenderer) emit(comment string, node ast.Node) { r.printf("\n") } +// TODO(jba): consolidate this function to avoid duplication. func (r *textRenderer) printf(format string, args ...any) { if r.err != nil { return @@ -175,6 +181,77 @@ func (r *markdownRenderer) printf(format string, args ...any) { } } +type htmlRenderer struct { + fset *token.FileSet + w io.Writer + pkg *doc.Package + parser *comment.Parser + printer *comment.Printer + caser cases.Caser + err error +} + +func (r *htmlRenderer) start(pkg *doc.Package) { + r.pkg = pkg + r.parser = pkg.Parser() + r.printer = pkg.Printer() + r.printer.HeadingLevel = 3 + r.caser = cases.Title(language.English) + + 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 { + r.err = err + } + } + r.printf("\n") +} + +func (r *htmlRenderer) end() error { return r.err } + +func (r *htmlRenderer) startSection(name string) { + if name == "" { + return + } + r.printf("<h2>%s</h2>\n\n", r.caser.String(name)) +} + +func (r *htmlRenderer) endSection() {} + +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) + if err != nil { + r.err = err + return + } + r.printf("<pre><code>%s</code></pre>\n", html.EscapeString(buf.String())) + formatted := r.printer.HTML(r.parser.Parse(comment)) + if len(formatted) > 0 { + _, err = r.w.Write(formatted) + if err != nil { + r.err = err + return + } + } + r.printf("\n") +} + +func (r *htmlRenderer) printf(format string, args ...any) { + if r.err != nil { + return + } + _, err := fmt.Fprintf(r.w, format, args...) + if 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 { diff --git a/internal/api/render_test.go b/internal/api/render_test.go index d163b26d..14e06d6d 100644 --- a/internal/api/render_test.go +++ b/internal/api/render_test.go @@ -65,4 +65,5 @@ func TestRenderDoc(t *testing.T) { check(t, "text", &textRenderer{fset: decoded.Fset, w: &sb}) check(t, "markdown", &markdownRenderer{fset: decoded.Fset, w: &sb}) + check(t, "html", &htmlRenderer{fset: decoded.Fset, w: &sb}) } diff --git a/internal/api/testdata/html.golden b/internal/api/testdata/html.golden new file mode 100644 index 00000000..d77b329c --- /dev/null +++ b/internal/api/testdata/html.golden @@ -0,0 +1,75 @@ +<h1>package pkg</h1> + +<p>Package pkg has every form of declaration. +<h3 id="hdr-Heading">Heading</h3> +<p>Search <a href="https://google.com">Google</a> for details. +<h3 id="hdr-Links">Links</h3> +<ul> +<li>pkgsite repo, <a href="https://go.googlesource.com/pkgsite">https://go.googlesource.com/pkgsite</a> +<li>Play with Go, <a href="https://play-with-go.dev">https://play-with-go.dev</a> +</ul> + +<h2>Constants</h2> + +<pre><code>const ( + X = 1 + Y = 2 +)</code></pre> +<p>Several constants. + +<pre><code>const C = 1</code></pre> +<p>C is a shorthand for 1. + +<h2>Variables</h2> + +<pre><code>var V = 2</code></pre> +<p>V is a variable. + +<h2>Functions</h2> + +<pre><code>func Add(x int) int</code></pre> +<p>Add adds 1 to x. + +<pre><code>func F()</code></pre> +<p>F is a function. + +<h2>Types</h2> + +<pre><code>type A int</code></pre> + +<pre><code>type B bool</code></pre> + +<pre><code>type I1 interface { + M1() +}</code></pre> +<p>I1 is an interface. + +<pre><code>type I2 interface { + I1 + M2() +}</code></pre> + +<pre><code>type S1 struct { + F int // field +}</code></pre> +<p>S1 is a struct. + +<pre><code>type S2 struct { + S1 + G int +}</code></pre> +<p>S2 is another struct. + +<pre><code>type T int</code></pre> +<p>T is a type. + +<pre><code>const CT T = 3</code></pre> +<p>CT is a typed constant. +They appear after their type. + +<pre><code>func TF() T</code></pre> +<p>TF is a constructor for T. + +<pre><code>func (T) M()</code></pre> +<p>M is a method of T. +BUG(xxx): this verifies that notes are rendered.
\ No newline at end of file |
