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