roff
This commit is contained in:
parent
59084d8292
commit
ace14e534c
8
lib.go
8
lib.go
@ -141,7 +141,7 @@ func Title(level int, text string, manInfo ...TitleInfo) Entry {
|
||||
return e
|
||||
}
|
||||
|
||||
func ManualSection(s int) TitleInfo {
|
||||
func ManSection(s int) TitleInfo {
|
||||
return TitleInfo{section: s}
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ func ReleaseVersion(v string) TitleInfo {
|
||||
return TitleInfo{version: v}
|
||||
}
|
||||
|
||||
func ManualCategory(c string) TitleInfo {
|
||||
func ManCategory(c string) TitleInfo {
|
||||
return TitleInfo{category: c}
|
||||
}
|
||||
|
||||
@ -275,8 +275,8 @@ func Teletype(out io.Writer, d Document) error {
|
||||
//
|
||||
// Text is always wrapped, or as controlled by the roff processor, except for tables. The Wrap instrunction has
|
||||
// no effect, except for tables.
|
||||
func Runoff(io.Writer, Document) error {
|
||||
return nil
|
||||
func Runoff(out io.Writer, d Document) error {
|
||||
return renderRoff(out, d)
|
||||
}
|
||||
|
||||
func Markdown(io.Writer, Document) error {
|
||||
|
||||
5
notes.txt
Normal file
5
notes.txt
Normal file
@ -0,0 +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
|
||||
236
runoff.go
236
runoff.go
@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func escapeRoff(s string, additional ...string) string {
|
||||
@ -98,6 +100,22 @@ func roffDefinitionNames(d []DefinitionItem) []string {
|
||||
return n
|
||||
}
|
||||
|
||||
func roffCellTexts(r []TableRow) ([][]string, error) {
|
||||
var cellTexts [][]string
|
||||
for _, row := range r {
|
||||
var c []string
|
||||
for _, cell := range row.cells {
|
||||
var b bytes.Buffer
|
||||
renderRoffText(&roffWriter{w: &b, internal: true}, cell.text)
|
||||
c = append(c, b.String())
|
||||
}
|
||||
|
||||
cellTexts = append(cellTexts, c)
|
||||
}
|
||||
|
||||
return cellTexts, nil
|
||||
}
|
||||
|
||||
func renderRoffText(w writer, text Txt, additionalEscape ...string) {
|
||||
if len(text.cat) > 0 {
|
||||
for i, tc := range text.cat {
|
||||
@ -125,10 +143,8 @@ func renderRoffText(w writer, text Txt, additionalEscape ...string) {
|
||||
|
||||
if text.link != "" {
|
||||
if text.text != "" {
|
||||
w.write(text.text)
|
||||
renderRoffString(w, text.text, additionalEscape...)
|
||||
w.write(" (")
|
||||
w.write(text.link)
|
||||
renderRoffString(w, text.link, additionalEscape...)
|
||||
w.write(")")
|
||||
return
|
||||
@ -143,12 +159,12 @@ func renderRoffText(w writer, text Txt, additionalEscape ...string) {
|
||||
|
||||
func renderRoffTitle(w writer, e Entry) {
|
||||
if e.titleLevel != 0 || e.man.section == 0 {
|
||||
e.text.bold = true
|
||||
p := Entry{
|
||||
typ: paragraph,
|
||||
text: e.text,
|
||||
indent: e.indent,
|
||||
indentFirst: e.indentFirst,
|
||||
bold: true,
|
||||
}
|
||||
|
||||
renderRoffParagraph(w, p)
|
||||
@ -172,17 +188,17 @@ func renderRoffTitle(w writer, e Entry) {
|
||||
}
|
||||
|
||||
func renderRoffParagraph(w writer, e Entry) {
|
||||
w.write(".in ", e.indent, "\n.tin ", e.indent + e.indentFirst, "\n")
|
||||
w.write(".in ", e.indent, "\n.ti ", e.indent + e.indentFirst, "\n")
|
||||
renderRoffText(w, e.text)
|
||||
}
|
||||
|
||||
func renderRoffList(w writer, e Entry) {
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
w.write(".br\n")
|
||||
w.write("\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent + 2, "\n.tin ", e.indent + e.indentFirst, "\n")
|
||||
w.write(".in ", e.indent + 2, "\n.ti ", e.indent + e.indentFirst, "\n")
|
||||
w.write("\\(bu ")
|
||||
renderRoffText(w, item.text)
|
||||
}
|
||||
@ -192,10 +208,10 @@ func renderRoffNumberedList(w writer, e Entry) {
|
||||
maxDigits := numDigits(len(e.items))
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
w.write(".br\n")
|
||||
w.write("\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent + maxDigits + 2, "\n.tin ", e.indent + e.indentFirst, "\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))
|
||||
renderRoffText(w, item.text)
|
||||
}
|
||||
@ -206,10 +222,10 @@ func renderRoffDefinitions(w writer, e Entry) {
|
||||
maxNameLength := maxLength(names)
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
w.write(".br\n")
|
||||
w.write("\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent + maxNameLength + 4, "\n.tin ", e.indent + e.indentFirst, "\n")
|
||||
w.write(".in ", e.indent + maxNameLength + 4, "\n.ti ", e.indent + e.indentFirst, "\n")
|
||||
w.write("\\(bu ")
|
||||
renderRoffText(w, definition.name)
|
||||
w.write(":", timesn("\\~", maxNameLength - len([]rune(names[i])) + 1))
|
||||
@ -223,10 +239,10 @@ func renderRoffNumberedDefinitions(w writer, e Entry) {
|
||||
maxNameLength := maxLength(names)
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
w.write(".br\n")
|
||||
w.write("\n.br\n")
|
||||
}
|
||||
|
||||
w.write(".in ", e.indent + maxDigits + maxNameLength + 4, "\n.tin ", e.indent + e.indentFirst, "\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))
|
||||
renderRoffText(w, definition.name)
|
||||
w.write(":", timesn("\\~", maxNameLength - len([]rune(names[i])) + 1))
|
||||
@ -235,12 +251,208 @@ func renderRoffNumberedDefinitions(w writer, e Entry) {
|
||||
}
|
||||
|
||||
func renderRoffTable(w writer, e Entry) {
|
||||
if len(e.rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
e.rows = normalizeTable(e.rows)
|
||||
if len(e.rows[0].cells) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.write(".nf\n")
|
||||
defer w.write("\n.fi")
|
||||
|
||||
cellTexts, err := roffCellTexts(e.rows)
|
||||
if err != nil {
|
||||
w.setErr(err)
|
||||
}
|
||||
|
||||
totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3
|
||||
if e.wrapWidth > 0 {
|
||||
allocatedWidth := e.wrapWidth - e.indent - totalSeparatorWidth
|
||||
columnWeights := columnWeights(cellTexts)
|
||||
targetColumnWidths := targetColumnWidths(allocatedWidth, columnWeights)
|
||||
for i := range cellTexts {
|
||||
for j := range cellTexts[i] {
|
||||
cellTexts[i][j] = wrap(cellTexts[i][j], targetColumnWidths[j], 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
columnWidths := columnWidths(cellTexts)
|
||||
totalWidth := totalSeparatorWidth
|
||||
for i := range columnWidths {
|
||||
totalWidth += columnWidths[i]
|
||||
}
|
||||
|
||||
hasHeader := e.rows[0].header
|
||||
for i := range cellTexts {
|
||||
if i > 0 {
|
||||
sep := "-"
|
||||
if hasHeader && i == 1 {
|
||||
sep = "="
|
||||
}
|
||||
|
||||
w.write("\n")
|
||||
w.write(timesn(" ", e.indent), timesn(sep, totalWidth))
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
lines := make([][]string, len(cellTexts[i]))
|
||||
for j := range cellTexts[i] {
|
||||
lines[j] = strings.Split(cellTexts[i][j], "\n")
|
||||
}
|
||||
|
||||
var maxLines int
|
||||
for j := range lines {
|
||||
if len(lines[j]) > maxLines {
|
||||
maxLines = len(lines[j])
|
||||
}
|
||||
}
|
||||
|
||||
for k := 0; k < maxLines; k++ {
|
||||
if k > 0 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
for j := range lines {
|
||||
if j == 0 {
|
||||
w.write(timesn(" ", e.indent))
|
||||
} else {
|
||||
w.write(" | ")
|
||||
}
|
||||
|
||||
var l string
|
||||
if k < len(lines[j]) {
|
||||
l = lines[j][k]
|
||||
}
|
||||
|
||||
w.write(padRight(l, columnWidths[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasHeader && len(cellTexts) == 1 {
|
||||
w.write("\n", timesn("=", totalWidth))
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffCode(w writer, e Entry) {
|
||||
w.write(".nf\n")
|
||||
defer w.write("\n.fi")
|
||||
e.text.text = escapeRoff(e.text.text)
|
||||
writeLines(w, e.text.text, e.indent, e.indent)
|
||||
}
|
||||
|
||||
func renderRoffMultiple(w writer, s SyntaxItem) {
|
||||
s.topLevel = false
|
||||
s.multiple = false
|
||||
renderRoffSyntaxItem(w, s)
|
||||
w.write("...")
|
||||
}
|
||||
|
||||
func renderRoffRequired(w writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.required = false
|
||||
w.write("<")
|
||||
renderRoffSyntaxItem(w, s)
|
||||
w.write(">")
|
||||
}
|
||||
|
||||
func renderRoffOptional(w writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.optional = false
|
||||
w.write("[")
|
||||
renderRoffSyntaxItem(w, s)
|
||||
w.write("]")
|
||||
}
|
||||
|
||||
func renderRoffSequence(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
|
||||
renderRoffSyntaxItem(w, item)
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffChoice(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
|
||||
renderRoffSyntaxItem(w, item)
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffSymbol(w writer, s SyntaxItem) {
|
||||
w.write(escapeRoff(s.symbol))
|
||||
}
|
||||
|
||||
func renderRoffSyntaxItem(w writer, s SyntaxItem) {
|
||||
switch {
|
||||
|
||||
// foo...
|
||||
case s.multiple:
|
||||
renderRoffMultiple(w, s)
|
||||
|
||||
// <foo>
|
||||
case s.required:
|
||||
renderRoffRequired(w, s)
|
||||
|
||||
// [foo]
|
||||
case s.optional:
|
||||
renderRoffOptional(w, s)
|
||||
|
||||
// foo bar baz or (foo bar baz)
|
||||
case len(s.sequence) > 0:
|
||||
renderRoffSequence(w, s)
|
||||
|
||||
// foo|bar|baz or (foo|bar|baz)
|
||||
case len(s.choice) > 0:
|
||||
renderRoffChoice(w, s)
|
||||
|
||||
// foo
|
||||
default:
|
||||
renderRoffSymbol(w, s)
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffSyntax(w writer, e Entry) {
|
||||
s := e.syntax
|
||||
s.topLevel = true
|
||||
w.write(".nf\n")
|
||||
defer w.write("\n.fi")
|
||||
w.write(timesn("\u00a0", e.indent + e.indentFirst))
|
||||
renderRoffSyntaxItem(w, s)
|
||||
}
|
||||
|
||||
func renderRoff(out io.Writer, d Document) error {
|
||||
|
||||
2300
runoff_test.go
Normal file
2300
runoff_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -400,6 +400,7 @@ func renderTTYSyntaxItem(w writer, s SyntaxItem) {
|
||||
func renderTTYSyntax(w writer, e Entry) {
|
||||
s := e.syntax
|
||||
s.topLevel = true
|
||||
w.write(timesn(" ", e.indent + e.indentFirst))
|
||||
renderTTYSyntaxItem(w, s)
|
||||
}
|
||||
|
||||
|
||||
@ -125,7 +125,7 @@ text items.
|
||||
|
||||
Document syntax:
|
||||
|
||||
textfmt.Doc ( [Entry]... )
|
||||
textfmt.Doc ( [Entry]... )
|
||||
|
||||
Entries:
|
||||
|
||||
@ -537,8 +537,8 @@ Entry explanations:
|
||||
textfmt.Item(textfmt.Text("this is another item")),
|
||||
textfmt.Item(textfmt.Text("this is a third item")),
|
||||
),
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
),
|
||||
)
|
||||
|
||||
@ -688,8 +688,8 @@ third item
|
||||
textfmt.Item(textfmt.Text("this is another item")),
|
||||
textfmt.Item(textfmt.Text("this is a third item")),
|
||||
),
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
),
|
||||
)
|
||||
|
||||
@ -880,8 +880,8 @@ third item
|
||||
textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")),
|
||||
textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")),
|
||||
),
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
),
|
||||
)
|
||||
|
||||
@ -1953,5 +1953,33 @@ and silver birch | their canopies creating | and shadow on the
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("example indented", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Indent(
|
||||
textfmt.Syntax(
|
||||
textfmt.Symbol("foo"),
|
||||
textfmt.ZeroOrMore(textfmt.Symbol("options")),
|
||||
textfmt.Required(textfmt.Symbol("filename")),
|
||||
textfmt.ZeroOrMore(
|
||||
textfmt.Choice(
|
||||
textfmt.Symbol("string"),
|
||||
textfmt.Symbol("number"),
|
||||
),
|
||||
),
|
||||
),
|
||||
4,
|
||||
0,
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if b.String() != " foo [options]... <filename> [string|number]...\n" {
|
||||
t.Fatal(b.String())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
2
text.go
2
text.go
@ -3,6 +3,8 @@ package textfmt
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"fmt"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func timesn(s string, n int) string {
|
||||
|
||||
6
write.go
6
write.go
@ -19,7 +19,7 @@ type ttyWriter struct {
|
||||
}
|
||||
|
||||
type roffWriter struct {
|
||||
w *bufio.Writer
|
||||
w io.Writer
|
||||
internal bool
|
||||
err error
|
||||
}
|
||||
@ -63,6 +63,9 @@ func (w *roffWriter) write(a ...any) {
|
||||
var rr []rune
|
||||
s := fmt.Sprint(ai)
|
||||
r := []rune(s)
|
||||
if w.internal {
|
||||
rr = r
|
||||
} else {
|
||||
for i := range r {
|
||||
if r[i] == '\u00a0' {
|
||||
rr = append(rr, []rune("\\~")...)
|
||||
@ -71,6 +74,7 @@ func (w *roffWriter) write(a ...any) {
|
||||
|
||||
rr = append(rr, r[i])
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := w.w.Write([]byte(string(rr))); err != nil {
|
||||
w.err = err
|
||||
|
||||
Loading…
Reference in New Issue
Block a user