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}
|
return TableRow{cells: cells}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when no header and markdown, header automatic
|
||||||
func Table(rows ...TableRow) Entry {
|
func Table(rows ...TableRow) Entry {
|
||||||
return Entry{typ: table, rows: rows}
|
return Entry{typ: table, rows: rows}
|
||||||
}
|
}
|
||||||
@ -279,8 +280,8 @@ func Runoff(out io.Writer, d Document) error {
|
|||||||
return renderRoff(out, d)
|
return renderRoff(out, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Markdown(io.Writer, Document) error {
|
func Markdown(out io.Writer, d Document) error {
|
||||||
return nil
|
return renderMarkdown(out, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HTML(io.Writer, Document) error {
|
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
|
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?
|
does the table need the non-breaking space for the filling in roff?
|
||||||
indentation for syntax may not require non-break spaces
|
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 nineth item")),
|
||||||
textfmt.Item(textfmt.Text("this is the tenth 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 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
|
.br
|
||||||
.in 4
|
.in 4
|
||||||
.ti 0
|
.ti 0
|
||||||
12.\~this is the twelveth item
|
12.\~this is the twelfth item
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
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("nine"), textfmt.Text("this is the nineth item")),
|
||||||
textfmt.Definition(textfmt.Text("ten"), textfmt.Text("this is the tenth 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("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
|
.br
|
||||||
.in 12
|
.in 12
|
||||||
.ti 0
|
.ti 0
|
||||||
12.\~twelve:\~this is the twelveth item
|
12.\~twelve:\~this is the twelfth item
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if b.String() != expect {
|
||||||
|
|||||||
9
table.go
9
table.go
@ -81,3 +81,12 @@ func columnWidths(rows [][]string) []int {
|
|||||||
|
|
||||||
return widths
|
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)
|
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) {
|
func ttyDefinitionNames(d []DefinitionItem) ([]string, error) {
|
||||||
var n []string
|
var n []string
|
||||||
for _, di := range d {
|
for _, di := range d {
|
||||||
@ -37,17 +48,6 @@ func ttyDefinitionNames(d []DefinitionItem) ([]string, error) {
|
|||||||
return n, nil
|
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) {
|
func renderTTYText(w writer, text Txt) {
|
||||||
if len(text.cat) > 0 {
|
if len(text.cat) > 0 {
|
||||||
for i, tc := range text.cat {
|
for i, tc := range text.cat {
|
||||||
@ -63,6 +63,8 @@ func renderTTYText(w writer, text Txt) {
|
|||||||
|
|
||||||
text.text = singleLine(text.text)
|
text.text = singleLine(text.text)
|
||||||
text.text = escapeTeletype(text.text)
|
text.text = escapeTeletype(text.text)
|
||||||
|
text.link = singleLine(text.link)
|
||||||
|
text.link = escapeTeletype(text.link)
|
||||||
if text.link != "" {
|
if text.link != "" {
|
||||||
if text.text != "" {
|
if text.text != "" {
|
||||||
w.write(text.text)
|
w.write(text.text)
|
||||||
@ -79,18 +81,6 @@ func renderTTYText(w writer, text Txt) {
|
|||||||
w.write(text.text)
|
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) {
|
func renderTTYTitle(w writer, e Entry) {
|
||||||
w.write(timesn(" ", e.indent))
|
w.write(timesn(" ", e.indent))
|
||||||
renderTTYText(w, e.text)
|
renderTTYText(w, e.text)
|
||||||
|
|||||||
@ -373,7 +373,7 @@ Entry explanations:
|
|||||||
t.Fatal(err)
|
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())
|
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 nineth item")),
|
||||||
textfmt.Item(textfmt.Text("this is the tenth 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 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
|
9. this is the nineth item
|
||||||
10. this is the tenth item
|
10. this is the tenth item
|
||||||
11. this is the eleventh item
|
11. this is the eleventh item
|
||||||
12. this is the twelveth item
|
12. this is the twelfth item
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
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("nine"), textfmt.Text("this is the nineth item")),
|
||||||
textfmt.Definition(textfmt.Text("ten"), textfmt.Text("this is the tenth 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("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
|
9. nine: this is the nineth item
|
||||||
10. ten: this is the tenth item
|
10. ten: this is the tenth item
|
||||||
11. eleven: this is the eleventh 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 {
|
if b.String() != expect {
|
||||||
|
|||||||
29
text.go
29
text.go
@ -97,3 +97,32 @@ func textToString(t Txt) string {
|
|||||||
|
|
||||||
return singleLine(b.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
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mdWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
internal bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
func (w *ttyWriter) write(a ...any) {
|
func (w *ttyWriter) write(a ...any) {
|
||||||
for _, ai := range a {
|
for _, ai := range a {
|
||||||
if w.err != nil {
|
if w.err != nil {
|
||||||
@ -90,6 +96,35 @@ func (w *roffWriter) setErr(err error) {
|
|||||||
w.err = err
|
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) {
|
func writeLines(w writer, txt string, indentFirst, indentRest int) {
|
||||||
lines := strings.Split(txt, "\n")
|
lines := strings.Split(txt, "\n")
|
||||||
for i, l := range lines {
|
for i, l := range lines {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user