diff --git a/runoff.go b/runoff.go index 54e3f85..4211a5b 100644 --- a/runoff.go +++ b/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(")") - return - } - - renderRoffString(w, text.link, additionalEscape...) + w, f := writeWith(w, singleLine(), escapeRoff(additionalEscape...)) + if text.link == "" { + write(w, text.text) + f() return } - renderRoffString(w, text.text, additionalEscape...) + if text.text == "" { + write(w, text.link) + f() + return + } + + 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 } diff --git a/runoff_test.go b/runoff_test.go index e20f28b..6485eb7 100644 --- a/runoff_test.go +++ b/runoff_test.go @@ -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]... [string|number]...\n.fi\n" { + if b.String() != ".nf\n foo [options]... [string|number]...\n.fi\n" { t.Fatal(b.String()) } }) diff --git a/teletype.go b/teletype.go index 6fb9b89..ca57e9b 100644 --- a/teletype.go +++ b/teletype.go @@ -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)