Files
chatservice_concept/vendor/github.com/go-chi/docgen/markdown.go
2024-12-29 23:29:33 -05:00

219 lines
5.4 KiB
Go

package docgen
import (
"bytes"
"errors"
"fmt"
"sort"
"strings"
"github.com/go-chi/chi/v5"
)
type MarkdownDoc struct {
Opts MarkdownOpts
Router chi.Router
Doc Doc
Routes map[string]DocRouter // Pattern : DocRouter
buf *bytes.Buffer
}
type MarkdownOpts struct {
// ProjectPath is the base Go import path of the project
ProjectPath string
// Intro text included at the top of the generated markdown file.
Intro string
// ForceRelativeLinks to be relative even if they're not on github
ForceRelativeLinks bool
// URLMap allows specifying a map of package import paths to their link sources
// Used for mapping vendored dependencies to their upstream sources
// For example:
// map[string]string{"github.com/my/package/vendor/go-chi/chi/": "https://github.com/go-chi/chi/blob/master/"}
URLMap map[string]string
}
func MarkdownRoutesDoc(r chi.Router, opts MarkdownOpts) string {
md := &MarkdownDoc{Router: r, Opts: opts}
if err := md.Generate(); err != nil {
return fmt.Sprintf("ERROR: %s\n", err.Error())
}
return md.String()
}
func (md *MarkdownDoc) String() string {
return md.buf.String()
}
func (md *MarkdownDoc) Generate() error {
if md.Router == nil {
return errors.New("docgen: router is nil")
}
doc, err := BuildDoc(md.Router)
if err != nil {
return err
}
md.Doc = doc
md.buf = &bytes.Buffer{}
md.Routes = make(map[string]DocRouter)
md.WriteIntro()
md.WriteRoutes()
return nil
}
func (md *MarkdownDoc) WriteIntro() {
pkgName := md.Opts.ProjectPath
md.buf.WriteString(fmt.Sprintf("# %s\n\n", pkgName))
intro := md.Opts.Intro
md.buf.WriteString(fmt.Sprintf("%s\n\n", intro))
}
func (md *MarkdownDoc) WriteRoutes() {
md.buf.WriteString(fmt.Sprintf("## Routes\n\n"))
var buildRoutesMap func(parentPattern string, ar, nr, dr *DocRouter)
buildRoutesMap = func(parentPattern string, ar, nr, dr *DocRouter) {
nr.Middlewares = append(nr.Middlewares, dr.Middlewares...)
for pat, rt := range dr.Routes {
pattern := parentPattern + pat
nr.Routes = DocRoutes{}
if rt.Router != nil {
nnr := &DocRouter{}
nr.Routes[pat] = DocRoute{
Pattern: pat,
Handlers: rt.Handlers,
Router: nnr,
}
buildRoutesMap(pattern, ar, nnr, rt.Router)
} else if len(rt.Handlers) > 0 {
nr.Routes[pat] = DocRoute{
Pattern: pat,
Handlers: rt.Handlers,
Router: nil,
}
// Remove the trailing slash if the handler is a subroute for "/"
routeKey := pattern
if pat == "/" && len(routeKey) > 1 {
routeKey = routeKey[:len(routeKey)-1]
}
md.Routes[routeKey] = copyDocRouter(*ar)
} else {
panic("not possible")
}
}
}
// Build a route tree that consists of the full route pattern
// and the part of the tree for just that specific route, stored
// in routes map on the markdown struct. This is the structure we
// are going to render to markdown.
dr := md.Doc.Router
ar := DocRouter{}
buildRoutesMap("", &ar, &ar, &dr)
// Generate the markdown to render the above structure
var printRouter func(depth int, dr DocRouter)
printRouter = func(depth int, dr DocRouter) {
tabs := ""
for i := 0; i < depth; i++ {
tabs += "\t"
}
// Middlewares
for _, mw := range dr.Middlewares {
md.buf.WriteString(fmt.Sprintf("%s- [%s](%s)\n", tabs, mw.Func, md.githubSourceURL(mw.File, mw.Line)))
}
// Routes
for _, rt := range dr.Routes {
md.buf.WriteString(fmt.Sprintf("%s- **%s**\n", tabs, normalizer(rt.Pattern)))
if rt.Router != nil {
printRouter(depth+1, *rt.Router)
} else {
for meth, dh := range rt.Handlers {
md.buf.WriteString(fmt.Sprintf("%s\t- _%s_\n", tabs, meth))
// Handler middlewares
for _, mw := range dh.Middlewares {
md.buf.WriteString(fmt.Sprintf("%s\t\t- [%s](%s)\n", tabs, mw.Func, md.githubSourceURL(mw.File, mw.Line)))
}
// Handler endpoint
md.buf.WriteString(fmt.Sprintf("%s\t\t- [%s](%s)\n", tabs, dh.Func, md.githubSourceURL(dh.File, dh.Line)))
}
}
}
}
routePaths := []string{}
for pat := range md.Routes {
routePaths = append(routePaths, pat)
}
sort.Strings(routePaths)
for _, pat := range routePaths {
dr := md.Routes[pat]
md.buf.WriteString(fmt.Sprintf("<details>\n"))
md.buf.WriteString(fmt.Sprintf("<summary>`%s`</summary>\n", normalizer(pat)))
md.buf.WriteString(fmt.Sprintf("\n"))
printRouter(0, dr)
md.buf.WriteString(fmt.Sprintf("\n"))
md.buf.WriteString(fmt.Sprintf("</details>\n"))
}
md.buf.WriteString(fmt.Sprintf("\n"))
md.buf.WriteString(fmt.Sprintf("Total # of routes: %d\n", len(md.Routes)))
// TODO: total number of handlers..
}
func (md *MarkdownDoc) githubSourceURL(file string, line int) string {
// Currently, we only automatically link to source for github projects
if strings.Index(file, "github.com/") != 0 && !md.Opts.ForceRelativeLinks {
return ""
}
if md.Opts.ProjectPath == "" {
return ""
}
for pkg, url := range md.Opts.URLMap {
if idx := strings.Index(file, pkg); idx >= 0 {
pos := idx + len(pkg)
url = strings.TrimRight(url, "/")
filepath := strings.TrimLeft(file[pos:], "/")
return fmt.Sprintf("%s/%s#L%d", url, filepath, line)
}
}
if idx := strings.Index(file, md.Opts.ProjectPath); idx >= 0 {
// relative
pos := idx + len(md.Opts.ProjectPath)
return fmt.Sprintf("%s#L%d", file[pos:], line)
}
// absolute
return fmt.Sprintf("https://%s#L%d", file, line)
}
func normalizer(s string) string {
if strings.Contains(s, "/*") {
return strings.Replace(s, "/*", "", -1)
}
return s
}