// 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 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 { 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(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 } // Field returns the docuemntation for a struct field. func Field(t reflect.Type, fieldPath ...string) string { if len(fieldPath) == 0 { return "" } t = unpack(t) if t.Kind() != reflect.Struct { return "" } 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...) } } } // 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) }