// 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) }