mirror of
https://github.com/coder/coder.git
synced 2026-09-22 21:22:17 +08:00
Fixes CODAGT-447. Alternative implementation of https://github.com/coder/coder/pull/26212 and https://github.com/coder/coder/pull/25978 - Adds up to the first 1000 characters of `README.md` (with leading frontmatter stripped) to `chattool.list_templates` output - Adds up to 800 characters of `README.md` to `chattool.read_template`. **Note:** skipping `toolsdk` versions to keep scope small. > 🤖 Generated by Coder Agents
83 lines
2.4 KiB
Go
83 lines
2.4 KiB
Go
package chattool
|
|
|
|
import (
|
|
"bufio"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/coder/coder/v2/coderd/render"
|
|
coderstrings "github.com/coder/coder/v2/coderd/util/strings"
|
|
)
|
|
|
|
// readmeInputMaxBytes caps how many README bytes are parsed so a giant README
|
|
// can't OOM coderd. It sits well above the output rune cap, so it never trims a
|
|
// real excerpt.
|
|
const readmeInputMaxBytes = 64 * 1024
|
|
|
|
// readmeText returns the README as bounded, frontmatter-stripped plain text
|
|
// truncated to maxRunes, or "" when the README is blank or conversion fails.
|
|
func readmeText(readme string, maxRunes int) string {
|
|
// Cap the parse input first (see readmeInputMaxBytes); goldmark and the
|
|
// tokenizer tolerate a mid-line or mid-rune cut.
|
|
bounded := readme[:min(len(readme), readmeInputMaxBytes)]
|
|
text, err := render.InnerTextFromMarkdown(stripReadmeFrontmatter(bounded))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return coderstrings.Truncate(text, maxRunes, coderstrings.TruncateWithEllipsis)
|
|
}
|
|
|
|
// stripReadmeFrontmatter strips a leading frontmatter block from the README if it
|
|
// exists. An unterminated frontmatter block is treated as a regular body section.
|
|
// UTF-8 BOMs are stripped if present, and CRLF is normalized to LF. Leading
|
|
// whitespace is also stripped.
|
|
func stripReadmeFrontmatter(readme string) string {
|
|
trimmed := strings.TrimLeftFunc(readme, func(r rune) bool {
|
|
return unicode.IsSpace(r) || r == '\ufeff'
|
|
})
|
|
|
|
var out strings.Builder
|
|
scn := bufio.NewScanner(strings.NewReader(trimmed))
|
|
scn.Buffer(nil, readmeInputMaxBytes+1) // headroom
|
|
var lineNumber int
|
|
var fences int
|
|
for scn.Scan() {
|
|
line := scn.Text()
|
|
lineNumber++
|
|
|
|
// Only handle fences if we haven't already found an
|
|
// opening and closing fence.
|
|
if fences < 2 {
|
|
isFence := strings.TrimRight(line, " \t\r") == "---"
|
|
if isFence {
|
|
fences++
|
|
continue
|
|
} else if lineNumber == 1 {
|
|
// No leading fence -> no frontmatter. Return the entire document.
|
|
return trimmed
|
|
}
|
|
// We are still in the frontmatter block. Skip writing this line.
|
|
continue
|
|
}
|
|
_, _ = out.WriteString(line)
|
|
_, _ = out.WriteString("\n")
|
|
}
|
|
|
|
// Can err if input is too big. Shouldn't happen normally.
|
|
if scn.Err() != nil {
|
|
return trimmed
|
|
}
|
|
|
|
// Scanner did not scan any lines. No frontmatter to strip.
|
|
if lineNumber == 0 {
|
|
return trimmed
|
|
}
|
|
|
|
// If we are still fenced but we reached the end of the document
|
|
// we have an unterminated fence.
|
|
if fences == 1 {
|
|
return trimmed
|
|
}
|
|
return out.String()
|
|
}
|