aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEthan Lee <ethanalee@google.com>2026-04-06 21:37:38 +0000
committerGopher Robot <gobot@golang.org>2026-04-08 11:49:01 -0700
commit0ef8af41d6814a34f239b02ab621c4cfcb8c0019 (patch)
tree9803564ec92e6cbe5ab8cf72bb2ff5a1cf197093
parent372618454cdb62e4cbaab1fd14c58f2faf5db80a (diff)
downloadgo-x-pkgsite-0ef8af41d6814a34f239b02ab621c4cfcb8c0019.tar.xz
internal/api: add examples parameter to PackageParams
- Enable conditional population of examples by introducing a new examples parameter. Change-Id: I53314344a414c41b423185c115e600ec8b63e6bf Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/763282 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Ethan Lee <ethanalee@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> kokoro-CI: kokoro <noreply+kokoro@google.com>
-rw-r--r--internal/api/api.go9
-rw-r--r--internal/api/api_test.go88
-rw-r--r--internal/api/params.go1
-rw-r--r--internal/testing/sample/sample.go26
4 files changed, 116 insertions, 8 deletions
diff --git a/internal/api/api.go b/internal/api/api.go
index 831b1af2..2d4d80b5 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -54,7 +54,7 @@ func ServePackage(w http.ResponseWriter, r *http.Request, ds internal.DataSource
if params.Imports {
fs |= internal.WithImports
}
- if params.Doc != "" {
+ if params.Doc != "" || params.Examples {
fs |= internal.WithDocsSource
}
@@ -543,6 +543,10 @@ func paginate[T any](all []T, lp ListParams, defaultLimit int) (PaginatedRespons
// unitToPackage processes unit documentation into a Package struct.
func unitToPackage(unit *internal.Unit, params PackageParams) (*Package, error) {
+ if params.Examples && params.Doc == "" {
+ return nil, fmt.Errorf("%w: examples require doc format to be specified", derrors.InvalidArgument)
+ }
+
// Although unit.Documentation is a slice, it will
// have at most one item, the documentation matching
// the build context.
@@ -565,8 +569,7 @@ func unitToPackage(unit *internal.Unit, params PackageParams) (*Package, error)
}
if params.Doc != "" {
var err error
- const examples = true // TODO(jba): make examples configurable.
- docs, err = renderDocumentation(unit, d, params.Doc, examples)
+ docs, err = renderDocumentation(unit, d, params.Doc, params.Examples)
if err != nil {
return nil, err
}
diff --git a/internal/api/api_test.go b/internal/api/api_test.go
index 36066f46..5d5c9c03 100644
--- a/internal/api/api_test.go
+++ b/internal/api/api_test.go
@@ -106,6 +106,32 @@ func TestServePackage(t *testing.T) {
}},
})
+ ds.MustInsertModule(ctx, &internal.Module{
+ ModuleInfo: internal.ModuleInfo{
+ ModulePath: "example.com/ex",
+ Version: "v1.0.0",
+ LatestVersion: "v1.0.0",
+ },
+ Units: []*internal.Unit{{
+ UnitMeta: internal.UnitMeta{
+ Path: "example.com/ex/pkg",
+ ModuleInfo: internal.ModuleInfo{
+ ModulePath: "example.com/ex",
+ Version: "v1.0.0",
+ LatestVersion: "v1.0.0",
+ },
+ Name: "pkg",
+ },
+ Documentation: []*internal.Documentation{sample.DocumentationWithExamples("linux", "amd64", "", `
+ import "fmt"
+ func Example() {
+ fmt.Println("hello")
+ // Output: hello
+ }
+ `)},
+ }},
+ })
+
for _, test := range []struct {
name string
url string
@@ -195,6 +221,68 @@ func TestServePackage(t *testing.T) {
Docs: "package p\n\nPackage p is a package.\n\n# Links\n\n- pkg.go.dev, https://pkg.go.dev\n\nVARIABLES\n\nvar V int\n\n",
},
},
+ {
+ name: "doc with examples",
+ url: "/v1/package/example.com/ex/pkg?version=v1.0.0&doc=text&examples=true",
+ wantStatus: http.StatusOK,
+ want: &Package{
+ Path: "example.com/ex/pkg",
+ ModulePath: "example.com/ex",
+ ModuleVersion: "v1.0.0",
+ Synopsis: "This is a package synopsis for GOOS=linux, GOARCH=amd64",
+ IsLatest: true,
+ GOOS: "linux",
+ GOARCH: "amd64",
+ Docs: "package pkg\n\nPackage pkg is a package.\n\nExample:\n\t{\n\t\tfmt.Println(\"hello\")\n\t}\n\n\tOutput:\n\thello\n\n",
+ },
+ },
+ {
+ name: "examples without doc (returns 400)",
+ url: "/v1/package/example.com/ex/pkg?version=v1.0.0&examples=true",
+ wantStatus: http.StatusBadRequest,
+ want: &Error{
+ Code: http.StatusBadRequest,
+ Message: "ServePackage: invalid argument: examples require doc format to be specified",
+ },
+ },
+ {
+ name: "doc without examples",
+ url: "/v1/package/example.com/ex/pkg?version=v1.0.0&doc=text&examples=false",
+ wantStatus: http.StatusOK,
+ want: &Package{
+ Path: "example.com/ex/pkg",
+ ModulePath: "example.com/ex",
+ ModuleVersion: "v1.0.0",
+ Synopsis: "This is a package synopsis for GOOS=linux, GOARCH=amd64",
+ IsLatest: true,
+ GOOS: "linux",
+ GOARCH: "amd64",
+ Docs: "package pkg\n\nPackage pkg is a package.\n\n",
+ },
+ },
+ {
+ name: "invalid doc format",
+ url: "/v1/package/example.com/pkg?version=v1.2.3&doc=invalid",
+ wantStatus: http.StatusBadRequest,
+ want: &Error{
+ Code: http.StatusBadRequest,
+ Message: "ServePackage: invalid argument: bad doc format: need one of 'text', 'md', 'markdown' or 'html'",
+ },
+ },
+ {
+ name: "empty doc format",
+ url: "/v1/package/example.com/pkg?version=v1.2.3&doc=",
+ wantStatus: http.StatusOK,
+ want: &Package{
+ Path: "example.com/pkg",
+ ModulePath: "example.com",
+ ModuleVersion: version,
+ Synopsis: "This is a package synopsis for GOOS=linux, GOARCH=amd64",
+ GOOS: "linux",
+ GOARCH: "amd64",
+ Docs: "",
+ },
+ },
} {
t.Run(test.name, func(t *testing.T) {
r := httptest.NewRequest("GET", test.url, nil)
diff --git a/internal/api/params.go b/internal/api/params.go
index 255a6128..6858de5e 100644
--- a/internal/api/params.go
+++ b/internal/api/params.go
@@ -27,6 +27,7 @@ type PackageParams struct {
GOOS string `form:"goos"`
GOARCH string `form:"goarch"`
Doc string `form:"doc"`
+ Examples bool `form:"examples"`
Imports bool `form:"imports"`
Licenses bool `form:"licenses"`
}
diff --git a/internal/testing/sample/sample.go b/internal/testing/sample/sample.go
index f08d3bfa..5c6af994 100644
--- a/internal/testing/sample/sample.go
+++ b/internal/testing/sample/sample.go
@@ -379,13 +379,29 @@ func constructFullPath(modulePath, suffix string) string {
// Documentation returns a Documentation value for the given Go source.
// It panics if there are errors parsing or encoding the source.
func Documentation(goos, goarch, fileContents string) *internal.Documentation {
+ return documentation(goos, goarch, map[string]string{"sample.go": fileContents})
+}
+
+// DocumentationWithExamples returns a Documentation value for the given package
+// documentation and example code.
+// It panics if there are errors parsing or encoding the source.
+func DocumentationWithExamples(goos, goarch, pkgDoc, exampleCode string) *internal.Documentation {
+ return documentation(goos, goarch, map[string]string{
+ "pkg.go": "// Package pkg is a package.\npackage pkg\n" + pkgDoc,
+ "pkg_test.go": "package pkg\n" + exampleCode,
+ })
+}
+
+func documentation(goos, goarch string, files map[string]string) *internal.Documentation {
fset := token.NewFileSet()
- pf, err := parser.ParseFile(fset, "sample.go", fileContents, parser.ParseComments)
- if err != nil {
- panic(err)
- }
docPkg := godoc.NewPackage(fset, nil)
- docPkg.AddFile(pf, true)
+ for name, contents := range files {
+ pf, err := parser.ParseFile(fset, name, contents, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+ docPkg.AddFile(pf, true)
+ }
src, err := docPkg.Encode(context.Background())
if err != nil {
panic(err)