utilize wand
This commit is contained in:
parent
40bd187975
commit
20bb6895bf
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
|||||||
.bin
|
.build
|
||||||
testdocs_test.go
|
testdocs_test.go
|
||||||
.cover
|
.cover
|
||||||
|
|||||||
51
Makefile
51
Makefile
@ -1,5 +1,8 @@
|
|||||||
sources = $(shell find . -name "*.go" | grep -v testdocs_test.go)
|
sources = $(shell find . -name "*.go" | grep -v testdocs_test.go | grep -v cmd/docreflect/docs.gen.go)
|
||||||
prefix ?= ~/bin
|
PREFIX ?= /usr/local
|
||||||
|
prefix ?= $(PREFIX)
|
||||||
|
release_date = $(shell git show -s --format=%cs HEAD)
|
||||||
|
version = $(release_date)-$(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
default: build
|
default: build
|
||||||
|
|
||||||
@ -9,16 +12,33 @@ libdocreflect: $(sources)
|
|||||||
libgenerate: $(sources)
|
libgenerate: $(sources)
|
||||||
go build ./generate
|
go build ./generate
|
||||||
|
|
||||||
.bin:
|
.build:
|
||||||
mkdir -p .bin
|
mkdir -p .build
|
||||||
|
|
||||||
.bin/docreflect: $(sources) .bin
|
.build/docreflect: $(sources) .build cmd/docreflect/docs.gen.go
|
||||||
go build -o .bin/docreflect ./cmd/docreflect
|
go build -o .build/docreflect -ldflags "-X main.version=$(version)" ./cmd/docreflect
|
||||||
|
|
||||||
build: libdocreflect libgenerate .bin/docreflect
|
.build/docreflect.1: $(sources) .build cmd/docreflect/docs.gen.go
|
||||||
|
go run scripts/man.go $(version) $(release_date) ./cmd/docreflect > .build/docreflect.1
|
||||||
|
|
||||||
testdocs_test.go: $(sources) .bin/docreflect
|
cmd/docreflect/readme.md: $(sources) cmd/docreflect/docs.gen.go
|
||||||
.bin/docreflect generate docreflect_test code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage > testdocs_test.go
|
go run scripts/markdown.go ./cmd/docreflect > cmd/docreflect/readme.md
|
||||||
|
|
||||||
|
build: libdocreflect libgenerate .build/docreflect cmd/docreflect/readme.md
|
||||||
|
|
||||||
|
testdocs_test.go: $(sources) .build
|
||||||
|
go run ./cmd/docreflect \
|
||||||
|
docreflect_test \
|
||||||
|
code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage \
|
||||||
|
> .build/testdocs_test.go \
|
||||||
|
&& mv .build/testdocs_test.go testdocs_test.go
|
||||||
|
|
||||||
|
cmd/docreflect/docs.gen.go: $(sources)
|
||||||
|
go run ./cmd/docreflect \
|
||||||
|
main \
|
||||||
|
code.squareroundforest.org/arpio/docreflect/generate \
|
||||||
|
> .build/docs.gen.go \
|
||||||
|
&& mv .build/docs.gen.go cmd/docreflect/docs.gen.go
|
||||||
|
|
||||||
.cover: $(sources) testdocs_test.go
|
.cover: $(sources) testdocs_test.go
|
||||||
go test -count 1 -coverprofile .cover . ./generate
|
go test -count 1 -coverprofile .cover . ./generate
|
||||||
@ -32,12 +52,17 @@ showcover: .cover
|
|||||||
go tool cover -html .cover
|
go tool cover -html .cover
|
||||||
|
|
||||||
fmt: $(sources)
|
fmt: $(sources)
|
||||||
go fmt . ./generate ./cmd/docreflect
|
go fmt . ./generate ./cmd/docreflect ./scripts
|
||||||
|
|
||||||
install: .bin/docreflect
|
$(prefix)/bin/docreflect: .build/docreflect
|
||||||
cp .bin/docreflect $(prefix)
|
cp .build/docreflect $(prefix)/bin/docreflect
|
||||||
|
|
||||||
|
$(prefix)/share/man/man1/docreflect.1: .build/docreflect.1
|
||||||
|
cp .build/docreflect.1 $(prefix)/share/man/man1/docreflect.1
|
||||||
|
|
||||||
|
install: $(prefix)/bin/docreflect $(prefix)/share/man/man1/docreflect.1
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean ./...
|
go clean ./...
|
||||||
rm -rf .bin
|
rm -rf .build
|
||||||
rm -f testdocs_test.go
|
rm -f testdocs_test.go
|
||||||
|
|||||||
55
cmd/docreflect/docs.gen.go
Normal file
55
cmd/docreflect/docs.gen.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "code.squareroundforest.org/arpio/docreflect"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate", "Package generate produces Go source code that registers documentation for use with the docreflect package.\n")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.GenerateRegistry", "GenerateRegistry generates a Go source file containing an init function that registers the documentation for\nthe declarations specified by their fully qualified Go path.\n\nThe paths argument accepts arbitrary packages, package-level symbols, or struct fields. Usage of package\npaths is recommended over specific symbols in most cases.\n\nLimitations:\n\n- Type references (such as type aliases or definitions based on named types) are not resolved.\n\n- Import paths are not followed.\n\nfunc(o, w, outputPackageName, path)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.Options", "Options contains options for the generator.\n")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.Options.Main", "Main indicates that the docs for the symbols will be lookded up as part of the main package of an\nexecutable. This is necessary, because the fully qualified Go path of symbols in a main package differs\nfrom the path pointing to that package in a module.\n")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.cleanPaths", "\nfunc(gopath)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.collectGoDirs", "\nfunc(o)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.findFieldDocs", "\nfunc(str, fieldPath)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.findGoMod", "\nfunc(dir)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.fixDocPackage", "\nfunc(p)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.format", "\nfunc(w, pname, docs)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.funcDocs", "\nfunc(f)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.funcParams", "\nfunc(f)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.generate", "\nfunc(o, gopaths)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.getGoroot", "\nfunc()")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.header", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.importPackages", "\nfunc(o, godirs, paths)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.initOptions", "\nfunc()")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.merge", "\nfunc(m)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.methodDocs", "\nfunc(importPath, t)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.modCache", "\nfunc()")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.goroot", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.isMain", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.modules", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.wd", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.packageDocs", "\nfunc(pkg)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.packageFuncDocs", "\nfunc(importPath, funcs)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.packagePaths", "\nfunc(p)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.parsePackages", "\nfunc(pkgs)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.parserInclude", "\nfunc(pkg)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.readGomod", "\nfunc(wd)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.replacePath", "\nfunc(p, prefix, replace)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.replacePaths", "\nfunc(m, prefix, replace)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.set", "\nfunc(m, key, value)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.splitGopath", "\nfunc(p)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.structFieldDocs", "\nfunc(t, fieldPath)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.structFieldsDocs", "\nfunc(importPath, t)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.symbolDocs", "\nfunc(pkg, gopath)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.symbolPath", "\nfunc(packagePath, name)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.takeDocs", "\nfunc(o, pkgs, gopaths)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.takeFieldDocs", "\nfunc(packagePath, prefix, f)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.typeDocs", "\nfunc(importPath, types)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.typeMethodDocs", "\nfunc(t, name)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.unpack", "\nfunc(e)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.valueDocs", "\nfunc(packagePath, v)")
|
||||||
|
}
|
||||||
@ -2,18 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"code.squareroundforest.org/arpio/docreflect/generate"
|
"code.squareroundforest.org/arpio/docreflect/generate"
|
||||||
"log"
|
"code.squareroundforest.org/arpio/wand"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
var version = "dev"
|
||||||
args := os.Args[1:]
|
|
||||||
if args[0] == "generate" {
|
|
||||||
args = args[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
packageName, args := args[0], args[1:]
|
func main() {
|
||||||
if err := generate.GenerateRegistry(generate.Options{}, os.Stdout, packageName, args...); err != nil {
|
cmd := wand.Command("generate-registry", generate.GenerateRegistry)
|
||||||
log.Fatalln(err)
|
cmd = wand.Default(cmd)
|
||||||
}
|
cmd = wand.ShortForm(cmd, "m", "main")
|
||||||
|
cmd = wand.Group("docreflect", cmd)
|
||||||
|
cmd = wand.Version(cmd, version)
|
||||||
|
wand.Exec(cmd)
|
||||||
}
|
}
|
||||||
|
|||||||
90
cmd/docreflect/readme.md
Normal file
90
cmd/docreflect/readme.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# docreflect
|
||||||
|
|
||||||
|
## Synopsis:
|
||||||
|
|
||||||
|
```
|
||||||
|
docreflect [options]... [--] <outputPackageName string> [path string]...
|
||||||
|
docreflect <subcommand>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Description:
|
||||||
|
|
||||||
|
generates a Go source file containing an init function that registers the documentation for the declarations
|
||||||
|
specified by their fully qualified Go path.
|
||||||
|
|
||||||
|
The paths argument accepts arbitrary packages, package-level symbols, or struct fields. Usage of package paths
|
||||||
|
is recommended over specific symbols in most cases.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
\- Type references (such as type aliases or definitions based on named types) are not resolved.
|
||||||
|
|
||||||
|
\- Import paths are not followed.
|
||||||
|
|
||||||
|
## Options:
|
||||||
|
|
||||||
|
- --main, -m bool: indicates that the docs for the symbols will be lookded up as part of the main package of an
|
||||||
|
executable. This is necessary, because the fully qualified Go path of symbols in a main package differs from
|
||||||
|
the path pointing to that package in a module.
|
||||||
|
- --help: Show help.
|
||||||
|
|
||||||
|
Hints:
|
||||||
|
|
||||||
|
- Bool options can be used with implicit true values.
|
||||||
|
- Option values can be set both via = or just separated by space.
|
||||||
|
|
||||||
|
## Subcommands:
|
||||||
|
|
||||||
|
Show help for each subcommand by calling \<command\> help or \<command\> --help.
|
||||||
|
|
||||||
|
### docreflect generate-registry (default)
|
||||||
|
|
||||||
|
#### Synopsis:
|
||||||
|
|
||||||
|
```
|
||||||
|
docreflect generate-registry [options]... [--] <outputPackageName string> [path string]...
|
||||||
|
docreflect generate-registry <subcommand>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Description:
|
||||||
|
|
||||||
|
generates a Go source file containing an init function that registers the documentation for the declarations
|
||||||
|
specified by their fully qualified Go path.
|
||||||
|
|
||||||
|
The paths argument accepts arbitrary packages, package-level symbols, or struct fields. Usage of package paths
|
||||||
|
is recommended over specific symbols in most cases.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
\- Type references (such as type aliases or definitions based on named types) are not resolved.
|
||||||
|
|
||||||
|
\- Import paths are not followed.
|
||||||
|
|
||||||
|
#### Options:
|
||||||
|
|
||||||
|
- --main, -m bool: indicates that the docs for the symbols will be lookded up as part of the main package of an
|
||||||
|
executable. This is necessary, because the fully qualified Go path of symbols in a main package differs from
|
||||||
|
the path pointing to that package in a module.
|
||||||
|
- --help: Show help.
|
||||||
|
|
||||||
|
### docreflect version
|
||||||
|
|
||||||
|
Show version.
|
||||||
|
|
||||||
|
## Environment variables:
|
||||||
|
|
||||||
|
Every command line option's value can also be provided as an environment variable. Environment variable names
|
||||||
|
need to use snake casing like myapp\_foo\_bar\_baz or MYAPP\_FOO\_BAR\_BAZ, or other casing that doesn't include the
|
||||||
|
'-' dash character, and they need to be prefixed with the name of the application, as in the base name of the
|
||||||
|
command.
|
||||||
|
|
||||||
|
When both the environment variable and the command line option is defined, the command line option overrides the
|
||||||
|
environment variable. Multiple values for the same environment variable can be defined by concatenating the
|
||||||
|
values with the ':' separator character. When overriding multiple values with command line options, all the
|
||||||
|
environment values of the same field are dropped.
|
||||||
|
|
||||||
|
### Example environment variable:
|
||||||
|
|
||||||
|
```
|
||||||
|
DOCREFLECT_MAIN=true
|
||||||
|
```
|
||||||
651
generate/generate.go
Normal file
651
generate/generate.go
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/doc"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"golang.org/x/mod/modfile"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const header = `/*
|
||||||
|
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||||
|
*/
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
wd string
|
||||||
|
goroot string
|
||||||
|
modules map[string]string
|
||||||
|
isMain bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoroot() string {
|
||||||
|
gr := os.Getenv("GOROOT")
|
||||||
|
if gr != "" {
|
||||||
|
return gr
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtime.GOROOT()
|
||||||
|
}
|
||||||
|
|
||||||
|
func modCache() string {
|
||||||
|
mc := os.Getenv("GOMODCACHE")
|
||||||
|
if mc != "" {
|
||||||
|
return mc
|
||||||
|
}
|
||||||
|
|
||||||
|
gp := os.Getenv("GOPATH")
|
||||||
|
if gp == "" {
|
||||||
|
gp = path.Join(os.Getenv("HOME"), "go")
|
||||||
|
}
|
||||||
|
|
||||||
|
mc = path.Join(gp, "pkg/mod")
|
||||||
|
return mc
|
||||||
|
}
|
||||||
|
|
||||||
|
func findGoMod(dir string) (string, bool) {
|
||||||
|
p := path.Join(dir, "go.mod")
|
||||||
|
f, err := os.Stat(p)
|
||||||
|
if err == nil && !f.IsDir() {
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir == "/" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return findGoMod(path.Dir(dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func readGomod(wd string) map[string]string {
|
||||||
|
p, ok := findGoMod(wd)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := os.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := modfile.Parse(p, d, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs map[string]string
|
||||||
|
mc := modCache()
|
||||||
|
for _, dep := range m.Require {
|
||||||
|
p := dep.Mod.String()
|
||||||
|
dirs = set(dirs, p, path.Join(mc, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
name := m.Module.Mod.String()
|
||||||
|
dirs[name] = path.Dir(p)
|
||||||
|
return dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
func initOptions() options {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
gr := getGoroot()
|
||||||
|
modules := readGomod(wd)
|
||||||
|
return options{
|
||||||
|
wd: wd,
|
||||||
|
goroot: gr,
|
||||||
|
modules: modules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanPaths(gopath []string) []string {
|
||||||
|
var c []string
|
||||||
|
for _, pi := range gopath {
|
||||||
|
pi = path.Clean(pi)
|
||||||
|
c = append(c, pi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitGopath(p string) (string, string) {
|
||||||
|
parts := strings.Split(p, "/")
|
||||||
|
last := len(parts) - 1
|
||||||
|
parts, lastPart := parts[:last], parts[last]
|
||||||
|
symbolParts := strings.Split(lastPart, ".")
|
||||||
|
pkg := symbolParts[0]
|
||||||
|
return strings.Join(append(parts, pkg), "/"), strings.Join(symbolParts[1:], ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func packagePaths(p []string) []string {
|
||||||
|
var (
|
||||||
|
pp []string
|
||||||
|
m map[string]bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, pi := range p {
|
||||||
|
ppi, _ := splitGopath(pi)
|
||||||
|
if m[ppi] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pp = append(pp, ppi)
|
||||||
|
m = set(m, ppi, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectGoDirs(o options) []string {
|
||||||
|
var dirs []string
|
||||||
|
if o.goroot != "" {
|
||||||
|
dirs = append(dirs, path.Join(o.goroot, "src"))
|
||||||
|
dirs = append(dirs, path.Join(o.goroot, "src", "cmd"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
func importPackages(o options, godirs, paths []string) ([]*build.Package, error) {
|
||||||
|
var pkgs []*build.Package
|
||||||
|
for _, p := range paths {
|
||||||
|
var found bool
|
||||||
|
for mod, modDir := range o.modules {
|
||||||
|
if !strings.HasPrefix(p, strings.Split(mod, "@")[0]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg, err := build.Import(p, modDir, build.ImportComment)
|
||||||
|
if err != nil || pkg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs = append(pkgs, pkg)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range godirs {
|
||||||
|
pkg, err := build.Import(p, d, build.ImportComment)
|
||||||
|
if err != nil || pkg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs = append(pkgs, pkg)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("failed to import package for %s", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserInclude(pkg *build.Package) func(fs.FileInfo) bool {
|
||||||
|
return func(file fs.FileInfo) bool {
|
||||||
|
for _, fn := range pkg.GoFiles {
|
||||||
|
if fn == file.Name() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePackages(pkgs []*build.Package) (map[string][]*ast.Package, error) {
|
||||||
|
var ppkgs map[string][]*ast.Package
|
||||||
|
for _, p := range pkgs {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
pm, err := parser.ParseDir(fset, p.Dir, parserInclude(p), parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse package %s: %w", p.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pp := range pm {
|
||||||
|
if pp == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ppkgs = set(ppkgs, p.ImportPath, append(ppkgs[p.ImportPath], pp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ppkgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixDocPackage(p doc.Package) doc.Package {
|
||||||
|
for _, t := range p.Types {
|
||||||
|
p.Consts = append(p.Consts, t.Consts...)
|
||||||
|
p.Vars = append(p.Vars, t.Vars...)
|
||||||
|
p.Funcs = append(p.Funcs, t.Funcs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func set[K comparable, V any](m map[K]V, key K, value V) map[K]V {
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[K]V)
|
||||||
|
}
|
||||||
|
|
||||||
|
m[key] = value
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge[K comparable, V any](m ...map[K]V) map[K]V {
|
||||||
|
if len(m) == 1 {
|
||||||
|
return m[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var mm map[K]V
|
||||||
|
for key, value := range m[0] {
|
||||||
|
mm = set(mm, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
mr := merge(m[1:]...)
|
||||||
|
for key, value := range mr {
|
||||||
|
mm = set(mm, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mm
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpack(e ast.Expr) ast.Expr {
|
||||||
|
p, ok := e.(*ast.StarExpr)
|
||||||
|
if !ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return unpack(p.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func funcParams(f *doc.Func) string {
|
||||||
|
if f.Decl == nil || f.Decl.Type == nil || f.Decl.Type.Params == nil {
|
||||||
|
return "func()"
|
||||||
|
}
|
||||||
|
|
||||||
|
var paramNames []string
|
||||||
|
for _, p := range f.Decl.Type.Params.List {
|
||||||
|
for _, n := range p.Names {
|
||||||
|
if n == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
paramNames = append(paramNames, n.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("func(%s)", strings.Join(paramNames, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func funcDocs(f *doc.Func) string {
|
||||||
|
return fmt.Sprintf("%s\n%s", f.Doc, funcParams(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFieldDocs(str *ast.StructType, fieldPath []string) (string, bool) {
|
||||||
|
if str.Fields == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range str.Fields.List {
|
||||||
|
var found bool
|
||||||
|
for _, fn := range f.Names {
|
||||||
|
if fn != nil && fn.Name == fieldPath[0] {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fieldPath) == 1 {
|
||||||
|
if f.Doc == nil {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Doc.Text(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
te := unpack(f.Type)
|
||||||
|
fstr, ok := te.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return findFieldDocs(fstr, fieldPath[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func structFieldDocs(t *doc.Type, fieldPath []string) (string, bool) {
|
||||||
|
if t.Decl == nil || len(t.Decl.Specs) != 1 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, ok := t.Decl.Specs[0].(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
te := unpack(ts.Type)
|
||||||
|
str, ok := te.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return findFieldDocs(str, fieldPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeMethodDocs(t *doc.Type, name string) (string, bool) {
|
||||||
|
for _, m := range t.Methods {
|
||||||
|
if m.Name != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return funcDocs(m), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func symbolDocs(pkg *doc.Package, gopath string) (string, bool) {
|
||||||
|
_, s := splitGopath(gopath)
|
||||||
|
symbol := strings.Split(s, ".")
|
||||||
|
if len(symbol) == 1 {
|
||||||
|
for _, c := range pkg.Consts {
|
||||||
|
if c == nil || !slices.Contains(c.Names, symbol[0]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Doc, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range pkg.Vars {
|
||||||
|
if v == nil || !slices.Contains(v.Names, symbol[0]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.Doc, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range pkg.Funcs {
|
||||||
|
if f == nil || f.Name != symbol[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return funcDocs(f), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range pkg.Types {
|
||||||
|
if t == nil || t.Name != symbol[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(symbol) == 1 {
|
||||||
|
return t.Doc, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if d, ok := structFieldDocs(t, symbol[1:]); ok {
|
||||||
|
return d, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(symbol) != 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if d, ok := typeMethodDocs(t, symbol[1]); ok {
|
||||||
|
return d, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func symbolPath(packagePath string, name ...string) string {
|
||||||
|
return fmt.Sprintf("%s.%s", packagePath, strings.Join(name, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueDocs(packagePath string, v []*doc.Value) map[string]string {
|
||||||
|
var d map[string]string
|
||||||
|
for _, vi := range v {
|
||||||
|
if vi != nil {
|
||||||
|
for _, n := range vi.Names {
|
||||||
|
d = set(d, symbolPath(packagePath, n), vi.Doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func takeFieldDocs(packagePath string, prefix []string, f *ast.Field) map[string]string {
|
||||||
|
var docs map[string]string
|
||||||
|
for _, fn := range f.Names {
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
docs = set(docs, symbolPath(packagePath, append(prefix, fn.Name)...), f.Doc.Text())
|
||||||
|
te := unpack(f.Type)
|
||||||
|
fst, ok := te.(*ast.StructType)
|
||||||
|
if !ok || fst.Fields == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fst.Fields.List {
|
||||||
|
if fi == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
docs = merge(docs, takeFieldDocs(packagePath, append(prefix, fn.Name), fi))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
|
||||||
|
func structFieldsDocs(importPath string, t *doc.Type) map[string]string {
|
||||||
|
if t.Decl == nil || len(t.Decl.Specs) != 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, ok := t.Decl.Specs[0].(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
te := unpack(ts.Type)
|
||||||
|
str, ok := te.(*ast.StructType)
|
||||||
|
if !ok || str.Fields == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var d map[string]string
|
||||||
|
for _, f := range str.Fields.List {
|
||||||
|
if f == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d = merge(d, takeFieldDocs(importPath, []string{t.Name}, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func methodDocs(importPath string, t *doc.Type) map[string]string {
|
||||||
|
var d map[string]string
|
||||||
|
for _, m := range t.Methods {
|
||||||
|
if m != nil {
|
||||||
|
d = set(d, symbolPath(importPath, t.Name, m.Name), funcDocs(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeDocs(importPath string, types []*doc.Type) map[string]string {
|
||||||
|
var d map[string]string
|
||||||
|
for _, t := range types {
|
||||||
|
if t == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d = set(d, symbolPath(importPath, t.Name), t.Doc)
|
||||||
|
d = merge(d, structFieldsDocs(importPath, t))
|
||||||
|
d = merge(d, methodDocs(importPath, t))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageFuncDocs(importPath string, funcs []*doc.Func) map[string]string {
|
||||||
|
var d map[string]string
|
||||||
|
for _, f := range funcs {
|
||||||
|
if f != nil {
|
||||||
|
d = set(d, symbolPath(importPath, f.Name), funcDocs(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageDocs(pkg *doc.Package) map[string]string {
|
||||||
|
return merge(
|
||||||
|
map[string]string{pkg.ImportPath: pkg.Doc},
|
||||||
|
valueDocs(pkg.ImportPath, pkg.Consts),
|
||||||
|
valueDocs(pkg.ImportPath, pkg.Vars),
|
||||||
|
typeDocs(pkg.ImportPath, pkg.Types),
|
||||||
|
packageFuncDocs(pkg.ImportPath, pkg.Funcs),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replacePath(p, prefix, replace string) string {
|
||||||
|
if !strings.HasPrefix(p, prefix) {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s", replace, p[len(prefix):])
|
||||||
|
}
|
||||||
|
|
||||||
|
func replacePaths(m map[string]string, prefix, replace string) map[string]string {
|
||||||
|
var mm map[string]string
|
||||||
|
for p, d := range m {
|
||||||
|
mm = set(mm, replacePath(p, prefix, replace), d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mm
|
||||||
|
}
|
||||||
|
|
||||||
|
func takeDocs(o options, pkgs map[string][]*ast.Package, gopaths []string) (map[string]string, error) {
|
||||||
|
var dm map[string]string
|
||||||
|
for _, gp := range gopaths {
|
||||||
|
pp, _ := splitGopath(gp)
|
||||||
|
isPackage := pp == gp
|
||||||
|
for _, pkg := range pkgs[pp] {
|
||||||
|
dpkg := doc.New(pkg, pp, doc.AllDecls|doc.PreserveAST)
|
||||||
|
*dpkg = fixDocPackage(*dpkg)
|
||||||
|
if isPackage {
|
||||||
|
pd := packageDocs(dpkg)
|
||||||
|
if o.isMain {
|
||||||
|
pd = replacePaths(pd, pp, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
dm = merge(dm, pd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sd, ok := symbolDocs(dpkg, gp)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("symbol not found: %s", gp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.isMain {
|
||||||
|
gp = replacePath(gp, pp, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
dm = set(dm, gp, sd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generate(o options, gopaths ...string) (map[string]string, error) {
|
||||||
|
gopaths = cleanPaths(gopaths)
|
||||||
|
ppaths := packagePaths(gopaths)
|
||||||
|
dirs := collectGoDirs(o)
|
||||||
|
pkgs, err := importPackages(o, dirs, ppaths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ppkgs, err := parsePackages(pkgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return takeDocs(o, ppkgs, gopaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func format(w io.Writer, pname string, docs map[string]string) error {
|
||||||
|
var err error
|
||||||
|
printf := func(f string, a ...any) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintf(w, f, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
println := func(a ...any) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintln(w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(header)
|
||||||
|
printf("package %s\n", pname)
|
||||||
|
println("import \"code.squareroundforest.org/arpio/docreflect\"")
|
||||||
|
println("func init() {")
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
for path := range docs {
|
||||||
|
paths = append(paths, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(paths)
|
||||||
|
for _, path := range paths {
|
||||||
|
doc := docs[path]
|
||||||
|
path, doc = strconv.Quote(path), strconv.Quote(doc)
|
||||||
|
printf("docreflect.Register(%s, %s)\n", path, doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("}")
|
||||||
|
return err
|
||||||
|
}
|
||||||
675
generate/lib.go
675
generate/lib.go
@ -1,677 +1,32 @@
|
|||||||
// Package generate provides a generator to generate go code from go docs that registers doc entries
|
// Package generate produces Go source code that registers documentation for use with the docreflect package.
|
||||||
// for use with the docreflect package.
|
|
||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import "io"
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/doc"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"golang.org/x/mod/modfile"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const header = `/*
|
|
||||||
Generated with https://code.squareroundforest.org/arpio/docreflect
|
|
||||||
*/
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
// Options contains options for the generator.
|
// Options contains options for the generator.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
|
||||||
// Main indicates that the docs for the symbols will be lookded up as part of the main package of an
|
// Main indicates that the docs for the symbols will be lookded up as part of the main package of an
|
||||||
// executable.
|
// executable. This is necessary, because the fully qualified Go path of symbols in a main package differs
|
||||||
|
// from the path pointing to that package in a module.
|
||||||
Main bool
|
Main bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type options struct {
|
// GenerateRegistry generates a Go source file containing an init function that registers the documentation for
|
||||||
wd string
|
// the declarations specified by their fully qualified Go path.
|
||||||
goroot string
|
|
||||||
modules map[string]string
|
|
||||||
isMain bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGoroot() string {
|
|
||||||
gr := os.Getenv("GOROOT")
|
|
||||||
if gr != "" {
|
|
||||||
return gr
|
|
||||||
}
|
|
||||||
|
|
||||||
return runtime.GOROOT()
|
|
||||||
}
|
|
||||||
|
|
||||||
func modCache() string {
|
|
||||||
mc := os.Getenv("GOMODCACHE")
|
|
||||||
if mc != "" {
|
|
||||||
return mc
|
|
||||||
}
|
|
||||||
|
|
||||||
gp := os.Getenv("GOPATH")
|
|
||||||
if gp == "" {
|
|
||||||
gp = path.Join(os.Getenv("HOME"), "go")
|
|
||||||
}
|
|
||||||
|
|
||||||
mc = path.Join(gp, "pkg/mod")
|
|
||||||
return mc
|
|
||||||
}
|
|
||||||
|
|
||||||
func findGoMod(dir string) (string, bool) {
|
|
||||||
p := path.Join(dir, "go.mod")
|
|
||||||
f, err := os.Stat(p)
|
|
||||||
if err == nil && !f.IsDir() {
|
|
||||||
return p, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir == "/" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
return findGoMod(path.Dir(dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func readGomod(wd string) map[string]string {
|
|
||||||
p, ok := findGoMod(wd)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := os.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := modfile.Parse(p, d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirs map[string]string
|
|
||||||
mc := modCache()
|
|
||||||
for _, dep := range m.Require {
|
|
||||||
p := dep.Mod.String()
|
|
||||||
dirs = set(dirs, p, path.Join(mc, p))
|
|
||||||
}
|
|
||||||
|
|
||||||
name := m.Module.Mod.String()
|
|
||||||
dirs[name] = path.Dir(p)
|
|
||||||
return dirs
|
|
||||||
}
|
|
||||||
|
|
||||||
func initOptions() options {
|
|
||||||
wd, _ := os.Getwd()
|
|
||||||
gr := getGoroot()
|
|
||||||
modules := readGomod(wd)
|
|
||||||
return options{
|
|
||||||
wd: wd,
|
|
||||||
goroot: gr,
|
|
||||||
modules: modules,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanPaths(gopath []string) []string {
|
|
||||||
var c []string
|
|
||||||
for _, pi := range gopath {
|
|
||||||
pi = path.Clean(pi)
|
|
||||||
c = append(c, pi)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitGopath(p string) (string, string) {
|
|
||||||
parts := strings.Split(p, "/")
|
|
||||||
last := len(parts) - 1
|
|
||||||
parts, lastPart := parts[:last], parts[last]
|
|
||||||
symbolParts := strings.Split(lastPart, ".")
|
|
||||||
pkg := symbolParts[0]
|
|
||||||
return strings.Join(append(parts, pkg), "/"), strings.Join(symbolParts[1:], ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func packagePaths(p []string) []string {
|
|
||||||
var (
|
|
||||||
pp []string
|
|
||||||
m map[string]bool
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, pi := range p {
|
|
||||||
ppi, _ := splitGopath(pi)
|
|
||||||
if m[ppi] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pp = append(pp, ppi)
|
|
||||||
m = set(m, ppi, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pp
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectGoDirs(o options) []string {
|
|
||||||
var dirs []string
|
|
||||||
if o.goroot != "" {
|
|
||||||
dirs = append(dirs, path.Join(o.goroot, "src"))
|
|
||||||
dirs = append(dirs, path.Join(o.goroot, "src", "cmd"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return dirs
|
|
||||||
}
|
|
||||||
|
|
||||||
func importPackages(o options, godirs, paths []string) ([]*build.Package, error) {
|
|
||||||
var pkgs []*build.Package
|
|
||||||
for _, p := range paths {
|
|
||||||
var found bool
|
|
||||||
for mod, modDir := range o.modules {
|
|
||||||
if !strings.HasPrefix(p, strings.Split(mod, "@")[0]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg, err := build.Import(p, modDir, build.ImportComment)
|
|
||||||
if err != nil || pkg == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range godirs {
|
|
||||||
pkg, err := build.Import(p, d, build.ImportComment)
|
|
||||||
if err != nil || pkg == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return nil, fmt.Errorf("failed to import package for %s", p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkgs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parserInclude(pkg *build.Package) func(fs.FileInfo) bool {
|
|
||||||
return func(file fs.FileInfo) bool {
|
|
||||||
for _, fn := range pkg.GoFiles {
|
|
||||||
if fn == file.Name() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePackages(pkgs []*build.Package) (map[string][]*ast.Package, error) {
|
|
||||||
var ppkgs map[string][]*ast.Package
|
|
||||||
for _, p := range pkgs {
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
pm, err := parser.ParseDir(fset, p.Dir, parserInclude(p), parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse package %s: %w", p.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pp := range pm {
|
|
||||||
if pp == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ppkgs = set(ppkgs, p.ImportPath, append(ppkgs[p.ImportPath], pp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ppkgs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixDocPackage(p doc.Package) doc.Package {
|
|
||||||
for _, t := range p.Types {
|
|
||||||
p.Consts = append(p.Consts, t.Consts...)
|
|
||||||
p.Vars = append(p.Vars, t.Vars...)
|
|
||||||
p.Funcs = append(p.Funcs, t.Funcs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func set[K comparable, V any](m map[K]V, key K, value V) map[K]V {
|
|
||||||
if m == nil {
|
|
||||||
m = make(map[K]V)
|
|
||||||
}
|
|
||||||
|
|
||||||
m[key] = value
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func merge[K comparable, V any](m ...map[K]V) map[K]V {
|
|
||||||
if len(m) == 1 {
|
|
||||||
return m[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var mm map[K]V
|
|
||||||
for key, value := range m[0] {
|
|
||||||
mm = set(mm, key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
mr := merge(m[1:]...)
|
|
||||||
for key, value := range mr {
|
|
||||||
mm = set(mm, key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mm
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpack(e ast.Expr) ast.Expr {
|
|
||||||
p, ok := e.(*ast.StarExpr)
|
|
||||||
if !ok {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return unpack(p.X)
|
|
||||||
}
|
|
||||||
|
|
||||||
func funcParams(f *doc.Func) string {
|
|
||||||
if f.Decl == nil || f.Decl.Type == nil || f.Decl.Type.Params == nil {
|
|
||||||
return "func()"
|
|
||||||
}
|
|
||||||
|
|
||||||
var paramNames []string
|
|
||||||
for _, p := range f.Decl.Type.Params.List {
|
|
||||||
for _, n := range p.Names {
|
|
||||||
if n == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
paramNames = append(paramNames, n.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("func(%s)", strings.Join(paramNames, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func funcDocs(f *doc.Func) string {
|
|
||||||
return fmt.Sprintf("%s\n%s", f.Doc, funcParams(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
func findFieldDocs(str *ast.StructType, fieldPath []string) (string, bool) {
|
|
||||||
if str.Fields == nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range str.Fields.List {
|
|
||||||
var found bool
|
|
||||||
for _, fn := range f.Names {
|
|
||||||
if fn != nil && fn.Name == fieldPath[0] {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fieldPath) == 1 {
|
|
||||||
if f.Doc == nil {
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.Doc.Text(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
te := unpack(f.Type)
|
|
||||||
fstr, ok := te.(*ast.StructType)
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
return findFieldDocs(fstr, fieldPath[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func structFieldDocs(t *doc.Type, fieldPath []string) (string, bool) {
|
|
||||||
if t.Decl == nil || len(t.Decl.Specs) != 1 {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
ts, ok := t.Decl.Specs[0].(*ast.TypeSpec)
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
te := unpack(ts.Type)
|
|
||||||
str, ok := te.(*ast.StructType)
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
return findFieldDocs(str, fieldPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeMethodDocs(t *doc.Type, name string) (string, bool) {
|
|
||||||
for _, m := range t.Methods {
|
|
||||||
if m.Name != name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return funcDocs(m), true
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func symbolDocs(pkg *doc.Package, gopath string) (string, bool) {
|
|
||||||
_, s := splitGopath(gopath)
|
|
||||||
symbol := strings.Split(s, ".")
|
|
||||||
if len(symbol) == 1 {
|
|
||||||
for _, c := range pkg.Consts {
|
|
||||||
if c == nil || !slices.Contains(c.Names, symbol[0]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Doc, true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range pkg.Vars {
|
|
||||||
if v == nil || !slices.Contains(v.Names, symbol[0]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.Doc, true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range pkg.Funcs {
|
|
||||||
if f == nil || f.Name != symbol[0] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return funcDocs(f), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range pkg.Types {
|
|
||||||
if t == nil || t.Name != symbol[0] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(symbol) == 1 {
|
|
||||||
return t.Doc, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if d, ok := structFieldDocs(t, symbol[1:]); ok {
|
|
||||||
return d, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(symbol) != 2 {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
if d, ok := typeMethodDocs(t, symbol[1]); ok {
|
|
||||||
return d, ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func symbolPath(packagePath string, name ...string) string {
|
|
||||||
return fmt.Sprintf("%s.%s", packagePath, strings.Join(name, "."))
|
|
||||||
}
|
|
||||||
|
|
||||||
func valueDocs(packagePath string, v []*doc.Value) map[string]string {
|
|
||||||
var d map[string]string
|
|
||||||
for _, vi := range v {
|
|
||||||
if vi != nil {
|
|
||||||
for _, n := range vi.Names {
|
|
||||||
d = set(d, symbolPath(packagePath, n), vi.Doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func takeFieldDocs(packagePath string, prefix []string, f *ast.Field) map[string]string {
|
|
||||||
var docs map[string]string
|
|
||||||
for _, fn := range f.Names {
|
|
||||||
if fn == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
docs = set(docs, symbolPath(packagePath, append(prefix, fn.Name)...), f.Doc.Text())
|
|
||||||
te := unpack(f.Type)
|
|
||||||
fst, ok := te.(*ast.StructType)
|
|
||||||
if !ok || fst.Fields == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range fst.Fields.List {
|
|
||||||
if fi == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
docs = merge(docs, takeFieldDocs(packagePath, append(prefix, fn.Name), fi))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return docs
|
|
||||||
}
|
|
||||||
|
|
||||||
func structFieldsDocs(importPath string, t *doc.Type) map[string]string {
|
|
||||||
if t.Decl == nil || len(t.Decl.Specs) != 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ts, ok := t.Decl.Specs[0].(*ast.TypeSpec)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
te := unpack(ts.Type)
|
|
||||||
str, ok := te.(*ast.StructType)
|
|
||||||
if !ok || str.Fields == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var d map[string]string
|
|
||||||
for _, f := range str.Fields.List {
|
|
||||||
if f == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d = merge(d, takeFieldDocs(importPath, []string{t.Name}, f))
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func methodDocs(importPath string, t *doc.Type) map[string]string {
|
|
||||||
var d map[string]string
|
|
||||||
for _, m := range t.Methods {
|
|
||||||
if m != nil {
|
|
||||||
d = set(d, symbolPath(importPath, t.Name, m.Name), funcDocs(m))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeDocs(importPath string, types []*doc.Type) map[string]string {
|
|
||||||
var d map[string]string
|
|
||||||
for _, t := range types {
|
|
||||||
if t == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d = set(d, symbolPath(importPath, t.Name), t.Doc)
|
|
||||||
d = merge(d, structFieldsDocs(importPath, t))
|
|
||||||
d = merge(d, methodDocs(importPath, t))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func packageFuncDocs(importPath string, funcs []*doc.Func) map[string]string {
|
|
||||||
var d map[string]string
|
|
||||||
for _, f := range funcs {
|
|
||||||
if f != nil {
|
|
||||||
d = set(d, symbolPath(importPath, f.Name), funcDocs(f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func packageDocs(pkg *doc.Package) map[string]string {
|
|
||||||
return merge(
|
|
||||||
map[string]string{pkg.ImportPath: pkg.Doc},
|
|
||||||
valueDocs(pkg.ImportPath, pkg.Consts),
|
|
||||||
valueDocs(pkg.ImportPath, pkg.Vars),
|
|
||||||
typeDocs(pkg.ImportPath, pkg.Types),
|
|
||||||
packageFuncDocs(pkg.ImportPath, pkg.Funcs),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func replacePath(p, prefix, replace string) string {
|
|
||||||
if !strings.HasPrefix(p, prefix) {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s", replace, p[len(prefix):])
|
|
||||||
}
|
|
||||||
|
|
||||||
func replacePaths(m map[string]string, prefix, replace string) map[string]string {
|
|
||||||
var mm map[string]string
|
|
||||||
for p, d := range m {
|
|
||||||
mm = set(mm, replacePath(p, prefix, replace), d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mm
|
|
||||||
}
|
|
||||||
|
|
||||||
func takeDocs(o options, pkgs map[string][]*ast.Package, gopaths []string) (map[string]string, error) {
|
|
||||||
var dm map[string]string
|
|
||||||
for _, gp := range gopaths {
|
|
||||||
pp, _ := splitGopath(gp)
|
|
||||||
isPackage := pp == gp
|
|
||||||
for _, pkg := range pkgs[pp] {
|
|
||||||
dpkg := doc.New(pkg, pp, doc.AllDecls|doc.PreserveAST)
|
|
||||||
*dpkg = fixDocPackage(*dpkg)
|
|
||||||
if isPackage {
|
|
||||||
pd := packageDocs(dpkg)
|
|
||||||
if o.isMain {
|
|
||||||
pd = replacePaths(pd, pp, "main")
|
|
||||||
}
|
|
||||||
|
|
||||||
dm = merge(dm, pd)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sd, ok := symbolDocs(dpkg, gp)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("symbol not found: %s", gp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.isMain {
|
|
||||||
gp = replacePath(gp, pp, "main")
|
|
||||||
}
|
|
||||||
|
|
||||||
dm = set(dm, gp, sd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generate(o options, gopaths ...string) (map[string]string, error) {
|
|
||||||
gopaths = cleanPaths(gopaths)
|
|
||||||
ppaths := packagePaths(gopaths)
|
|
||||||
dirs := collectGoDirs(o)
|
|
||||||
pkgs, err := importPackages(o, dirs, ppaths)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ppkgs, err := parsePackages(pkgs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return takeDocs(o, ppkgs, gopaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func format(w io.Writer, pname string, docs map[string]string) error {
|
|
||||||
var err error
|
|
||||||
printf := func(f string, a ...any) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintf(w, f, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
println := func(a ...any) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintln(w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
println(header)
|
|
||||||
printf("package %s\n", pname)
|
|
||||||
println("import \"code.squareroundforest.org/arpio/docreflect\"")
|
|
||||||
println("func init() {")
|
|
||||||
|
|
||||||
var paths []string
|
|
||||||
for path := range docs {
|
|
||||||
paths = append(paths, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(paths)
|
|
||||||
for _, path := range paths {
|
|
||||||
doc := docs[path]
|
|
||||||
path, doc = strconv.Quote(path), strconv.Quote(doc)
|
|
||||||
printf("docreflect.Register(%s, %s)\n", path, doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("}")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRegistry generates a Go code file to the output, including a package init function that
|
|
||||||
// will register the documentation of the declarations specified by their gopath.
|
|
||||||
//
|
//
|
||||||
// The gopath argument accepts any number of package, package level symbol, or struct field paths.
|
// The paths argument accepts arbitrary packages, package-level symbols, or struct fields. Usage of package
|
||||||
// It is recommended to use package paths unless special circumstances.
|
// paths is recommended over specific symbols in most cases.
|
||||||
//
|
//
|
||||||
// Some important gotchas to keep in mind, GenerateRegistry does not resolve type references like
|
// Limitations:
|
||||||
// type aliases, or type definitions based on named types, and it doesn't follow import paths.
|
//
|
||||||
func GenerateRegistry(o Options, w io.Writer, outputPackageName string, gopath ...string) error {
|
// - Type references (such as type aliases or definitions based on named types) are not resolved.
|
||||||
|
//
|
||||||
|
// - Import paths are not followed.
|
||||||
|
func GenerateRegistry(o Options, w io.Writer, outputPackageName string, path ...string) error {
|
||||||
oo := initOptions()
|
oo := initOptions()
|
||||||
oo.isMain = o.Main
|
oo.isMain = o.Main
|
||||||
d, err := generate(oo, gopath...)
|
d, err := generate(oo, path...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
15
go.mod
15
go.mod
@ -1,10 +1,19 @@
|
|||||||
module code.squareroundforest.org/arpio/docreflect
|
module code.squareroundforest.org/arpio/docreflect
|
||||||
|
|
||||||
go 1.24.3
|
go 1.25.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174
|
code.squareroundforest.org/arpio/notation v0.0.0-20251101123932-5f5c05ee0239
|
||||||
|
code.squareroundforest.org/arpio/wand v0.0.0-20260115221425-3b0fa9ff1ec4
|
||||||
golang.org/x/mod v0.27.0
|
golang.org/x/mod v0.27.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/aryszka/notation v0.0.0-20230129164653-172017dde5e4 // indirect
|
require (
|
||||||
|
code.squareroundforest.org/arpio/bind v0.0.0-20251105181644-3443251be2d5 // indirect
|
||||||
|
code.squareroundforest.org/arpio/html v0.0.0-20251103020946-e262eca50ac9 // indirect
|
||||||
|
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f // indirect
|
||||||
|
code.squareroundforest.org/arpio/textfmt v0.0.0-20251207234108-fed32c8bbe18 // indirect
|
||||||
|
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||||
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
|
golang.org/x/term v0.37.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
22
go.sum
22
go.sum
@ -1,6 +1,20 @@
|
|||||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174 h1:DKMSagVY3uyRhJ4ohiwQzNnR6CWdVKLkg97A8eQGxQU=
|
code.squareroundforest.org/arpio/bind v0.0.0-20251105181644-3443251be2d5 h1:SIgLIawD6Vv7rAvUobpVshLshdwFEJ0NOUrWpheS088=
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
code.squareroundforest.org/arpio/bind v0.0.0-20251105181644-3443251be2d5/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||||
github.com/aryszka/notation v0.0.0-20230129164653-172017dde5e4 h1:JzqT9RArcw2sD4QPAyTss/sHaCZvCv+91DDJPZOrShw=
|
code.squareroundforest.org/arpio/html v0.0.0-20251103020946-e262eca50ac9 h1:b7voJlwe0jKH568X+O7b/JTAUrHLTSKNSSL+hhV2Q/Q=
|
||||||
github.com/aryszka/notation v0.0.0-20230129164653-172017dde5e4/go.mod h1:myJFmFAZ/75y5xdA1jjpc4ItNJwdRqaL+TQhIvDU8Vk=
|
code.squareroundforest.org/arpio/html v0.0.0-20251103020946-e262eca50ac9/go.mod h1:hq+2CENEd4bVSZnOdq38FUFOJJnF3OTQRv78qMGkNlE=
|
||||||
|
code.squareroundforest.org/arpio/notation v0.0.0-20251101123932-5f5c05ee0239 h1:JvLVMuvF2laxXkIZbHC1/0xtKyKndAwIHbIIWkHqTzc=
|
||||||
|
code.squareroundforest.org/arpio/notation v0.0.0-20251101123932-5f5c05ee0239/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
||||||
|
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f h1:gomu8xTD953IkL3M528qVEuZ2z93C2I6Hr4vyIwE7kI=
|
||||||
|
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f/go.mod h1:nXdFdxdI69JrkIT97f+AEE4OgplmxbgNFZC5j7gsdqs=
|
||||||
|
code.squareroundforest.org/arpio/textfmt v0.0.0-20251207234108-fed32c8bbe18 h1:2aa62CYm9ld5SNoFxWzE2wUN0xjVWQ+xieoeFantdg4=
|
||||||
|
code.squareroundforest.org/arpio/textfmt v0.0.0-20251207234108-fed32c8bbe18/go.mod h1:+0G3gufMAP8SCEIrDT1D/DaVOSfjS8EwPTBs5vfxqQg=
|
||||||
|
code.squareroundforest.org/arpio/wand v0.0.0-20260115221425-3b0fa9ff1ec4 h1:+CjwwD1mWGHFpcIgP9F0uQ8JEepDBht7yojLum6n6QA=
|
||||||
|
code.squareroundforest.org/arpio/wand v0.0.0-20260115221425-3b0fa9ff1ec4/go.mod h1:fPxs3LeGPxRMWUIXgBcdszk3a8d1TRqSHSVs5VL28Rc=
|
||||||
|
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||||
|
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
|
|||||||
11
lib.go
11
lib.go
@ -1,8 +1,8 @@
|
|||||||
// Package docreflect returns the Go documentation for packages, types and functions.
|
// Package docreflect returns the Go documentation for packages, types and functions.
|
||||||
//
|
//
|
||||||
// The Go documentation of packages is not available during runtime by default, so in order to use docreflect, the documentation
|
// The Go documentation of packages is not available during runtime by default, so in order to use docreflect,
|
||||||
// for the selected packages and symbols needs to be generated during build time. To generate the documentation, see the
|
// the documentation for the selected packages and symbols needs to be generated during build time. To generate
|
||||||
// docreflect/generate package or the docreflect generate command.
|
// the documentation, see the docreflect/generate package or the docreflect generate command.
|
||||||
package docreflect
|
package docreflect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -95,8 +95,9 @@ func functionParams(d string) []string {
|
|||||||
return pp
|
return pp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register is used by the code generated by the docreflect/generate package or the docreflect generate command to register
|
// Register is used by the code generated by the docreflect/generate package or the docreflect generate command
|
||||||
// the selected documentation during startup. Register is not meant to be used directly by importing packages.
|
// to register the selected documentation during startup. Register is not meant to be used directly by importing
|
||||||
|
// packages.
|
||||||
func Register(gopath, docs string) {
|
func Register(gopath, docs string) {
|
||||||
registry[gopath] = docs
|
registry[gopath] = docs
|
||||||
}
|
}
|
||||||
|
|||||||
24
readme.md
24
readme.md
@ -2,20 +2,23 @@
|
|||||||
|
|
||||||
Library and command to help accessing go doc comments during runtime.
|
Library and command to help accessing go doc comments during runtime.
|
||||||
|
|
||||||
Go doc comments are not accessible during runtime via reflection. To make them avaiable during runtime, we need to capture them
|
Go doc comments are not accessible during runtime via reflection. To make them avaiable during runtime, we need
|
||||||
during build time. The docreflect command, or the docreflect/generate library package, fetches the go doc comments of the specified
|
to capture them during build time. The docreflect command, or the docreflect/generate library package, fetches
|
||||||
declarations, and generates Go code that registers the docs for every declaration. Code that includes the generated initialization
|
the go doc comments of the specified declarations, and generates Go code that registers the docs for every
|
||||||
code, will have the docs accessible via the top level docreflect package methods.
|
declaration. Code that includes the generated initialization code, will have the docs accessible via the top
|
||||||
|
level docreflect package methods.
|
||||||
|
|
||||||
**Declarations:**
|
**Declarations:**
|
||||||
|
|
||||||
- the declarations must be absolute Go paths
|
- the declarations must be absolute Go paths
|
||||||
- when passing in the import path of a package, all the top level symbols of the package, plus the struct fields and methods of the
|
- when passing in the import path of a package, all the top level symbols of the package, plus the struct fields
|
||||||
top level types will be included
|
and methods of the top level types will be included
|
||||||
- the package documentation can be fetched using `docreflect.Docs("absolute/import/path/of/package")`
|
- the package documentation can be fetched using `docreflect.Docs("absolute/import/path/of/package")`
|
||||||
- when passing in the import path of only the selected symbols, the rest of the package level symbols will be ignroed
|
- when passing in the import path of only the selected symbols, the rest of the package level symbols will be
|
||||||
|
ignroed
|
||||||
|
|
||||||
**Gotchas:**
|
**Gotchas:**
|
||||||
|
|
||||||
- type aliases and type definitions based on named types are not resolved
|
- type aliases and type definitions based on named types are not resolved
|
||||||
- package imports are not resolved, necessary packages need to be included in the generate arguments
|
- package imports are not resolved, necessary packages need to be included in the generate arguments
|
||||||
|
|
||||||
@ -30,7 +33,10 @@ make install
|
|||||||
Usage of the docreflect command:
|
Usage of the docreflect command:
|
||||||
|
|
||||||
```
|
```
|
||||||
docreflect generate --package-name mypackage coderepos.org/jdoe/mypackage coderepos.org/jdoe/otherpackage > docreflect.go
|
docreflect generate \
|
||||||
|
--package-name mypackage \
|
||||||
|
coderepos.org/jdoe/mypackage coderepos.org/jdoe/otherpackage \
|
||||||
|
> docreflect.go
|
||||||
```
|
```
|
||||||
|
|
||||||
*Made in Berlin, DE*
|
*Made in Berlin, DE*
|
||||||
|
|||||||
18
scripts/man.go
Normal file
18
scripts/man.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/wand/tools"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
o := tools.ManOptions{
|
||||||
|
Version: os.Args[1],
|
||||||
|
DateString: os.Args[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tools.Man(os.Stdout, o, os.Args[3]); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
scripts/markdown.go
Normal file
14
scripts/markdown.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/wand/tools"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var o tools.MarkdownOptions
|
||||||
|
if err := tools.Markdown(os.Stdout, o, os.Args[1]); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user