1
0
textfmt/markdown.go

386 lines
6.9 KiB
Go
Raw Permalink Normal View History

2025-10-28 00:47:41 +01:00
package textfmt
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
)
2025-12-08 00:41:08 +01:00
func mdTextToString(text Txt, es mdEscapeState) string {
2025-10-28 00:47:41 +01:00
var b bytes.Buffer
2025-12-08 00:41:08 +01:00
renderMDText(&b, text, es)
2025-11-02 07:34:41 +01:00
return b.String()
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func mdCellTexts(rows []TableRow) [][]string {
2025-10-28 00:47:41 +01:00
var texts [][]string
for _, row := range rows {
var rowTexts []string
for _, cell := range row.cells {
2025-12-08 00:41:08 +01:00
rowTexts = append(rowTexts, mdTextToString(cell.text, mdEscapeState{lineStarted: false}))
2025-10-28 00:47:41 +01:00
}
texts = append(texts, rowTexts)
}
2025-11-02 07:34:41 +01:00
return texts
2025-10-28 00:47:41 +01:00
}
func mdEnsureHeaderTexts(h []string) []string {
var hh []string
for _, t := range h {
if strings.TrimSpace(t) == "" {
t = "\\-"
}
hh = append(hh, t)
}
return hh
}
2025-12-08 00:41:08 +01:00
func renderMDText(w io.Writer, text Txt, es mdEscapeState, wr ...wrapper) {
2025-10-28 00:47:41 +01:00
if len(text.cat) > 0 {
for i, tc := range text.cat {
if i > 0 {
2025-11-02 07:34:41 +01:00
write(w, " ")
2025-10-28 00:47:41 +01:00
}
2025-12-08 00:41:08 +01:00
esi := es
esi.lineStarted = esi.lineStarted || i > 0
renderMDText(w, tc, esi, wr...)
2025-10-28 00:47:41 +01:00
}
return
}
if text.bold {
2025-11-02 07:34:41 +01:00
write(w, "**")
2025-12-08 00:41:08 +01:00
es.lineStarted = true
2025-10-28 00:47:41 +01:00
}
if text.italic {
2025-11-02 07:34:41 +01:00
write(w, "_")
2025-12-08 00:41:08 +01:00
es.lineStarted = true
2025-10-28 00:47:41 +01:00
}
defer func() {
if text.italic {
2025-11-02 07:34:41 +01:00
write(w, "_")
2025-10-28 00:47:41 +01:00
}
if text.bold {
2025-11-02 07:34:41 +01:00
write(w, "**")
2025-10-28 00:47:41 +01:00
}
}()
2025-12-08 00:41:08 +01:00
wr = append([]wrapper{singleLine()}, wr...)
2025-11-02 22:15:31 +01:00
if text.link == "" {
2025-12-08 00:41:08 +01:00
w, f := writeWith(w, append(wr, escapeMarkdown(es))...)
2025-11-02 22:15:31 +01:00
write(w, text.text)
w, _ = f()
return
}
2025-10-28 00:47:41 +01:00
2025-11-02 22:15:31 +01:00
if text.text == "" {
2025-12-08 00:41:08 +01:00
w, f := writeWith(w, append(wr, escapeMarkdown(es))...)
2025-11-02 07:34:41 +01:00
write(w, text.link)
2025-11-02 22:15:31 +01:00
w, _ = f()
2025-10-28 00:47:41 +01:00
return
}
2025-11-02 22:15:31 +01:00
write(w, "[")
2025-12-08 00:41:08 +01:00
es.lineStarted = true
w, f := writeWith(w, append(wr, escapeMarkdown(es))...)
2025-11-02 07:34:41 +01:00
write(w, text.text)
2025-11-02 22:15:31 +01:00
w, _ = f()
write(w, "](")
2025-12-08 00:41:08 +01:00
w, f = writeWith(w, append(wr, escapeMarkdown(es))...)
2025-11-02 22:15:31 +01:00
write(w, text.link)
w, _ = f()
write(w, ")")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDTitle(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
hashes := e.titleLevel + 1
if hashes > 6 {
hashes = 6
}
2025-11-02 07:34:41 +01:00
write(w, timesn("#", hashes), " ")
2025-12-08 00:41:08 +01:00
renderMDText(w, e.text, mdEscapeState{lineStarted: true})
2025-10-28 00:47:41 +01:00
}
2025-12-08 00:41:08 +01:00
func renderMDParagraphIndent(w io.Writer, e Entry, es mdEscapeState) {
var wr []wrapper
2025-10-28 00:47:41 +01:00
if e.wrapWidth > 0 {
2025-11-02 07:34:41 +01:00
indentFirst := e.indent + e.indentFirst
2025-12-08 00:41:08 +01:00
wr = []wrapper{wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth)}
2025-10-28 00:47:41 +01:00
}
2025-12-08 00:41:08 +01:00
renderMDText(w, e.text, es, wr...)
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDParagraph(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
e.indent = 0
e.indentFirst = 0
2025-12-08 00:41:08 +01:00
renderMDParagraphIndent(w, e, mdEscapeState{})
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDList(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
e.indent = 2
e.indentFirst = -2
if e.wrapWidth > 2 {
e.wrapWidth -= 2
}
for i, item := range e.items {
if i > 0 {
2025-11-02 07:34:41 +01:00
write(w, "\n")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
write(w, "- ")
2025-10-28 00:47:41 +01:00
p := itemToParagraph(e, item.text)
2025-12-08 00:41:08 +01:00
renderMDParagraphIndent(w, p, mdEscapeState{lineStarted: true})
2025-10-28 00:47:41 +01:00
}
}
2025-11-02 07:34:41 +01:00
func renderMDNumberedList(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
maxDigits := numDigits(len(e.items))
e.indent = maxDigits + 2
e.indentFirst = 0 - maxDigits - 2
if e.wrapWidth > maxDigits+2 {
e.wrapWidth -= maxDigits + 2
}
for i, item := range e.items {
if i > 0 {
2025-11-02 07:34:41 +01:00
write(w, "\n")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
write(w, padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
2025-10-28 00:47:41 +01:00
p := itemToParagraph(e, item.text)
2025-12-08 00:41:08 +01:00
renderMDParagraphIndent(w, p, mdEscapeState{lineStarted: true})
2025-10-28 00:47:41 +01:00
}
}
2025-11-02 07:34:41 +01:00
func renderMDDefinitions(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
for _, d := range e.definitions {
e.items = append(
e.items,
2025-12-08 00:41:08 +01:00
Item(Text(fmt.Sprintf("%s: %s", d.name.text, d.value.text))),
2025-10-28 00:47:41 +01:00
)
}
renderMDList(w, e)
}
2025-11-02 07:34:41 +01:00
func renderMDNumberedDefinitions(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
for _, d := range e.definitions {
e.items = append(
e.items,
2025-12-08 00:41:08 +01:00
Item(Text(fmt.Sprintf("%s: %s", d.name.text, d.value.text))),
2025-10-28 00:47:41 +01:00
)
}
renderMDNumberedList(w, e)
}
2025-11-02 07:34:41 +01:00
func renderMDTable(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
e.rows = normalizeTable(e.rows)
e.rows = ensureHeader(e.rows)
if len(e.rows) == 0 || len(e.rows[0].cells) == 0 {
return
}
2025-11-02 07:34:41 +01:00
headerTexts := mdCellTexts(e.rows[:1])
cellTexts := mdCellTexts(e.rows[1:])
2025-10-28 00:47:41 +01:00
headerTexts[0] = mdEnsureHeaderTexts(headerTexts[0])
columns := columnWidths(headerTexts)
cellColumns := columnWidths(cellTexts)
if len(cellColumns) > 0 {
for i := range columns {
if cellColumns[i] > columns[i] {
columns[i] = cellColumns[i]
}
}
}
2025-11-02 07:34:41 +01:00
write(w, "|")
2025-10-28 00:47:41 +01:00
for i, h := range headerTexts[0] {
2025-11-02 07:34:41 +01:00
write(w, " ", padRight(h, columns[i]))
write(w, " |")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
write(w, "\n|")
2025-10-28 00:47:41 +01:00
for _, c := range columns {
2025-11-02 07:34:41 +01:00
write(w, timesn("-", c+1))
write(w, "-|")
2025-10-28 00:47:41 +01:00
}
for _, row := range cellTexts {
2025-11-02 07:34:41 +01:00
write(w, "\n|")
2025-10-28 00:47:41 +01:00
for i, cell := range row {
2025-11-02 07:34:41 +01:00
write(w, " ", padRight(cell, columns[i]))
write(w, " |")
2025-10-28 00:47:41 +01:00
}
}
}
2025-11-02 07:34:41 +01:00
func renderMDCode(w io.Writer, e Entry) {
write(w, "```\n")
write(w, e.text.text)
write(w, "\n```")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDMultiple(w io.Writer, s SyntaxItem) {
2025-10-28 00:47:41 +01:00
s.topLevel = false
s.multiple = false
renderMDSyntaxItem(w, s)
2025-11-02 07:34:41 +01:00
write(w, "...")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDRequired(w io.Writer, s SyntaxItem) {
2025-10-28 00:47:41 +01:00
s.delimited = true
s.topLevel = false
s.required = false
2025-11-02 07:34:41 +01:00
write(w, "<")
2025-10-28 00:47:41 +01:00
renderMDSyntaxItem(w, s)
2025-11-02 07:34:41 +01:00
write(w, ">")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDOptional(w io.Writer, s SyntaxItem) {
2025-10-28 00:47:41 +01:00
s.delimited = true
s.topLevel = false
s.optional = false
2025-11-02 07:34:41 +01:00
write(w, "[")
2025-10-28 00:47:41 +01:00
renderMDSyntaxItem(w, s)
2025-11-02 07:34:41 +01:00
write(w, "]")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDSequence(w io.Writer, s SyntaxItem) {
2025-10-28 00:47:41 +01:00
if !s.delimited && !s.topLevel {
2025-11-02 07:34:41 +01:00
write(w, "(")
2025-10-28 00:47:41 +01:00
}
for i, item := range s.sequence {
if i > 0 {
2025-11-02 07:34:41 +01:00
write(w, " ")
2025-10-28 00:47:41 +01:00
}
item.delimited = false
renderMDSyntaxItem(w, item)
}
if !s.delimited && !s.topLevel {
2025-11-02 07:34:41 +01:00
write(w, ")")
2025-10-28 00:47:41 +01:00
}
}
2025-11-02 07:34:41 +01:00
func renderMDChoice(w io.Writer, s SyntaxItem) {
2025-10-28 00:47:41 +01:00
if !s.delimited && !s.topLevel {
2025-11-02 07:34:41 +01:00
write(w, "(")
2025-10-28 00:47:41 +01:00
}
for i, item := range s.choice {
if i > 0 {
separator := "|"
if s.topLevel {
separator = "\n"
}
2025-11-02 07:34:41 +01:00
write(w, separator)
2025-10-28 00:47:41 +01:00
}
item.delimited = false
item.topLevel = s.topLevel
2025-10-28 00:47:41 +01:00
renderMDSyntaxItem(w, item)
}
if !s.delimited && !s.topLevel {
2025-11-02 07:34:41 +01:00
write(w, ")")
2025-10-28 00:47:41 +01:00
}
}
2025-11-02 07:34:41 +01:00
func renderMDSymbol(w io.Writer, s SyntaxItem) {
write(w, s.symbol)
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
func renderMDSyntaxItem(w io.Writer, s SyntaxItem) {
2025-10-28 00:47:41 +01:00
switch {
// foo...
case s.multiple:
renderMDMultiple(w, s)
// <foo>
case s.required:
renderMDRequired(w, s)
// [foo]
case s.optional:
renderMDOptional(w, s)
// foo bar baz or (foo bar baz)
case len(s.sequence) > 0:
renderMDSequence(w, s)
// foo|bar|baz or (foo|bar|baz)
case len(s.choice) > 0:
renderMDChoice(w, s)
// foo
default:
renderMDSymbol(w, s)
}
}
2025-11-02 07:34:41 +01:00
func renderMDSyntax(w io.Writer, e Entry) {
2025-10-28 00:47:41 +01:00
s := e.syntax
s.topLevel = true
2025-11-02 07:34:41 +01:00
write(w, "```\n")
2025-10-28 00:47:41 +01:00
renderMDSyntaxItem(w, s)
2025-11-02 07:34:41 +01:00
write(w, "\n```")
2025-10-28 00:47:41 +01:00
}
func renderMarkdown(out io.Writer, d Document) error {
2025-11-02 07:34:41 +01:00
w, f := writeWith(out, mdNBSP(), errorHandler)
2025-10-28 00:47:41 +01:00
for i, e := range d.entries {
if i > 0 {
2025-11-02 07:34:41 +01:00
write(w, "\n\n")
2025-10-28 00:47:41 +01:00
}
switch e.typ {
case title:
2025-11-02 06:27:17 +01:00
renderMDTitle(w, e)
2025-10-28 00:47:41 +01:00
case paragraph:
2025-11-02 06:27:17 +01:00
renderMDParagraph(w, e)
2025-10-28 00:47:41 +01:00
case list:
2025-11-02 06:27:17 +01:00
renderMDList(w, e)
2025-10-28 00:47:41 +01:00
case numberedList:
2025-11-02 06:27:17 +01:00
renderMDNumberedList(w, e)
2025-10-28 00:47:41 +01:00
case definitions:
2025-11-02 06:27:17 +01:00
renderMDDefinitions(w, e)
2025-10-28 00:47:41 +01:00
case numberedDefinitions:
2025-11-02 06:27:17 +01:00
renderMDNumberedDefinitions(w, e)
2025-10-28 00:47:41 +01:00
case table:
2025-11-02 06:27:17 +01:00
renderMDTable(w, e)
2025-10-28 00:47:41 +01:00
case code:
2025-11-02 06:27:17 +01:00
renderMDCode(w, e)
2025-10-28 00:47:41 +01:00
case syntax:
2025-11-02 06:27:17 +01:00
renderMDSyntax(w, e)
2025-10-28 02:48:55 +01:00
default:
return errors.New("invalid entry")
2025-10-28 00:47:41 +01:00
}
}
if len(d.entries) > 0 {
2025-11-02 07:34:41 +01:00
write(w, "\n")
2025-10-28 00:47:41 +01:00
}
2025-11-02 07:34:41 +01:00
_, err := f()
return err
2025-10-28 00:47:41 +01:00
}