mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-11 00:24:14 -04:00
misc/genreadme,tempfork/pkgdoc,tsnet: generate README.md files from godoc
Adds a CI check to keep opted-in directories' README.md files in sync
with their package godoc. For now tsnet (and its sub-packages under
tsnet/example) is the only opted-in tree. The list of directories
lives in misc/genreadme/genreadme.go as defaultRoots, so CI and humans
both just run `./tool/go run ./misc/genreadme` with no arguments.
The check piggybacks on the existing go_generate job in test.yml and
fails if any README.md is out of date, pointing the user at the same
command.
Along the way:
- tempfork/pkgdoc now emits Markdown instead of plain text: headings
become level-2 with no {#hdr-...} anchors, and [Symbol] doc links
resolve to pkg.go.dev URLs, including for symbols in the current
package (which the default Printer would otherwise emit as bare
#Name fragments with no backing anchor in a README). Parsing no
longer uses parser.ImportsOnly, so doc.Package knows the package's
symbols and can resolve [Symbol] links at all.
- genreadme also emits a pkg.go.dev Go Reference badge at the top of
a library package's README; suppressed for package main.
- tsnet/tsnet.go's package godoc is expanded in idiomatic godoc
syntax — [Type], [Type.Method], reference-style [link]: URL
definitions — rather than Markdown-flavored [text](url) or
backtick-quoted identifiers, so that both pkg.go.dev and the
generated README.md render cleanly from a single source.
Fixes #19431
Fixes #19483
Fixes #19470
Change-Id: I8ca37e9e7b3bd446b8bfa7a91ac548f142688cb1
Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: Walter Poupore <walterp@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
311dd3839d
commit
a7d8aeb8ae
@@ -13,6 +13,7 @@
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/doc"
|
||||
"go/doc/comment"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
@@ -46,6 +47,31 @@ func (pkg *Package) ToText(w io.Writer, text, prefix, codePrefix string) {
|
||||
w.Write(pr.Text(d))
|
||||
}
|
||||
|
||||
// ToMarkdown parses the godoc comment text and writes a Markdown rendering to w
|
||||
// suitable for a repository README.md: top-level sections become ## headings
|
||||
// without per-heading anchor IDs, and [Symbol] doc links resolve to pkg.go.dev,
|
||||
// including for symbols in the current package (which the default printer would
|
||||
// otherwise emit as bare #Name fragments with no backing anchor).
|
||||
func (pkg *Package) ToMarkdown(w io.Writer, text string) {
|
||||
d := pkg.doc.Parser().Parse(text)
|
||||
pr := pkg.doc.Printer()
|
||||
pr.HeadingLevel = 2
|
||||
pr.HeadingID = func(*comment.Heading) string { return "" }
|
||||
pr.DocLinkBaseURL = "https://pkg.go.dev"
|
||||
pr.DocLinkURL = func(link *comment.DocLink) string {
|
||||
importPath := link.ImportPath
|
||||
if importPath == "" {
|
||||
importPath = pkg.doc.ImportPath
|
||||
}
|
||||
name := link.Name
|
||||
if link.Recv != "" {
|
||||
name = link.Recv + "." + name
|
||||
}
|
||||
return "https://pkg.go.dev/" + importPath + "#" + name
|
||||
}
|
||||
w.Write(pr.Markdown(d))
|
||||
}
|
||||
|
||||
// pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the
|
||||
// first time Write is called.
|
||||
type pkgBuffer struct {
|
||||
@@ -85,7 +111,10 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag
|
||||
return slices.Contains(pkg.GoFiles, info.Name()) || slices.Contains(pkg.CgoFiles, info.Name())
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments|parser.ImportsOnly)
|
||||
// Parse declarations (not just imports) so that doc.Package knows the
|
||||
// package's symbols; the Markdown printer needs this to resolve
|
||||
// [Symbol] doc links in package comments.
|
||||
pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -144,10 +173,10 @@ func (pkg *Package) newlines(n int) {
|
||||
}
|
||||
}
|
||||
|
||||
// packageDoc prints the docs for the package.
|
||||
// packageDoc prints the docs for the package as Markdown.
|
||||
func (pkg *Package) packageDoc() {
|
||||
pkg.Printf("") // Trigger the package clause; we know the package exists.
|
||||
pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent)
|
||||
pkg.ToMarkdown(&pkg.buf, pkg.doc.Doc)
|
||||
pkg.newlines(1)
|
||||
|
||||
pkg.bugs()
|
||||
@@ -175,8 +204,12 @@ func (pkg *Package) bugs() {
|
||||
}
|
||||
}
|
||||
|
||||
// PackageDoc generates documentation for a package in the given directory.
|
||||
func PackageDoc(dir string) ([]byte, error) {
|
||||
// PackageDoc generates Markdown documentation for the package in the given
|
||||
// directory. importPath is the full Go import path of that package (e.g.
|
||||
// "tailscale.com/tsnet"); it's used to render [Symbol] doc links to the
|
||||
// right pkg.go.dev URL. If importPath is empty, build.ImportDir's guess
|
||||
// is used (typically "." for module-based repos).
|
||||
func PackageDoc(dir, importPath string) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var writer io.Writer = &buf
|
||||
|
||||
@@ -188,6 +221,9 @@ func PackageDoc(dir string) ([]byte, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if importPath != "" {
|
||||
buildPackage.ImportPath = importPath
|
||||
}
|
||||
userPath := dir
|
||||
|
||||
pkg := parsePackage(writer, buildPackage, userPath)
|
||||
|
||||
Reference in New Issue
Block a user