diff options
| author | Ethan Lee <ethanalee@google.com> | 2026-04-06 21:37:38 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2026-04-08 11:49:01 -0700 |
| commit | 0ef8af41d6814a34f239b02ab621c4cfcb8c0019 (patch) | |
| tree | 9803564ec92e6cbe5ab8cf72bb2ff5a1cf197093 | |
| parent | 372618454cdb62e4cbaab1fd14c58f2faf5db80a (diff) | |
| download | go-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.go | 9 | ||||
| -rw-r--r-- | internal/api/api_test.go | 88 | ||||
| -rw-r--r-- | internal/api/params.go | 1 | ||||
| -rw-r--r-- | internal/testing/sample/sample.go | 26 |
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) |
