227 lines
4.8 KiB
Go
227 lines
4.8 KiB
Go
// 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
|
|
// for the selected packages and symbols needs to be generated during build time. To generate the documentation, see the
|
|
// docreflect/generate package or the docreflect generate command.
|
|
package docreflect
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
type pointer[T any] interface {
|
|
Kind() reflect.Kind
|
|
Elem() T
|
|
}
|
|
|
|
var (
|
|
registry = make(map[string]string)
|
|
funcParamsExp = regexp.MustCompile("^func[(]([a-zA-Z_][a-zA-Z0-9_]+(, [a-zA-Z_][a-zA-Z0-9_]+)*)?[)]$")
|
|
)
|
|
|
|
func unpack[T pointer[T]](p T) T {
|
|
if p.Kind() != reflect.Pointer {
|
|
return p
|
|
}
|
|
|
|
return unpack(p.Elem())
|
|
}
|
|
|
|
func docs(p string) string {
|
|
return registry[p]
|
|
}
|
|
|
|
func functionPath(v reflect.Value) string {
|
|
t := v.Type()
|
|
pp := t.PkgPath()
|
|
p := v.Pointer()
|
|
rf := runtime.FuncForPC(p)
|
|
n := rf.Name()
|
|
|
|
// method names appear to have an -fm suffix:
|
|
np := strings.Split(n, ".")
|
|
npl := np[len(np)-1]
|
|
nplp := strings.Split(npl, "-")
|
|
if len(nplp) > 1 {
|
|
npl = nplp[0]
|
|
np[len(np)-1] = npl
|
|
n = strings.Join(np, ".")
|
|
}
|
|
|
|
if strings.HasPrefix(n, pp) {
|
|
return n
|
|
}
|
|
|
|
// sometimes we get packagename.FunctionName instead of the full import path:
|
|
np = strings.Split(n, ".")
|
|
if len(np) > 1 {
|
|
np = np[1:]
|
|
}
|
|
|
|
return strings.Join(append([]string{pp}, np...), ".")
|
|
}
|
|
|
|
func splitDocs(d string) (string, string) {
|
|
parts := strings.Split(d, "\n")
|
|
last := len(parts) - 1
|
|
lastPart := parts[last]
|
|
|
|
var params string
|
|
if funcParamsExp.MatchString(lastPart) {
|
|
parts = parts[:last]
|
|
params = lastPart
|
|
}
|
|
|
|
return strings.Join(parts, "\n"), params
|
|
}
|
|
|
|
func functionParams(d string) []string {
|
|
_, p := splitDocs(d)
|
|
if len(p) <= len("func()") {
|
|
return nil
|
|
}
|
|
|
|
p = p[len("func(") : len(p)-1]
|
|
pp := strings.Split(p, ",")
|
|
for i := range pp {
|
|
pp[i] = strings.TrimSpace(pp[i])
|
|
}
|
|
|
|
return pp
|
|
}
|
|
|
|
// Register is used by the code generated by the docreflect/generate package or the docreflect generate command to register
|
|
// the selected documentation during startup. Register is not meant to be used directly by importing packages.
|
|
func Register(gopath, docs string) {
|
|
registry[gopath] = docs
|
|
}
|
|
|
|
// Docs returns the documentation for a package or a symbol in a package identified by its full go path.
|
|
func Docs(gopath string) string {
|
|
d := docs(gopath)
|
|
d, _ = splitDocs(d)
|
|
return d
|
|
}
|
|
|
|
// Function returns the documentation for a package level function.
|
|
func Function(v reflect.Value) string {
|
|
v = unpack(v)
|
|
if v.Kind() != reflect.Func {
|
|
return ""
|
|
}
|
|
|
|
return Docs(functionPath(v))
|
|
}
|
|
|
|
// FunctionParams returns the list of the parameter names of a package level function.
|
|
func FunctionParams(v reflect.Value) []string {
|
|
v = unpack(v)
|
|
if v.Kind() != reflect.Func {
|
|
return nil
|
|
}
|
|
|
|
d := docs(functionPath(v))
|
|
return functionParams(d)
|
|
}
|
|
|
|
// Type returns the docuemntation for a package level type.
|
|
func Type(t reflect.Type) string {
|
|
t = unpack(t)
|
|
p := fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
|
return Docs(p)
|
|
}
|
|
|
|
func structField(visited map[reflect.Type]bool, t reflect.Type, fieldPath []string) (reflect.Type, string, bool) {
|
|
t = unpack(t)
|
|
if t.Kind() != reflect.Struct {
|
|
return nil, "", false
|
|
}
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if !f.Anonymous {
|
|
continue
|
|
}
|
|
|
|
ft := unpack(f.Type)
|
|
if visited[ft] {
|
|
continue
|
|
}
|
|
|
|
if visited == nil {
|
|
visited = make(map[reflect.Type]bool)
|
|
}
|
|
|
|
visited[ft] = true
|
|
if t, s, ok := structField(visited, ft, fieldPath); ok {
|
|
return t, s, true
|
|
}
|
|
}
|
|
|
|
name := fieldPath[0]
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if f.Name != name {
|
|
continue
|
|
}
|
|
|
|
if len(fieldPath) == 1 {
|
|
return t, name, true
|
|
}
|
|
|
|
tt, s, ok := structField(nil, unpack(f.Type), fieldPath[1:])
|
|
if !ok {
|
|
return nil, "", false
|
|
}
|
|
|
|
if tt.Name() == "" {
|
|
return t, fmt.Sprintf("%s.%s", name, s), true
|
|
}
|
|
|
|
return tt, s, true
|
|
}
|
|
|
|
return nil, "", false
|
|
}
|
|
|
|
// Field returns the docuemntation for a struct field.
|
|
func Field(t reflect.Type, fieldPath ...string) string {
|
|
if len(fieldPath) == 0 {
|
|
return ""
|
|
}
|
|
|
|
t, s, ok := structField(nil, t, fieldPath)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
|
|
return Docs(strings.Join([]string{t.PkgPath(), t.Name(), s}, "."))
|
|
}
|
|
|
|
// Method returns the documentation for a type method.
|
|
func Method(t reflect.Type, name string) string {
|
|
t = unpack(t)
|
|
if t.Kind() != reflect.Struct {
|
|
return ""
|
|
}
|
|
|
|
p := fmt.Sprintf("%s.%s.%s", t.PkgPath(), t.Name(), name)
|
|
return Docs(p)
|
|
}
|
|
|
|
// MethodParams returns the list of the parameter names of a type method.
|
|
func MethodParams(t reflect.Type, name string) []string {
|
|
t = unpack(t)
|
|
if t.Kind() != reflect.Struct {
|
|
return nil
|
|
}
|
|
|
|
p := fmt.Sprintf("%s.%s.%s", t.PkgPath(), t.Name(), name)
|
|
d := docs(p)
|
|
return functionParams(d)
|
|
}
|