refactor roff rendering
This commit is contained in:
parent
4c0d034620
commit
a6332946e5
265
runoff.go
265
runoff.go
@ -7,91 +7,46 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func trim(s string) string {
|
||||
return strings.TrimFunc(
|
||||
s,
|
||||
func(r rune) bool { return r != '\u00a0' && unicode.IsSpace(r) },
|
||||
)
|
||||
}
|
||||
|
||||
func textToString(t Txt) string {
|
||||
if len(t.cat) == 0 && t.link == "" {
|
||||
return trim(t.text)
|
||||
}
|
||||
|
||||
if len(t.cat) == 0 && t.text == "" {
|
||||
return trim(t.link)
|
||||
}
|
||||
|
||||
if len(t.cat) == 0 {
|
||||
return fmt.Sprintf("%s (%s)", t.text, t.link)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
for i := range t.cat {
|
||||
if i > 0 {
|
||||
b.WriteRune(' ')
|
||||
}
|
||||
|
||||
b.WriteString(textToString(t.cat[i]))
|
||||
}
|
||||
|
||||
return editString(b.String(), singleLine())
|
||||
}
|
||||
|
||||
func manPageDate(d time.Time) string {
|
||||
return fmt.Sprintf("%v %d", d.Month(), d.Year())
|
||||
}
|
||||
|
||||
func roffString(s string, additionalEscape ...string) string {
|
||||
s = editString(s, singleLine())
|
||||
return editString(s, escapeRoff(additionalEscape...))
|
||||
}
|
||||
|
||||
func renderRoffString(w writer, s string, additionalEscape ...string) {
|
||||
s = roffString(s, additionalEscape...)
|
||||
w.write(s)
|
||||
func roffTextToString(t Txt) string {
|
||||
var b bytes.Buffer
|
||||
renderRoffText(&b, t)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func roffDefinitionNames(d []DefinitionItem) []string {
|
||||
var n []string
|
||||
for _, di := range d {
|
||||
n = append(n, textToString(di.name))
|
||||
n = append(n, roffTextToString(di.name))
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func roffCellTexts(r []TableRow) ([][]string, error) {
|
||||
func roffCellTexts(r []TableRow) [][]string {
|
||||
var cellTexts [][]string
|
||||
for _, row := range r {
|
||||
var c []string
|
||||
for _, cell := range row.cells {
|
||||
var b bytes.Buffer
|
||||
w := newRoffWriter(&b, true)
|
||||
renderRoffText(w, cell.text)
|
||||
w.flush()
|
||||
if w.err != nil {
|
||||
return nil, w.err
|
||||
}
|
||||
|
||||
c = append(c, b.String())
|
||||
c = append(c, roffTextToString(cell.text))
|
||||
}
|
||||
|
||||
cellTexts = append(cellTexts, c)
|
||||
}
|
||||
|
||||
return cellTexts, nil
|
||||
return cellTexts
|
||||
}
|
||||
|
||||
func renderRoffText(w writer, text Txt, additionalEscape ...string) {
|
||||
func renderRoffText(w io.Writer, text Txt, additionalEscape ...string) {
|
||||
if len(text.cat) > 0 {
|
||||
for i, tc := range text.cat {
|
||||
if i > 0 {
|
||||
w.write(" ")
|
||||
write(w, " ")
|
||||
}
|
||||
|
||||
renderRoffText(w, tc)
|
||||
@ -101,34 +56,38 @@ func renderRoffText(w writer, text Txt, additionalEscape ...string) {
|
||||
}
|
||||
|
||||
if text.bold {
|
||||
w.write("\\fB")
|
||||
write(w, "\\fB")
|
||||
}
|
||||
|
||||
if text.italic {
|
||||
w.write("\\fI")
|
||||
write(w, "\\fI")
|
||||
}
|
||||
|
||||
if text.bold || text.italic {
|
||||
defer w.write("\\fR")
|
||||
defer write(w, "\\fR")
|
||||
}
|
||||
|
||||
if text.link != "" {
|
||||
if text.text != "" {
|
||||
renderRoffString(w, text.text, additionalEscape...)
|
||||
w.write(" (")
|
||||
renderRoffString(w, text.link, additionalEscape...)
|
||||
w.write(")")
|
||||
w, f := writeWith(w, singleLine(), escapeRoff(additionalEscape...))
|
||||
if text.link == "" {
|
||||
write(w, text.text)
|
||||
f()
|
||||
return
|
||||
}
|
||||
|
||||
renderRoffString(w, text.link, additionalEscape...)
|
||||
if text.text == "" {
|
||||
write(w, text.link)
|
||||
f()
|
||||
return
|
||||
}
|
||||
|
||||
renderRoffString(w, text.text, additionalEscape...)
|
||||
write(w, text.text)
|
||||
write(w, " (")
|
||||
write(w, text.link)
|
||||
write(w, ")")
|
||||
f()
|
||||
}
|
||||
|
||||
func renderRoffTitle(w writer, e Entry) {
|
||||
func renderRoffTitle(w io.Writer, e Entry) {
|
||||
if e.titleLevel != 0 || e.man.section == 0 {
|
||||
e.text.bold = true
|
||||
p := Entry{
|
||||
@ -142,86 +101,92 @@ func renderRoffTitle(w writer, e Entry) {
|
||||
return
|
||||
}
|
||||
|
||||
w.write(".TH \"")
|
||||
write(w, ".TH \"")
|
||||
renderRoffText(w, e.text, "\"", "\\(dq")
|
||||
w.write("\" ")
|
||||
renderRoffString(w, fmt.Sprint(e.man.section), "\"", "\\(dq")
|
||||
w.write(" \"")
|
||||
write(w, "\" ")
|
||||
w, f := writeWith(w, singleLine(), escapeRoff("\"", "\\(dq"))
|
||||
write(w, fmt.Sprint(e.man.section))
|
||||
w, _ = f()
|
||||
write(w, " \"")
|
||||
if !e.man.date.IsZero() {
|
||||
w.write(manPageDate(e.man.date))
|
||||
write(w, manPageDate(e.man.date))
|
||||
}
|
||||
|
||||
w.write("\" \"")
|
||||
renderRoffString(w, e.man.version, "\"", "\\(dq")
|
||||
w.write("\" \"")
|
||||
renderRoffString(w, e.man.category, "\"", "\\(dq")
|
||||
w.write("\"")
|
||||
write(w, "\" \"")
|
||||
w, f = writeWith(w, singleLine(), escapeRoff("\"", "\\(dq"))
|
||||
write(w, e.man.version)
|
||||
w, _ = f()
|
||||
write(w, "\" \"")
|
||||
w, f = writeWith(w, singleLine(), escapeRoff("\"", "\\(dq"))
|
||||
write(w, e.man.category)
|
||||
w, _ = f()
|
||||
write(w, "\"")
|
||||
}
|
||||
|
||||
func renderRoffParagraph(w writer, e Entry) {
|
||||
w.write(".in ", e.indent, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
func renderRoffParagraph(w io.Writer, e Entry) {
|
||||
write(w, ".in ", e.indent, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
renderRoffText(w, e.text)
|
||||
}
|
||||
|
||||
func renderRoffList(w writer, e Entry) {
|
||||
func renderRoffList(w io.Writer, e Entry) {
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
w.write("\n.br\n")
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent+2, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
w.write("\\(bu ")
|
||||
write(w, ".in ", e.indent+2, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, "\\(bu ")
|
||||
renderRoffText(w, item.text)
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffNumberedList(w writer, e Entry) {
|
||||
func renderRoffNumberedList(w io.Writer, e Entry) {
|
||||
maxDigits := numDigits(len(e.items))
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
w.write("\n.br\n")
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent+maxDigits+2, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
w.write(padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
|
||||
write(w, ".in ", e.indent+maxDigits+2, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
|
||||
renderRoffText(w, item.text)
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffDefinitions(w writer, e Entry) {
|
||||
func renderRoffDefinitions(w io.Writer, e Entry) {
|
||||
names := roffDefinitionNames(e.definitions)
|
||||
maxNameLength := maxLength(names)
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
w.write("\n.br\n")
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent+maxNameLength+4, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
w.write("\\(bu ")
|
||||
write(w, ".in ", e.indent+maxNameLength+4, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, "\\(bu ")
|
||||
renderRoffText(w, definition.name)
|
||||
w.write(":", timesn("\\~", maxNameLength-len([]rune(names[i]))+1))
|
||||
write(w, ":", timesn("\\~", maxNameLength-len([]rune(names[i]))+1))
|
||||
renderRoffText(w, definition.value)
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffNumberedDefinitions(w writer, e Entry) {
|
||||
func renderRoffNumberedDefinitions(w io.Writer, e Entry) {
|
||||
maxDigits := numDigits(len(e.definitions))
|
||||
names := roffDefinitionNames(e.definitions)
|
||||
maxNameLength := maxLength(names)
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
w.write("\n.br\n")
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent+maxDigits+maxNameLength+4, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
w.write(padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
|
||||
write(w, ".in ", e.indent+maxDigits+maxNameLength+4, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
|
||||
renderRoffText(w, definition.name)
|
||||
w.write(":", timesn("\\~", maxNameLength-len([]rune(names[i]))+1))
|
||||
write(w, ":", timesn("\\~", maxNameLength-len([]rune(names[i]))+1))
|
||||
renderRoffText(w, definition.value)
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffTable(w writer, e Entry) {
|
||||
func renderRoffTable(w io.Writer, e Entry) {
|
||||
if len(e.rows) == 0 {
|
||||
return
|
||||
}
|
||||
@ -231,14 +196,10 @@ func renderRoffTable(w writer, e Entry) {
|
||||
return
|
||||
}
|
||||
|
||||
w.write(".nf\n")
|
||||
defer w.write("\n.fi")
|
||||
|
||||
cellTexts, err := roffCellTexts(e.rows)
|
||||
if err != nil {
|
||||
w.setErr(err)
|
||||
}
|
||||
write(w, ".nf\n")
|
||||
defer write(w, "\n.fi")
|
||||
|
||||
cellTexts := roffCellTexts(e.rows)
|
||||
totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3
|
||||
if e.wrapWidth > 0 {
|
||||
allocatedWidth := e.wrapWidth - e.indent - totalSeparatorWidth
|
||||
@ -257,6 +218,7 @@ func renderRoffTable(w writer, e Entry) {
|
||||
totalWidth += columnWidths[i]
|
||||
}
|
||||
|
||||
w, f := writeWith(w, indent(e.indent, e.indent))
|
||||
hasHeader := e.rows[0].header
|
||||
for i := range cellTexts {
|
||||
if i > 0 {
|
||||
@ -265,9 +227,9 @@ func renderRoffTable(w writer, e Entry) {
|
||||
sep = "="
|
||||
}
|
||||
|
||||
w.write("\n")
|
||||
w.write(timesn(" ", e.indent), timesn(sep, totalWidth))
|
||||
w.write("\n")
|
||||
write(w, "\n")
|
||||
write(w, timesn(sep, totalWidth))
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
lines := make([][]string, len(cellTexts[i]))
|
||||
@ -284,14 +246,12 @@ func renderRoffTable(w writer, e Entry) {
|
||||
|
||||
for k := 0; k < maxLines; k++ {
|
||||
if k > 0 {
|
||||
w.write("\n")
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
for j := range lines {
|
||||
if j == 0 {
|
||||
w.write(timesn(" ", e.indent))
|
||||
} else {
|
||||
w.write(" | ")
|
||||
if j > 0 {
|
||||
write(w, " | ")
|
||||
}
|
||||
|
||||
var l string
|
||||
@ -299,57 +259,59 @@ func renderRoffTable(w writer, e Entry) {
|
||||
l = lines[j][k]
|
||||
}
|
||||
|
||||
w.write(padRight(l, columnWidths[j]))
|
||||
write(w, padRight(l, columnWidths[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasHeader && len(cellTexts) == 1 {
|
||||
w.write("\n", timesn("=", totalWidth))
|
||||
write(w, "\n", timesn("=", totalWidth))
|
||||
}
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
func renderRoffCode(w writer, e Entry) {
|
||||
w.write(".nf\n")
|
||||
defer w.write("\n.fi")
|
||||
txt := editString(e.text.text, escapeRoff())
|
||||
txt = editString(txt, indent(e.indent, e.indent))
|
||||
w.write(txt)
|
||||
func renderRoffCode(w io.Writer, e Entry) {
|
||||
write(w, ".nf\n")
|
||||
defer write(w, "\n.fi")
|
||||
w, f := writeWith(w, escapeRoff(), indent(e.indent, e.indent))
|
||||
write(w, e.text.text)
|
||||
f()
|
||||
}
|
||||
|
||||
func renderRoffMultiple(w writer, s SyntaxItem) {
|
||||
func renderRoffMultiple(w io.Writer, s SyntaxItem) {
|
||||
s.topLevel = false
|
||||
s.multiple = false
|
||||
renderRoffSyntaxItem(w, s)
|
||||
w.write("...")
|
||||
write(w, "...")
|
||||
}
|
||||
|
||||
func renderRoffRequired(w writer, s SyntaxItem) {
|
||||
func renderRoffRequired(w io.Writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.required = false
|
||||
w.write("<")
|
||||
write(w, "<")
|
||||
renderRoffSyntaxItem(w, s)
|
||||
w.write(">")
|
||||
write(w, ">")
|
||||
}
|
||||
|
||||
func renderRoffOptional(w writer, s SyntaxItem) {
|
||||
func renderRoffOptional(w io.Writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.optional = false
|
||||
w.write("[")
|
||||
write(w, "[")
|
||||
renderRoffSyntaxItem(w, s)
|
||||
w.write("]")
|
||||
write(w, "]")
|
||||
}
|
||||
|
||||
func renderRoffSequence(w writer, s SyntaxItem) {
|
||||
func renderRoffSequence(w io.Writer, s SyntaxItem) {
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write("(")
|
||||
write(w, "(")
|
||||
}
|
||||
|
||||
for i, item := range s.sequence {
|
||||
if i > 0 {
|
||||
w.write(" ")
|
||||
write(w, " ")
|
||||
}
|
||||
|
||||
item.delimited = false
|
||||
@ -357,13 +319,13 @@ func renderRoffSequence(w writer, s SyntaxItem) {
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
write(w, ")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffChoice(w writer, s SyntaxItem) {
|
||||
func renderRoffChoice(w io.Writer, s SyntaxItem) {
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write("(")
|
||||
write(w, "(")
|
||||
}
|
||||
|
||||
for i, item := range s.choice {
|
||||
@ -373,7 +335,7 @@ func renderRoffChoice(w writer, s SyntaxItem) {
|
||||
separator = "\n"
|
||||
}
|
||||
|
||||
w.write(separator)
|
||||
write(w, separator)
|
||||
}
|
||||
|
||||
item.delimited = false
|
||||
@ -381,15 +343,15 @@ func renderRoffChoice(w writer, s SyntaxItem) {
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
write(w, ")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffSymbol(w writer, s SyntaxItem) {
|
||||
w.write(editString(s.symbol, escapeRoff()))
|
||||
func renderRoffSymbol(w io.Writer, s SyntaxItem) {
|
||||
write(w, s.symbol)
|
||||
}
|
||||
|
||||
func renderRoffSyntaxItem(w writer, s SyntaxItem) {
|
||||
func renderRoffSyntaxItem(w io.Writer, s SyntaxItem) {
|
||||
switch {
|
||||
|
||||
// foo...
|
||||
@ -418,24 +380,21 @@ func renderRoffSyntaxItem(w writer, s SyntaxItem) {
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffSyntax(w writer, e Entry) {
|
||||
func renderRoffSyntax(w io.Writer, e Entry) {
|
||||
s := e.syntax
|
||||
s.topLevel = true
|
||||
w.write(".nf\n")
|
||||
defer w.write("\n.fi")
|
||||
w.write(timesn("\u00a0", e.indent))
|
||||
write(w, ".nf\n")
|
||||
defer write(w, "\n.fi")
|
||||
w, f := writeWith(w, escapeRoff(), indent(e.indent, e.indent))
|
||||
renderRoffSyntaxItem(w, s)
|
||||
f()
|
||||
}
|
||||
|
||||
func renderRoff(out io.Writer, d Document) error {
|
||||
w := newRoffWriter(out, false)
|
||||
w, f := writeWith(out, roffNBSP(), errorHandler)
|
||||
for i, e := range d.entries {
|
||||
if err := w.error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
w.write("\n.br\n.sp 1v\n")
|
||||
write(w, "\n.br\n.sp 1v\n")
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
@ -463,9 +422,9 @@ func renderRoff(out io.Writer, d Document) error {
|
||||
}
|
||||
|
||||
if len(d.entries) > 0 {
|
||||
w.write("\n")
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
w.flush()
|
||||
return w.err
|
||||
_, err := f()
|
||||
return err
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ Below you can find some test text, with various text items.
|
||||
.br
|
||||
.sp 1v
|
||||
.nf
|
||||
\~\~\~\~\~\~\~\~textfmt.Doc ( [Entry]... )
|
||||
textfmt.Doc ( [Entry]... )
|
||||
.fi
|
||||
.br
|
||||
.sp 1v
|
||||
@ -329,7 +329,7 @@ Below you can find some test text, with various text items.
|
||||
.br
|
||||
.sp 1v
|
||||
.nf
|
||||
\~\~\~\~\~\~\~\~textfmt.Doc ( [Entry]... )
|
||||
textfmt.Doc ( [Entry]... )
|
||||
.fi
|
||||
.br
|
||||
.sp 1v
|
||||
@ -2292,7 +2292,7 @@ and silver birch\~\~\~\~ | their canopies creating\~ | and shadow on the
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if b.String() != ".nf\n\\~\\~\\~\\~foo [options]... <filename> [string|number]...\n.fi\n" {
|
||||
if b.String() != ".nf\n foo [options]... <filename> [string|number]...\n.fi\n" {
|
||||
t.Fatal(b.String())
|
||||
}
|
||||
})
|
||||
|
||||
@ -149,8 +149,7 @@ func ttyCellTexts(rows []TableRow) [][]string {
|
||||
for _, row := range rows {
|
||||
var c []string
|
||||
for _, cell := range row.cells {
|
||||
txt := ttyTextToString(cell.text)
|
||||
c = append(c, txt)
|
||||
c = append(c, ttyTextToString(cell.text))
|
||||
}
|
||||
|
||||
cellTexts = append(cellTexts, c)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user