render markdown entries
This commit is contained in:
parent
3b31267823
commit
b78959867f
5
lib.go
5
lib.go
@ -197,6 +197,7 @@ func Row(cells ...TableCell) TableRow {
|
||||
return TableRow{cells: cells}
|
||||
}
|
||||
|
||||
// when no header and markdown, header automatic
|
||||
func Table(rows ...TableRow) Entry {
|
||||
return Entry{typ: table, rows: rows}
|
||||
}
|
||||
@ -279,8 +280,8 @@ func Runoff(out io.Writer, d Document) error {
|
||||
return renderRoff(out, d)
|
||||
}
|
||||
|
||||
func Markdown(io.Writer, Document) error {
|
||||
return nil
|
||||
func Markdown(out io.Writer, d Document) error {
|
||||
return renderMarkdown(out, d)
|
||||
}
|
||||
|
||||
func HTML(io.Writer, Document) error {
|
||||
|
||||
460
markdown.go
Normal file
460
markdown.go
Normal file
@ -0,0 +1,460 @@
|
||||
package textfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func escapeMarkdown(s string, additional ...rune) string {
|
||||
var (
|
||||
rr []rune
|
||||
isNumberOnNewLine bool
|
||||
isLinkOpen, isLinkClosed, isLinkLabel bool
|
||||
)
|
||||
|
||||
isNewLine := true
|
||||
r := []rune(s)
|
||||
for _, ri := range r {
|
||||
switch ri {
|
||||
case '\\', '`', '*', '_', '[', ']', '#', '<', '>':
|
||||
rr = append(rr, '\\', ri)
|
||||
default:
|
||||
switch {
|
||||
case isNewLine:
|
||||
switch ri {
|
||||
case '+', '-':
|
||||
rr = append(rr, '\\', ri)
|
||||
default:
|
||||
rr = append(rr, ri)
|
||||
}
|
||||
case isNumberOnNewLine:
|
||||
switch ri {
|
||||
case '.':
|
||||
rr = append(rr, '\\', ri)
|
||||
default:
|
||||
rr = append(rr, ri)
|
||||
}
|
||||
case isLinkClosed:
|
||||
switch ri {
|
||||
case '(':
|
||||
rr = append(rr, '\\', ri)
|
||||
default:
|
||||
rr = append(rr, ri)
|
||||
}
|
||||
case isLinkLabel:
|
||||
switch ri {
|
||||
case ')':
|
||||
rr = append(rr, '\\', ri)
|
||||
default:
|
||||
rr = append(rr, ri)
|
||||
}
|
||||
default:
|
||||
if slices.Contains(additional, ri) {
|
||||
rr = append(rr, '\\', ri)
|
||||
} else {
|
||||
rr = append(rr, ri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isNumberOnNewLine = (isNewLine || isNumberOnNewLine) && ri >= '0' && ri <= '9'
|
||||
isNewLine = ri == '\n'
|
||||
isLinkOpen = !isLinkLabel && ri == '[' || isLinkOpen && ri != ']'
|
||||
isLinkClosed = isLinkOpen && ri == ']'
|
||||
isLinkLabel = isLinkClosed && ri == '(' || isLinkLabel && ri != ')'
|
||||
}
|
||||
|
||||
return string(rr)
|
||||
}
|
||||
|
||||
func mdTextToString(text Txt) (string, error) {
|
||||
var b bytes.Buffer
|
||||
w := mdWriter{w: &b, internal: true}
|
||||
renderMDText(&w, text)
|
||||
if w.err != nil {
|
||||
return "", w.err
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func mdCellTexts(rows []TableRow) ([][]string, error) {
|
||||
var texts [][]string
|
||||
for _, row := range rows {
|
||||
var rowTexts []string
|
||||
for _, cell := range row.cells {
|
||||
txt, err := mdTextToString(cell.text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowTexts = append(rowTexts, txt)
|
||||
}
|
||||
|
||||
texts = append(texts, rowTexts)
|
||||
}
|
||||
|
||||
return texts, nil
|
||||
}
|
||||
|
||||
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 writer, text Txt) {
|
||||
if len(text.cat) > 0 {
|
||||
for i, tc := range text.cat {
|
||||
if i > 0 {
|
||||
w.write(" ")
|
||||
}
|
||||
|
||||
renderMDText(w, tc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
text.text = singleLine(text.text)
|
||||
text.text = escapeMarkdown(text.text)
|
||||
text.link = singleLine(text.link)
|
||||
text.link = escapeMarkdown(text.link)
|
||||
if text.bold {
|
||||
w.write("**")
|
||||
}
|
||||
|
||||
if text.italic {
|
||||
w.write("_")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if text.italic {
|
||||
w.write("_")
|
||||
}
|
||||
|
||||
if text.bold {
|
||||
w.write("**")
|
||||
}
|
||||
}()
|
||||
|
||||
if text.link != "" {
|
||||
if text.text != "" {
|
||||
w.write("[")
|
||||
w.write(text.text)
|
||||
w.write("](")
|
||||
w.write(text.link)
|
||||
w.write(")")
|
||||
return
|
||||
}
|
||||
|
||||
w.write(text.link)
|
||||
return
|
||||
}
|
||||
|
||||
w.write(text.text)
|
||||
}
|
||||
|
||||
func renderMDTitle(w writer, e Entry) {
|
||||
hashes := e.titleLevel + 1
|
||||
if hashes > 6 {
|
||||
hashes = 6
|
||||
}
|
||||
|
||||
w.write(timesn("#", hashes), " ")
|
||||
renderMDText(w, e.text)
|
||||
}
|
||||
|
||||
func renderMDParagraphIndent(w writer, e Entry) {
|
||||
txt, err := mdTextToString(e.text)
|
||||
if err != nil {
|
||||
w.setErr(err)
|
||||
}
|
||||
|
||||
indentFirst := e.indent + e.indentFirst
|
||||
if e.wrapWidth > 0 {
|
||||
txt = wrap(txt, e.wrapWidth, indentFirst, e.indent)
|
||||
}
|
||||
|
||||
writeLines(w, txt, indentFirst, e.indent)
|
||||
}
|
||||
|
||||
func renderMDParagraph(w writer, e Entry) {
|
||||
e.indent = 0
|
||||
e.indentFirst = 0
|
||||
renderMDParagraphIndent(w, e)
|
||||
}
|
||||
|
||||
func renderMDList(w 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 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
w.write("- ")
|
||||
p := itemToParagraph(e, item.text)
|
||||
renderMDParagraphIndent(w, p)
|
||||
}
|
||||
}
|
||||
|
||||
func renderMDNumberedList(w 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 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
w.write(padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
|
||||
p := itemToParagraph(e, item.text)
|
||||
renderMDParagraphIndent(w, p)
|
||||
}
|
||||
}
|
||||
|
||||
func renderMDDefinitions(w writer, e Entry) {
|
||||
for _, d := range e.definitions {
|
||||
e.items = append(
|
||||
e.items,
|
||||
Item(Cat(Text(fmt.Sprintf("%s:", d.name.text)), d.value)),
|
||||
)
|
||||
}
|
||||
|
||||
renderMDList(w, e)
|
||||
}
|
||||
|
||||
func renderMDNumberedDefinitions(w writer, e Entry) {
|
||||
for _, d := range e.definitions {
|
||||
e.items = append(
|
||||
e.items,
|
||||
Item(Cat(Text(fmt.Sprintf("%s:", d.name.text)), d.value)),
|
||||
)
|
||||
}
|
||||
|
||||
renderMDNumberedList(w, e)
|
||||
}
|
||||
|
||||
func renderMDTable(w 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, err := mdCellTexts(e.rows[:1])
|
||||
if err != nil {
|
||||
w.setErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
cellTexts, err := mdCellTexts(e.rows[1:])
|
||||
if err != nil {
|
||||
w.setErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.write("|")
|
||||
for i, h := range headerTexts[0] {
|
||||
w.write(" ", padRight(h, columns[i]))
|
||||
w.write(" |")
|
||||
}
|
||||
|
||||
w.write("\n|")
|
||||
for _, c := range columns {
|
||||
w.write(timesn("-", c+1))
|
||||
w.write("-|")
|
||||
}
|
||||
|
||||
for _, row := range cellTexts {
|
||||
w.write("\n|")
|
||||
for i, cell := range row {
|
||||
w.write(" ", padRight(cell, columns[i]))
|
||||
w.write(" |")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderMDCode(w writer, e Entry) {
|
||||
w.write("```\n")
|
||||
w.write(e.text.text)
|
||||
w.write("\n```")
|
||||
}
|
||||
|
||||
func renderMDMultiple(w writer, s SyntaxItem) {
|
||||
s.topLevel = false
|
||||
s.multiple = false
|
||||
renderMDSyntaxItem(w, s)
|
||||
w.write("...")
|
||||
}
|
||||
|
||||
func renderMDRequired(w writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.required = false
|
||||
w.write("<")
|
||||
renderMDSyntaxItem(w, s)
|
||||
w.write(">")
|
||||
}
|
||||
|
||||
func renderMDOptional(w writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.optional = false
|
||||
w.write("[")
|
||||
renderMDSyntaxItem(w, s)
|
||||
w.write("]")
|
||||
}
|
||||
|
||||
func renderMDSequence(w writer, s SyntaxItem) {
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write("(")
|
||||
}
|
||||
|
||||
for i, item := range s.sequence {
|
||||
if i > 0 {
|
||||
w.write(" ")
|
||||
}
|
||||
|
||||
item.delimited = false
|
||||
renderMDSyntaxItem(w, item)
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderMDChoice(w writer, s SyntaxItem) {
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write("(")
|
||||
}
|
||||
|
||||
for i, item := range s.choice {
|
||||
if i > 0 {
|
||||
separator := "|"
|
||||
if s.topLevel {
|
||||
separator = "\n"
|
||||
}
|
||||
|
||||
w.write(separator)
|
||||
}
|
||||
|
||||
item.delimited = false
|
||||
renderMDSyntaxItem(w, item)
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderMDSymbol(w writer, s SyntaxItem) {
|
||||
w.write(escapeTeletype(s.symbol))
|
||||
}
|
||||
|
||||
func renderMDSyntaxItem(w 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 writer, e Entry) {
|
||||
s := e.syntax
|
||||
s.topLevel = true
|
||||
w.write("```\n")
|
||||
renderMDSyntaxItem(w, s)
|
||||
w.write("\n```")
|
||||
}
|
||||
|
||||
func renderMarkdown(out io.Writer, d Document) error {
|
||||
w := mdWriter{w: out}
|
||||
for i, e := range d.entries {
|
||||
if i > 0 {
|
||||
w.write("\n\n")
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case invalid:
|
||||
return errors.New("invalid entry")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.entries) > 0 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
return w.err
|
||||
}
|
||||
1490
markdown_test.go
Normal file
1490
markdown_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
indentation for syntax in tty and roff
|
||||
verify if empty document doesn't print even an empty line. E.g. empty table, empty paragraph. What should those
|
||||
print?
|
||||
does the table need the non-breaking space for the filling in roff?
|
||||
indentation for syntax may not require non-break spaces
|
||||
test empty cat
|
||||
show top level choice on separate lines in the same block
|
||||
|
||||
@ -1034,7 +1034,7 @@ This is a paragraph.
|
||||
textfmt.Item(textfmt.Text("this is the nineth item")),
|
||||
textfmt.Item(textfmt.Text("this is the tenth item")),
|
||||
textfmt.Item(textfmt.Text("this is the eleventh item")),
|
||||
textfmt.Item(textfmt.Text("this is the twelveth item")),
|
||||
textfmt.Item(textfmt.Text("this is the twelfth item")),
|
||||
),
|
||||
)
|
||||
|
||||
@ -1089,7 +1089,7 @@ This is a paragraph.
|
||||
.br
|
||||
.in 4
|
||||
.ti 0
|
||||
12.\~this is the twelveth item
|
||||
12.\~this is the twelfth item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -1399,7 +1399,7 @@ This is a paragraph.
|
||||
textfmt.Definition(textfmt.Text("nine"), textfmt.Text("this is the nineth item")),
|
||||
textfmt.Definition(textfmt.Text("ten"), textfmt.Text("this is the tenth item")),
|
||||
textfmt.Definition(textfmt.Text("eleven"), textfmt.Text("this is the eleventh item")),
|
||||
textfmt.Definition(textfmt.Text("twelve"), textfmt.Text("this is the twelveth item")),
|
||||
textfmt.Definition(textfmt.Text("twelve"), textfmt.Text("this is the twelfth item")),
|
||||
),
|
||||
)
|
||||
|
||||
@ -1454,7 +1454,7 @@ This is a paragraph.
|
||||
.br
|
||||
.in 12
|
||||
.ti 0
|
||||
12.\~twelve:\~this is the twelveth item
|
||||
12.\~twelve:\~this is the twelfth item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
|
||||
9
table.go
9
table.go
@ -81,3 +81,12 @@ func columnWidths(rows [][]string) []int {
|
||||
|
||||
return widths
|
||||
}
|
||||
|
||||
func ensureHeader(rows []TableRow) []TableRow {
|
||||
if len(rows) == 0 || len(rows[0].cells) == 0 || rows[0].header {
|
||||
return rows
|
||||
}
|
||||
|
||||
h := []TableRow{{header: true, cells: make([]TableCell, len(rows[0].cells))}}
|
||||
return append(h, rows...)
|
||||
}
|
||||
|
||||
36
teletype.go
36
teletype.go
@ -23,6 +23,17 @@ func escapeTeletype(s string) string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func ttyTextToString(text Txt) (string, error) {
|
||||
var b bytes.Buffer
|
||||
w := ttyWriter{w: &b, internal: true}
|
||||
renderTTYText(&w, text)
|
||||
if w.err != nil {
|
||||
return "", w.err
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func ttyDefinitionNames(d []DefinitionItem) ([]string, error) {
|
||||
var n []string
|
||||
for _, di := range d {
|
||||
@ -37,17 +48,6 @@ func ttyDefinitionNames(d []DefinitionItem) ([]string, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func ttyTextToString(text Txt) (string, error) {
|
||||
var b bytes.Buffer
|
||||
w := ttyWriter{w: &b, internal: true}
|
||||
renderTTYText(&w, text)
|
||||
if w.err != nil {
|
||||
return "", w.err
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func renderTTYText(w writer, text Txt) {
|
||||
if len(text.cat) > 0 {
|
||||
for i, tc := range text.cat {
|
||||
@ -63,6 +63,8 @@ func renderTTYText(w writer, text Txt) {
|
||||
|
||||
text.text = singleLine(text.text)
|
||||
text.text = escapeTeletype(text.text)
|
||||
text.link = singleLine(text.link)
|
||||
text.link = escapeTeletype(text.link)
|
||||
if text.link != "" {
|
||||
if text.text != "" {
|
||||
w.write(text.text)
|
||||
@ -79,18 +81,6 @@ func renderTTYText(w writer, text Txt) {
|
||||
w.write(text.text)
|
||||
}
|
||||
|
||||
func itemToParagraph(list Entry, itemText Txt, prefix string) Entry {
|
||||
p := Entry{
|
||||
typ: paragraph,
|
||||
wrapWidth: list.wrapWidth,
|
||||
indent: list.indent + len([]rune(prefix)) + 1,
|
||||
indentFirst: list.indentFirst - len([]rune(prefix)) - 1,
|
||||
}
|
||||
|
||||
p.text.cat = []Txt{Text(prefix), itemText}
|
||||
return p
|
||||
}
|
||||
|
||||
func renderTTYTitle(w writer, e Entry) {
|
||||
w.write(timesn(" ", e.indent))
|
||||
renderTTYText(w, e.text)
|
||||
|
||||
@ -373,7 +373,7 @@ Entry explanations:
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if b.String() != "a link (https://sqrndfst.org\n/foo)\n" {
|
||||
if b.String() != "a link (https://sqrndfst.org /foo)\n" {
|
||||
t.Fatal(b.String())
|
||||
}
|
||||
})
|
||||
@ -789,7 +789,7 @@ third item
|
||||
textfmt.Item(textfmt.Text("this is the nineth item")),
|
||||
textfmt.Item(textfmt.Text("this is the tenth item")),
|
||||
textfmt.Item(textfmt.Text("this is the eleventh item")),
|
||||
textfmt.Item(textfmt.Text("this is the twelveth item")),
|
||||
textfmt.Item(textfmt.Text("this is the twelfth item")),
|
||||
),
|
||||
)
|
||||
|
||||
@ -809,7 +809,7 @@ third item
|
||||
9. this is the nineth item
|
||||
10. this is the tenth item
|
||||
11. this is the eleventh item
|
||||
12. this is the twelveth item
|
||||
12. this is the twelfth item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -1137,7 +1137,7 @@ third item
|
||||
textfmt.Definition(textfmt.Text("nine"), textfmt.Text("this is the nineth item")),
|
||||
textfmt.Definition(textfmt.Text("ten"), textfmt.Text("this is the tenth item")),
|
||||
textfmt.Definition(textfmt.Text("eleven"), textfmt.Text("this is the eleventh item")),
|
||||
textfmt.Definition(textfmt.Text("twelve"), textfmt.Text("this is the twelveth item")),
|
||||
textfmt.Definition(textfmt.Text("twelve"), textfmt.Text("this is the twelfth item")),
|
||||
),
|
||||
)
|
||||
|
||||
@ -1157,7 +1157,7 @@ third item
|
||||
9. nine: this is the nineth item
|
||||
10. ten: this is the tenth item
|
||||
11. eleven: this is the eleventh item
|
||||
12. twelve: this is the twelveth item
|
||||
12. twelve: this is the twelfth item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
|
||||
29
text.go
29
text.go
@ -97,3 +97,32 @@ func textToString(t Txt) string {
|
||||
|
||||
return singleLine(b.String())
|
||||
}
|
||||
|
||||
func itemToParagraph(list Entry, itemText Txt, prefix ...string) Entry {
|
||||
p := Entry{
|
||||
typ: paragraph,
|
||||
wrapWidth: list.wrapWidth,
|
||||
indent: list.indent,
|
||||
indentFirst: list.indentFirst,
|
||||
}
|
||||
|
||||
if len(prefix) == 0 {
|
||||
p.text = itemText
|
||||
return p
|
||||
}
|
||||
|
||||
var prefixLength int
|
||||
for _, p := range prefix {
|
||||
prefixLength += len([]rune(p)) + 1
|
||||
}
|
||||
|
||||
var prefixText []Txt
|
||||
for _, p := range prefix {
|
||||
prefixText = append(prefixText, Text(p))
|
||||
}
|
||||
|
||||
p.indent += prefixLength
|
||||
p.indentFirst -= prefixLength
|
||||
p.text.cat = append(prefixText, itemText)
|
||||
return p
|
||||
}
|
||||
|
||||
35
write.go
35
write.go
@ -24,6 +24,12 @@ type roffWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type mdWriter struct {
|
||||
w io.Writer
|
||||
internal bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *ttyWriter) write(a ...any) {
|
||||
for _, ai := range a {
|
||||
if w.err != nil {
|
||||
@ -90,6 +96,35 @@ func (w *roffWriter) setErr(err error) {
|
||||
w.err = err
|
||||
}
|
||||
|
||||
func (w *mdWriter) write(a ...any) {
|
||||
for _, ai := range a {
|
||||
if w.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s := fmt.Sprint(ai)
|
||||
r := []rune(s)
|
||||
if !w.internal {
|
||||
for i := range r {
|
||||
if r[i] == '\u00a0' {
|
||||
r[i] = ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s = string(r)
|
||||
_, w.err = w.w.Write([]byte(s))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *mdWriter) error() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func (w *mdWriter) setErr(err error) {
|
||||
w.err = err
|
||||
}
|
||||
|
||||
func writeLines(w writer, txt string, indentFirst, indentRest int) {
|
||||
lines := strings.Split(txt, "\n")
|
||||
for i, l := range lines {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user