1
0
textfmt/teletype.go

438 lines
7.9 KiB
Go
Raw Normal View History

2025-09-11 21:16:09 +02:00
package textfmt
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
)
2025-11-02 06:27:17 +01:00
func ttyTextToString(text Txt) string {
2025-10-31 20:24:37 +01:00
var b bytes.Buffer
2025-11-02 06:27:17 +01:00
renderTTYText(&b, text)
2025-10-31 20:24:37 +01:00
return b.String()
2025-10-28 00:47:41 +01:00
}
2025-11-02 06:27:17 +01:00
func ttyDefinitionNames(d []DefinitionItem) []string {
2025-10-14 20:46:32 +02:00
var n []string
2025-10-10 15:47:36 +02:00
for _, di := range d {
2025-11-02 06:27:17 +01:00
n = append(n, ttyTextToString(di.name))
2025-09-11 21:16:09 +02:00
}
2025-10-10 15:47:36 +02:00
2025-11-02 06:27:17 +01:00
return n
2025-10-10 15:47:36 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYText(w io.Writer, text Txt) {
2025-09-11 21:16:09 +02:00
if len(text.cat) > 0 {
for i, tc := range text.cat {
if i > 0 {
2025-11-02 06:27:17 +01:00
write(w, " ")
2025-09-11 21:16:09 +02:00
}
renderTTYText(w, tc)
}
return
}
2025-11-02 06:27:17 +01:00
var f func() (io.Writer, error)
w, f = writeWith(w, escapeTeletype(), singleLine())
if text.link == "" {
write(w, text.text)
f()
return
}
2025-09-11 21:16:09 +02:00
2025-11-02 06:27:17 +01:00
if text.text == "" {
write(w, text.link)
f()
2025-09-11 21:16:09 +02:00
return
}
2025-11-02 06:27:17 +01:00
write(w, text.text)
write(w, " (")
write(w, text.link)
write(w, ")")
f()
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYTitle(w io.Writer, e Entry) {
write(w, timesn(" ", e.indent))
2025-09-11 21:16:09 +02:00
renderTTYText(w, e.text)
}
2025-11-02 06:27:17 +01:00
func renderTTYParagraph(w io.Writer, e Entry) {
var indentation wrapper
2025-10-14 20:46:32 +02:00
indentFirst := e.indent + e.indentFirst
2025-11-02 06:27:17 +01:00
if e.wrapWidth == 0 {
indentation = indent(indentFirst, e.indent)
} else {
indentation = wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth)
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
w, f := writeWith(w, indentation)
renderTTYText(w, e.text)
f()
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYList(w io.Writer, e Entry) {
var bullets []string
for _, item := range e.items {
itemStyle := mergeItemStyles(item.style)
if itemStyle.noBullet {
bullets = append(bullets, "")
} else {
bullet := "-"
if itemStyle.bullet != "" {
bullet = itemStyle.bullet
}
bullets = append(bullets, bullet)
}
}
maxBulletLength := maxLength(bullets)
for i := range bullets {
bullets[i] = padRight(bullets[i], maxBulletLength)
2025-09-11 21:16:09 +02:00
}
for i, item := range e.items {
if i > 0 {
2025-11-02 06:27:17 +01:00
write(w, "\n")
2025-09-11 21:16:09 +02:00
}
p := itemToParagraph(e, item.text, bullets[i])
2025-10-14 20:46:32 +02:00
renderTTYParagraph(w, p)
2025-09-11 21:16:09 +02:00
}
}
func renderTTYNumberedList(w io.Writer, e Entry) {
var items []ListItem
for i, item := range e.items {
items = append(
items,
Item(
item.text,
append(item.style, Bullet(fmt.Sprintf("%d.", i+1)))...,
),
)
}
e.typ = list
e.items = items
renderTTYList(w, e)
}
2025-11-02 06:27:17 +01:00
func renderTTYDefinitions(w io.Writer, e Entry) {
itemStyles := make([]ItemStyle, len(e.definitions))
for i := range e.definitions {
itemStyles[i] = mergeItemStyles(e.definitions[i].style)
}
bullets := make([]string, len(e.definitions))
for i := range e.definitions {
if itemStyles[i].noBullet {
continue
2025-09-11 21:16:09 +02:00
}
if itemStyles[i].bullet == "" {
bullets[i] = "-"
continue
}
bullets[i] = itemStyles[i].bullet
}
2025-10-14 20:46:32 +02:00
maxBulletLength := maxLength(bullets)
items := make([]ListItem, len(e.definitions))
for i := range e.definitions {
var bullet string
if maxBulletLength == 0 {
bullet = fmt.Sprintf("%s:", ttyTextToString(e.definitions[i].name))
} else {
bullet = fmt.Sprintf(
"%s %s:",
padRight(bullets[i], maxBulletLength),
ttyTextToString(e.definitions[i].name),
)
}
items[i] = Item(e.definitions[i].value, Bullet(bullet))
2025-09-11 21:16:09 +02:00
}
e.typ = list
e.items = items
renderTTYList(w, e)
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYNumberedDefinitions(w io.Writer, e Entry) {
var defs []DefinitionItem
2025-10-14 20:46:32 +02:00
for i, definition := range e.definitions {
defs = append(
defs,
Definition(
definition.name,
definition.value,
append(definition.style, Bullet(fmt.Sprintf("%d.", i+1)))...,
2025-10-14 20:46:32 +02:00
),
2025-10-10 15:47:36 +02:00
)
2025-09-11 21:16:09 +02:00
}
e.typ = definitions
e.definitions = defs
renderTTYDefinitions(w, e)
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func ttyCellTexts(rows []TableRow) [][]string {
2025-09-11 21:16:09 +02:00
var cellTexts [][]string
for _, row := range rows {
var c []string
for _, cell := range row.cells {
2025-11-02 07:14:35 +01:00
c = append(c, ttyTextToString(cell.text))
2025-09-11 21:16:09 +02:00
}
cellTexts = append(cellTexts, c)
}
2025-11-02 06:27:17 +01:00
return cellTexts
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYTable(w io.Writer, e Entry) {
2025-09-11 21:16:09 +02:00
if len(e.rows) == 0 {
return
}
e.rows = normalizeTable(e.rows)
if len(e.rows[0].cells) == 0 {
return
}
2025-11-02 06:27:17 +01:00
cellTexts := ttyCellTexts(e.rows)
2025-09-11 21:16:09 +02:00
totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3
if e.wrapWidth > 0 {
2025-10-10 15:47:36 +02:00
allocatedWidth := e.wrapWidth - e.indent - totalSeparatorWidth
2025-09-11 21:16:09 +02:00
columnWeights := columnWeights(cellTexts)
targetColumnWidths := targetColumnWidths(allocatedWidth, columnWeights)
for i := range cellTexts {
for j := range cellTexts[i] {
2025-11-02 06:27:17 +01:00
width := targetColumnWidths[j]
cellTexts[i][j] = editString(cellTexts[i][j], wrap(width, width))
2025-09-11 21:16:09 +02:00
}
}
}
columnWidths := columnWidths(cellTexts)
totalWidth := totalSeparatorWidth
for i := range columnWidths {
totalWidth += columnWidths[i]
}
hasHeader := e.rows[0].header
2025-11-02 06:27:17 +01:00
w, f := writeWith(w, indent(e.indent, e.indent))
2025-09-11 21:16:09 +02:00
for i := range cellTexts {
if i > 0 {
sep := "-"
if hasHeader && i == 1 {
sep = "="
}
2025-11-02 06:27:17 +01:00
write(w, "\n")
write(w, timesn(sep, totalWidth))
write(w, "\n")
2025-09-11 21:16:09 +02:00
}
2025-10-10 15:47:36 +02:00
lines := make([][]string, len(cellTexts[i]))
2025-09-11 21:16:09 +02:00
for j := range cellTexts[i] {
2025-10-10 15:47:36 +02:00
lines[j] = strings.Split(cellTexts[i][j], "\n")
}
var maxLines int
for j := range lines {
if len(lines[j]) > maxLines {
maxLines = len(lines[j])
2025-09-11 21:16:09 +02:00
}
2025-10-10 15:47:36 +02:00
}
for k := 0; k < maxLines; k++ {
if k > 0 {
2025-11-02 06:27:17 +01:00
write(w, "\n")
2025-10-10 15:47:36 +02:00
}
for j := range lines {
2025-11-02 06:27:17 +01:00
if j > 0 {
write(w, " | ")
2025-10-10 15:47:36 +02:00
}
2025-09-11 21:16:09 +02:00
2025-10-10 15:47:36 +02:00
var l string
if k < len(lines[j]) {
l = lines[j][k]
}
2025-11-02 06:27:17 +01:00
write(w, padRight(l, columnWidths[j]))
2025-10-10 15:47:36 +02:00
}
2025-09-11 21:16:09 +02:00
}
}
if hasHeader && len(cellTexts) == 1 {
2025-11-02 06:27:17 +01:00
write(w, "\n", timesn("=", totalWidth))
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
f()
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYCode(w io.Writer, e Entry) {
w, f := writeWith(w, escapeTeletype(), indent(e.indent, e.indent))
write(w, e.text.text)
f()
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYMultiple(w io.Writer, s SyntaxItem) {
2025-09-11 21:16:09 +02:00
s.topLevel = false
s.multiple = false
renderTTYSyntaxItem(w, s)
2025-11-02 06:27:17 +01:00
write(w, "...")
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYRequired(w io.Writer, s SyntaxItem) {
2025-09-11 21:16:09 +02:00
s.delimited = true
s.topLevel = false
s.required = false
2025-11-02 06:27:17 +01:00
write(w, "<")
2025-09-11 21:16:09 +02:00
renderTTYSyntaxItem(w, s)
2025-11-02 06:27:17 +01:00
write(w, ">")
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYOptional(w io.Writer, s SyntaxItem) {
2025-09-11 21:16:09 +02:00
s.delimited = true
s.topLevel = false
s.optional = false
2025-11-02 06:27:17 +01:00
write(w, "[")
2025-09-11 21:16:09 +02:00
renderTTYSyntaxItem(w, s)
2025-11-02 06:27:17 +01:00
write(w, "]")
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYSequence(w io.Writer, s SyntaxItem) {
2025-09-11 21:16:09 +02:00
if !s.delimited && !s.topLevel {
2025-11-02 06:27:17 +01:00
write(w, "(")
2025-09-11 21:16:09 +02:00
}
for i, item := range s.sequence {
if i > 0 {
2025-11-02 06:27:17 +01:00
write(w, " ")
2025-09-11 21:16:09 +02:00
}
item.delimited = false
renderTTYSyntaxItem(w, item)
}
if !s.delimited && !s.topLevel {
2025-11-02 06:27:17 +01:00
write(w, ")")
2025-09-11 21:16:09 +02:00
}
}
2025-11-02 06:27:17 +01:00
func renderTTYChoice(w io.Writer, s SyntaxItem) {
2025-09-11 21:16:09 +02:00
if !s.delimited && !s.topLevel {
2025-11-02 06:27:17 +01:00
write(w, "(")
2025-09-11 21:16:09 +02:00
}
2025-10-10 15:47:36 +02:00
for i, item := range s.choice {
2025-09-11 21:16:09 +02:00
if i > 0 {
separator := "|"
if s.topLevel {
separator = "\n"
}
2025-11-02 06:27:17 +01:00
write(w, separator)
2025-09-11 21:16:09 +02:00
}
item.delimited = false
item.topLevel = s.topLevel
2025-09-11 21:16:09 +02:00
renderTTYSyntaxItem(w, item)
}
if !s.delimited && !s.topLevel {
2025-11-02 06:27:17 +01:00
write(w, ")")
2025-09-11 21:16:09 +02:00
}
}
2025-11-02 06:27:17 +01:00
func renderTTYSymbol(w io.Writer, s SyntaxItem) {
write(w, s.symbol)
2025-09-11 21:16:09 +02:00
}
2025-11-02 06:27:17 +01:00
func renderTTYSyntaxItem(w io.Writer, s SyntaxItem) {
2025-09-11 21:16:09 +02:00
switch {
// foo...
case s.multiple:
renderTTYMultiple(w, s)
// <foo>
case s.required:
renderTTYRequired(w, s)
// [foo]
case s.optional:
renderTTYOptional(w, s)
// foo bar baz or (foo bar baz)
case len(s.sequence) > 0:
renderTTYSequence(w, s)
// foo|bar|baz or (foo|bar|baz)
case len(s.choice) > 0:
renderTTYChoice(w, s)
// foo
default:
renderTTYSymbol(w, s)
}
}
2025-11-02 06:27:17 +01:00
func renderTTYSyntax(w io.Writer, e Entry) {
w, f := writeWith(w, escapeTeletype(), indent(e.indent, e.indent))
2025-09-11 21:16:09 +02:00
s := e.syntax
s.topLevel = true
renderTTYSyntaxItem(w, s)
2025-11-02 06:27:17 +01:00
f()
2025-09-11 21:16:09 +02:00
}
func renderTeletype(out io.Writer, d Document) error {
2025-11-02 06:27:17 +01:00
w, f := writeWith(out, ttyNBSP(), errorHandler)
2025-09-11 21:16:09 +02:00
for i, e := range d.entries {
if i > 0 {
2025-11-02 06:27:17 +01:00
write(w, "\n\n")
2025-09-11 21:16:09 +02:00
}
switch e.typ {
case title:
2025-10-31 20:24:37 +01:00
renderTTYTitle(w, e)
2025-09-11 21:16:09 +02:00
case paragraph:
2025-10-31 20:24:37 +01:00
renderTTYParagraph(w, e)
2025-09-11 21:16:09 +02:00
case list:
2025-10-31 20:24:37 +01:00
renderTTYList(w, e)
2025-09-11 21:16:09 +02:00
case numberedList:
2025-10-31 20:24:37 +01:00
renderTTYNumberedList(w, e)
2025-09-11 21:16:09 +02:00
case definitions:
2025-10-31 20:24:37 +01:00
renderTTYDefinitions(w, e)
2025-09-11 21:16:09 +02:00
case numberedDefinitions:
2025-10-31 20:24:37 +01:00
renderTTYNumberedDefinitions(w, e)
2025-09-11 21:16:09 +02:00
case table:
2025-10-31 20:24:37 +01:00
renderTTYTable(w, e)
2025-09-11 21:16:09 +02:00
case code:
2025-10-31 20:24:37 +01:00
renderTTYCode(w, e)
2025-09-11 21:16:09 +02:00
case syntax:
2025-10-31 20:24:37 +01:00
renderTTYSyntax(w, e)
2025-10-28 02:48:55 +01:00
default:
return errors.New("invalid entry")
2025-09-11 21:16:09 +02:00
}
}
2025-10-10 15:47:36 +02:00
if len(d.entries) > 0 {
2025-11-02 06:27:17 +01:00
write(w, "\n")
2025-10-10 15:47:36 +02:00
}
2025-11-02 06:27:17 +01:00
_, err := f()
return err
2025-09-11 21:16:09 +02:00
}