2025-08-08 21:44:31 +02:00
|
|
|
// 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"
|
|
|
|
)
|
|
|
|
|
2025-08-09 20:39:45 +02:00
|
|
|
type pointer[T any] interface {
|
|
|
|
Kind() reflect.Kind
|
|
|
|
Elem() T
|
|
|
|
}
|
|
|
|
|
2025-08-08 21:44:31 +02:00
|
|
|
var (
|
|
|
|
registry = make(map[string]string)
|
|
|
|
funcParamsExp = regexp.MustCompile("^func[(]([a-zA-Z_][a-zA-Z0-9_]+(, [a-zA-Z_][a-zA-Z0-9_]+)*)?[)]$")
|
|
|
|
)
|
|
|
|
|
2025-08-09 20:39:45 +02:00
|
|
|
func unpack[T pointer[T]](p T) T {
|
|
|
|
if p.Kind() != reflect.Pointer {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
return unpack(p.Elem())
|
|
|
|
}
|
|
|
|
|
2025-08-08 21:44:31 +02:00
|
|
|
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 p == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
p = p[len("func(") : len(p)-1]
|
|
|
|
return strings.Split(p, ", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2025-08-09 20:39:45 +02:00
|
|
|
v = unpack(v)
|
2025-08-08 21:44:31 +02:00
|
|
|
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 {
|
2025-08-09 20:39:45 +02:00
|
|
|
v = unpack(v)
|
2025-08-08 21:44:31 +02:00
|
|
|
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 {
|
2025-08-09 20:39:45 +02:00
|
|
|
t = unpack(t)
|
2025-08-08 21:44:31 +02:00
|
|
|
p := fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
|
|
|
return docs(p)
|
|
|
|
}
|
|
|
|
|
2025-08-09 20:39:45 +02:00
|
|
|
func structField(st reflect.Type, name string) (reflect.StructField, bool) {
|
|
|
|
for i := 0; i < st.NumField(); i++ {
|
|
|
|
f := st.Field(i)
|
|
|
|
if f.Name == name {
|
|
|
|
return f, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reflect.StructField{}, false
|
|
|
|
}
|
|
|
|
|
2025-08-08 21:44:31 +02:00
|
|
|
// Field returns the docuemntation for a struct field.
|
|
|
|
func Field(t reflect.Type, fieldPath ...string) string {
|
|
|
|
if len(fieldPath) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2025-08-09 20:39:45 +02:00
|
|
|
t = unpack(t)
|
2025-08-08 21:44:31 +02:00
|
|
|
if t.Kind() != reflect.Struct {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2025-08-09 20:39:45 +02:00
|
|
|
f, found := structField(t, fieldPath[0])
|
|
|
|
if !found {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(fieldPath) == 1 {
|
|
|
|
println("returning docs", strings.Join([]string{t.PkgPath(), t.Name(), fieldPath[0]}, "."))
|
|
|
|
return docs(strings.Join([]string{t.PkgPath(), t.Name(), fieldPath[0]}, "."))
|
|
|
|
}
|
|
|
|
|
|
|
|
ft := unpack(f.Type)
|
|
|
|
if ft.Kind() != reflect.Struct {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if ft.Name() != "" {
|
|
|
|
return Field(ft, fieldPath[1:]...)
|
|
|
|
}
|
|
|
|
|
|
|
|
st := ft
|
|
|
|
path := fieldPath[1:]
|
|
|
|
for {
|
|
|
|
f, found = structField(st, path[0])
|
|
|
|
if !found {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(path) == 1 {
|
|
|
|
return docs(strings.Join(append([]string{t.PkgPath(), t.Name()}, fieldPath...), "."))
|
|
|
|
}
|
|
|
|
|
|
|
|
path = path[1:]
|
|
|
|
if len(path) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
st = unpack(f.Type)
|
|
|
|
if st.Kind() != reflect.Struct {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if st.Name() != "" {
|
|
|
|
return Field(st, path...)
|
|
|
|
}
|
|
|
|
}
|
2025-08-08 21:44:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Method returns the documentation for a type method.
|
|
|
|
func Method(t reflect.Type, name string) string {
|
2025-08-09 20:39:45 +02:00
|
|
|
t = unpack(t)
|
2025-08-08 21:44:31 +02:00
|
|
|
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 {
|
2025-08-09 20:39:45 +02:00
|
|
|
t = unpack(t)
|
2025-08-08 21:44:31 +02:00
|
|
|
if t.Kind() != reflect.Struct {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
p := fmt.Sprintf("%s.%s.%s", t.PkgPath(), t.Name(), name)
|
|
|
|
d := docs(p)
|
|
|
|
return functionParams(d)
|
|
|
|
}
|