feat(11.1-01): publish every module README as an API reference page
- discover modules/<m> with non-test Go files; a missing README is a readme: problem - one GitHub-compatible slug parser.IDs for heading anchors, passed per page - rewrite links to .md pages and module READMEs to site .html and .md URLs - search-index.json gains one entry per H2 with 300-char plain text - add the api section to docs/site.yaml; docsite.Pages exposes reading order - tests: TestSlugIDs, TestReadmeIngestion, TestEveryModuleInSidebar, TestDocsAIOutputsInSync
This commit is contained in:
@@ -77,11 +77,15 @@ func (s *site) render() ([]Problem, error) {
|
||||
return nil, fmt.Errorf("docsite: render template for %s: %w", p.Source, err)
|
||||
}
|
||||
s.outputs[p.URL+".html"] = buf.Bytes()
|
||||
s.outputs[p.URL+".md"] = s.pageMarkdown(p)
|
||||
}
|
||||
bodies := make([]string, len(s.pages))
|
||||
for i, p := range s.pages {
|
||||
bodies[i] = markdownBody(p, rendered[i].mdLinks)
|
||||
s.outputs[p.URL+".md"] = pageMarkdown(p, bodies[i])
|
||||
}
|
||||
s.outputs["llms.txt"] = s.llmsTxt()
|
||||
s.outputs["llms-full.txt"] = s.llmsFull()
|
||||
idx, err := s.searchIndex()
|
||||
s.outputs["llms-full.txt"] = s.llmsFull(bodies)
|
||||
idx, err := s.searchIndex(rendered)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,25 +113,27 @@ func (s *site) nav(current *Page) []navSection {
|
||||
}
|
||||
|
||||
// pageMarkdown returns the clean Markdown sibling of a page: no
|
||||
// frontmatter, "# Title", "> description", then the body without its H1.
|
||||
func (s *site) pageMarkdown(p *Page) []byte {
|
||||
// frontmatter, "# Title", "> description", then the transformed body.
|
||||
func pageMarkdown(p *Page, body string) []byte {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "# %s\n\n> %s\n\n", p.Title, p.Description)
|
||||
b.WriteString(s.markdownBody(p))
|
||||
b.WriteString(body)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// markdownBody is the page body without its H1 and leading blank lines,
|
||||
// ending in one newline.
|
||||
func (s *site) markdownBody(p *Page) string {
|
||||
// with links to other pages pointing at their .md URLs, ending in one
|
||||
// newline.
|
||||
func markdownBody(p *Page, links map[string]string) string {
|
||||
body := string(p.Body)
|
||||
if first, rest, ok := strings.Cut(body, "\n"); ok && strings.HasPrefix(first, "# ") {
|
||||
body = rest
|
||||
} else if !ok && strings.HasPrefix(first, "# ") {
|
||||
if first, rest, ok := strings.Cut(body, "\n"); strings.HasPrefix(first, "# ") {
|
||||
body = ""
|
||||
if ok {
|
||||
body = rest
|
||||
}
|
||||
}
|
||||
body = strings.TrimLeft(body, "\n")
|
||||
return strings.TrimRight(body, "\n") + "\n"
|
||||
body = strings.Trim(body, "\n")
|
||||
return rewriteMarkdown(body, links) + "\n"
|
||||
}
|
||||
|
||||
// llmsTxt writes the llms.txt index (llmstxt.org shape).
|
||||
@@ -161,14 +167,14 @@ func (s *site) llmsTxt() []byte {
|
||||
}
|
||||
|
||||
// llmsFull concatenates every page in reading order.
|
||||
func (s *site) llmsFull() []byte {
|
||||
func (s *site) llmsFull(bodies []string) []byte {
|
||||
var b bytes.Buffer
|
||||
for i, p := range s.pages {
|
||||
if i > 0 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
fmt.Fprintf(&b, "# %s\nSource: %s\n\n%s\n\n", p.Title, s.url(p.URL+".html"), p.Description)
|
||||
b.WriteString(s.markdownBody(p))
|
||||
b.WriteString(bodies[i])
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
@@ -191,14 +197,29 @@ type searchIndex struct {
|
||||
Entries []searchEntry `json:"e"`
|
||||
}
|
||||
|
||||
func (s *site) searchIndex() ([]byte, error) {
|
||||
const searchTextMax = 300
|
||||
|
||||
// searchIndex lists every page and one entry per H2 heading, so a hit
|
||||
// deep-links to its anchor.
|
||||
func (s *site) searchIndex(rendered []renderedPage) ([]byte, error) {
|
||||
idx := searchIndex{Pages: []searchPage{}, Entries: []searchEntry{}}
|
||||
for _, p := range s.pages {
|
||||
for i, p := range s.pages {
|
||||
sec := s.cfg.Title
|
||||
if p.Section != indexSection {
|
||||
sec = s.cfg.sectionTitle(p.Section)
|
||||
}
|
||||
idx.Pages = append(idx.Pages, searchPage{URL: s.url(p.URL + ".html"), Title: p.Title, Section: sec})
|
||||
for _, h := range rendered[i].headings {
|
||||
if h.Level != 2 {
|
||||
continue
|
||||
}
|
||||
idx.Entries = append(idx.Entries, searchEntry{
|
||||
Page: i,
|
||||
Anchor: h.ID,
|
||||
Heading: h.Text,
|
||||
Text: sectionText(h.node, p.Body, searchTextMax),
|
||||
})
|
||||
}
|
||||
}
|
||||
raw, err := json.Marshal(idx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user