wip
This commit is contained in:
parent
57ef6b1267
commit
59084d8292
84
lib.go
84
lib.go
@ -1,6 +1,9 @@
|
|||||||
package textfmt
|
package textfmt
|
||||||
|
|
||||||
import "io"
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
invalid = iota
|
invalid = iota
|
||||||
@ -58,9 +61,22 @@ type Entry struct {
|
|||||||
definitions []DefinitionItem
|
definitions []DefinitionItem
|
||||||
rows []TableRow
|
rows []TableRow
|
||||||
syntax SyntaxItem
|
syntax SyntaxItem
|
||||||
indentFirst int
|
|
||||||
indent int
|
|
||||||
wrapWidth int
|
wrapWidth int
|
||||||
|
indent int
|
||||||
|
indentFirst int
|
||||||
|
man struct{
|
||||||
|
section int
|
||||||
|
date time.Time
|
||||||
|
version string
|
||||||
|
category string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TitleInfo struct {
|
||||||
|
section int
|
||||||
|
date time.Time
|
||||||
|
version string
|
||||||
|
category string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Document struct {
|
type Document struct {
|
||||||
@ -89,12 +105,56 @@ func Cat(t ...Txt) Txt {
|
|||||||
return Txt{cat: t}
|
return Txt{cat: t}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Title(level int, text string) Entry {
|
func Title(level int, text string, manInfo ...TitleInfo) Entry {
|
||||||
|
if level != 0 {
|
||||||
return Entry{
|
return Entry{
|
||||||
typ: title,
|
typ: title,
|
||||||
titleLevel: level,
|
titleLevel: level,
|
||||||
text: Text(text),
|
text: Text(text),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := Entry{
|
||||||
|
typ: title,
|
||||||
|
titleLevel: level,
|
||||||
|
text: Text(text),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range manInfo {
|
||||||
|
if m.section != 0 {
|
||||||
|
e.man.section = m.section
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.date.IsZero() {
|
||||||
|
e.man.date = m.date
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.version != "" {
|
||||||
|
e.man.version = m.version
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.category != "" {
|
||||||
|
e.man.category = m.category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func ManualSection(s int) TitleInfo {
|
||||||
|
return TitleInfo{section: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReleaseDate(d time.Time) TitleInfo {
|
||||||
|
return TitleInfo{date: d}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReleaseVersion(v string) TitleInfo {
|
||||||
|
return TitleInfo{version: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ManualCategory(c string) TitleInfo {
|
||||||
|
return TitleInfo{category: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Paragraph(t Txt) Entry {
|
func Paragraph(t Txt) Entry {
|
||||||
@ -191,13 +251,14 @@ func Syntax(items ...SyntaxItem) Entry {
|
|||||||
return Entry{typ: syntax, syntax: Sequence(items...)}
|
return Entry{typ: syntax, syntax: Sequence(items...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Indent(e Entry, first, rest int) Entry {
|
func Wrap(e Entry, width int) Entry {
|
||||||
e.indentFirst, e.indent = first, rest
|
e.wrapWidth = width
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func Wrap(e Entry, width int) Entry {
|
// indentFirst is relative to indent
|
||||||
e.wrapWidth = width
|
func Indent(e Entry, indent, indentFirst int) Entry {
|
||||||
|
e.indent, e.indentFirst = indent, indentFirst
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +270,12 @@ func Teletype(out io.Writer, d Document) error {
|
|||||||
return renderTeletype(out, d)
|
return renderTeletype(out, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Roff(io.Writer, Document) error {
|
// Runoff is an attempt to render roff format. It is primarily targeting man pages. While it may be possible to
|
||||||
|
// use for other purposes, the man macro will likely be required to render the output.
|
||||||
|
//
|
||||||
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
282
runoff.go
Normal file
282
runoff.go
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
package textfmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func escapeRoff(s string, additional ...string) string {
|
||||||
|
const invalidAdditional = "invalid additional escape definition"
|
||||||
|
|
||||||
|
var (
|
||||||
|
e []rune
|
||||||
|
lineStarted bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(additional) % 2 != 0 {
|
||||||
|
panic(errors.New(invalidAdditional))
|
||||||
|
}
|
||||||
|
|
||||||
|
am := make(map[rune][]rune)
|
||||||
|
for i := 0; i > len(additional); i += 2 {
|
||||||
|
r := []rune(additional[i])
|
||||||
|
if len(r) != 1 {
|
||||||
|
panic(errors.New(invalidAdditional))
|
||||||
|
}
|
||||||
|
|
||||||
|
am[r[0]] = []rune(additional[i + 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range []rune(s) {
|
||||||
|
switch r {
|
||||||
|
case '\\':
|
||||||
|
e = append(e, '\\', '\\')
|
||||||
|
continue
|
||||||
|
case '.':
|
||||||
|
if lineStarted {
|
||||||
|
e = append(e, '.')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e = append(e, []rune("\\&.")...)
|
||||||
|
lineStarted = true
|
||||||
|
continue
|
||||||
|
case '\'':
|
||||||
|
if lineStarted {
|
||||||
|
e = append(e, '\'')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e = append(e, []rune("\\&'")...)
|
||||||
|
lineStarted = true
|
||||||
|
continue
|
||||||
|
case '\u00a0':
|
||||||
|
e = append(e, []rune("\\~")...)
|
||||||
|
lineStarted = true
|
||||||
|
continue
|
||||||
|
case '\n':
|
||||||
|
e = append(e, '\n')
|
||||||
|
lineStarted = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, ok := am[r]; ok {
|
||||||
|
e = append(e, a...)
|
||||||
|
lineStarted = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e = append(e, r)
|
||||||
|
lineStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func manPageDate(d time.Time) string {
|
||||||
|
return fmt.Sprintf("%v %d", d.Month(), d.Year())
|
||||||
|
}
|
||||||
|
|
||||||
|
func roffString(s string, additionalEscape ...string) string {
|
||||||
|
s = singleLine(s)
|
||||||
|
return escapeRoff(s, additionalEscape...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffString(w writer, s string, additionalEscape ...string) {
|
||||||
|
s = roffString(s, additionalEscape...)
|
||||||
|
w.write(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func roffDefinitionNames(d []DefinitionItem) []string {
|
||||||
|
var n []string
|
||||||
|
for _, di := range d {
|
||||||
|
n = append(n, textToString(di.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffText(w writer, text Txt, additionalEscape ...string) {
|
||||||
|
if len(text.cat) > 0 {
|
||||||
|
for i, tc := range text.cat {
|
||||||
|
if i > 0 {
|
||||||
|
w.write(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoffText(w, tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.bold {
|
||||||
|
w.write("\\fB")
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.italic {
|
||||||
|
w.write("\\fI")
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.bold || text.italic {
|
||||||
|
defer w.write("\\fR")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoffString(w, text.link, additionalEscape...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoffString(w, text.text, additionalEscape...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffTitle(w writer, e Entry) {
|
||||||
|
if e.titleLevel != 0 || e.man.section == 0 {
|
||||||
|
p := Entry{
|
||||||
|
typ: paragraph,
|
||||||
|
text: e.text,
|
||||||
|
indent: e.indent,
|
||||||
|
indentFirst: e.indentFirst,
|
||||||
|
bold: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoffParagraph(w, p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write(".TH \"")
|
||||||
|
renderRoffText(w, e.text, "\"", "\\(dq")
|
||||||
|
w.write("\" ")
|
||||||
|
renderRoffString(w, fmt.Sprint(e.man.section), "\"", "\\(dq")
|
||||||
|
w.write(" \"")
|
||||||
|
if !e.man.date.IsZero() {
|
||||||
|
w.write(manPageDate(e.man.date))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write("\" \"")
|
||||||
|
renderRoffString(w, e.man.version, "\"", "\\(dq")
|
||||||
|
w.write("\" \"")
|
||||||
|
renderRoffString(w, e.man.category, "\"", "\\(dq")
|
||||||
|
w.write("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffParagraph(w writer, e Entry) {
|
||||||
|
w.write(".in ", e.indent, "\n.tin ", 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(".in ", e.indent + 2, "\n.tin ", e.indent + e.indentFirst, "\n")
|
||||||
|
w.write("\\(bu ")
|
||||||
|
renderRoffText(w, item.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(".in ", e.indent + maxDigits + 2, "\n.tin ", e.indent + e.indentFirst, "\n")
|
||||||
|
w.write(padRight(fmt.Sprintf("%d.", i + 1), maxDigits + 2))
|
||||||
|
renderRoffText(w, item.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffDefinitions(w writer, e Entry) {
|
||||||
|
names := roffDefinitionNames(e.definitions)
|
||||||
|
maxNameLength := maxLength(names)
|
||||||
|
for i, definition := range e.definitions {
|
||||||
|
if i > 0 {
|
||||||
|
w.write(".br\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write(".in ", e.indent + maxNameLength + 4, "\n.tin ", e.indent + e.indentFirst, "\n")
|
||||||
|
w.write("\\(bu ")
|
||||||
|
renderRoffText(w, definition.name)
|
||||||
|
w.write(":", timesn("\\~", maxNameLength - len([]rune(names[i])) + 1))
|
||||||
|
renderRoffText(w, definition.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffNumberedDefinitions(w 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(".br\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write(".in ", e.indent + maxDigits + maxNameLength + 4, "\n.tin ", 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))
|
||||||
|
renderRoffText(w, definition.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffTable(w writer, e Entry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffCode(w writer, e Entry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoffSyntax(w writer, e Entry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRoff(out io.Writer, d Document) error {
|
||||||
|
w := roffWriter{w: out}
|
||||||
|
for i, e := range d.entries {
|
||||||
|
if i > 0 {
|
||||||
|
w.write("\n.br\n.sp 1v\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.typ {
|
||||||
|
case invalid:
|
||||||
|
return errors.New("invalid entry")
|
||||||
|
case title:
|
||||||
|
renderRoffTitle(&w, e)
|
||||||
|
case paragraph:
|
||||||
|
renderRoffParagraph(&w, e)
|
||||||
|
case list:
|
||||||
|
renderRoffList(&w, e)
|
||||||
|
case numberedList:
|
||||||
|
renderRoffNumberedList(&w, e)
|
||||||
|
case definitions:
|
||||||
|
renderRoffDefinitions(&w, e)
|
||||||
|
case numberedDefinitions:
|
||||||
|
renderRoffNumberedDefinitions(&w, e)
|
||||||
|
case table:
|
||||||
|
renderRoffTable(&w, e)
|
||||||
|
case code:
|
||||||
|
renderRoffCode(&w, e)
|
||||||
|
case syntax:
|
||||||
|
renderRoffSyntax(&w, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d.entries) > 0 {
|
||||||
|
w.write("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
182
teletype.go
182
teletype.go
@ -23,29 +23,23 @@ func escapeTeletype(s string) string {
|
|||||||
return string(r)
|
return string(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func definitionNamesValues(d []DefinitionItem) ([]string, []string, error) {
|
func ttyDefinitionNames(d []DefinitionItem) ([]string, error) {
|
||||||
var n, v []string
|
var n []string
|
||||||
for _, di := range d {
|
for _, di := range d {
|
||||||
name, err := ttyTextToString(di.name)
|
name, err := ttyTextToString(di.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
value, err := ttyTextToString(di.value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n = append(n, name)
|
n = append(n, name)
|
||||||
v = append(v, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return n, v, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ttyTextToString(text Txt) (string, error) {
|
func ttyTextToString(text Txt) (string, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
w := writer{w: &b}
|
w := ttyWriter{w: &b, internal: true}
|
||||||
renderTTYText(&w, text)
|
renderTTYText(&w, text)
|
||||||
if w.err != nil {
|
if w.err != nil {
|
||||||
return "", w.err
|
return "", w.err
|
||||||
@ -54,7 +48,7 @@ func ttyTextToString(text Txt) (string, error) {
|
|||||||
return b.String(), nil
|
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 {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@ -85,135 +79,111 @@ func renderTTYText(w *writer, text Txt) {
|
|||||||
w.write(text.text)
|
w.write(text.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYTitle(w *writer, e Entry) {
|
func itemToParagraph(list Entry, itemText Txt, prefix string) Entry {
|
||||||
w.write(timesn(" ", e.indentFirst))
|
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) {
|
||||||
|
w.write(timesn(" ", e.indent))
|
||||||
renderTTYText(w, e.text)
|
renderTTYText(w, e.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYParagraph(w *writer, e Entry) {
|
func renderTTYParagraph(w writer, e Entry) {
|
||||||
var txt string
|
txt, err := ttyTextToString(e.text)
|
||||||
txt, w.err = ttyTextToString(e.text)
|
if err != nil {
|
||||||
if e.wrapWidth > 0 {
|
w.setErr(err)
|
||||||
txt = wrap(txt, e.wrapWidth, e.indentFirst, e.indent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLines(w, txt, e.indentFirst, e.indent)
|
indentFirst := e.indent + e.indentFirst
|
||||||
|
if e.wrapWidth > 0 {
|
||||||
|
txt = wrap(txt, e.wrapWidth, indentFirst, e.indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLines(w, txt, indentFirst, e.indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYList(w *writer, e Entry) {
|
func renderTTYList(w writer, e Entry) {
|
||||||
const bullet = "- "
|
|
||||||
indent := e.indent + len(bullet)
|
|
||||||
for i, item := range e.items {
|
for i, item := range e.items {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
w.write("\n")
|
w.write("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
var txt string
|
p := itemToParagraph(e, item.text, "-")
|
||||||
txt, w.err = ttyTextToString(item.text)
|
renderTTYParagraph(w, p)
|
||||||
if e.wrapWidth > 0 {
|
|
||||||
txt = wrap(txt, e.wrapWidth-len(bullet), e.indentFirst, indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.write(timesn(" ", e.indentFirst))
|
|
||||||
w.write(bullet)
|
|
||||||
writeLines(w, txt, 0, indent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYNumberedList(w *writer, e Entry) {
|
func renderTTYNumberedList(w writer, e Entry) {
|
||||||
maxDigits := numDigits(len(e.items))
|
maxDigits := numDigits(len(e.items))
|
||||||
indent := e.indent + maxDigits + 2
|
|
||||||
for i, item := range e.items {
|
for i, item := range e.items {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
w.write("\n")
|
w.write("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
var txt string
|
p := itemToParagraph(e, item.text, padRight(fmt.Sprintf("%d.", i + 1), maxDigits + 1))
|
||||||
txt, w.err = ttyTextToString(item.text)
|
renderTTYParagraph(w, p)
|
||||||
if e.wrapWidth > 0 {
|
|
||||||
txt = wrap(txt, e.wrapWidth-maxDigits-2, e.indentFirst, indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.write(timesn(" ", e.indentFirst))
|
|
||||||
w.write(padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
|
|
||||||
writeLines(w, txt, 0, indent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYDefinitions(w *writer, e Entry) {
|
func renderTTYDefinitions(w writer, e Entry) {
|
||||||
const (
|
names, err := ttyDefinitionNames(e.definitions)
|
||||||
bullet = "- "
|
|
||||||
sep = ": "
|
|
||||||
)
|
|
||||||
|
|
||||||
names, values, err := definitionNamesValues(e.definitions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.err = err
|
w.setErr(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxNameLength := maxLength(names)
|
maxNameLength := maxLength(names)
|
||||||
nameColWidth := maxNameLength + e.indentFirst + len(bullet) + len(sep)
|
for i, definition := range e.definitions {
|
||||||
valueWidth := e.wrapWidth
|
|
||||||
if valueWidth > 0 {
|
|
||||||
valueWidth -= nameColWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range names {
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
w.write("\n")
|
w.write("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.write(timesn(" ", e.indentFirst), bullet, names[i], sep)
|
p := itemToParagraph(
|
||||||
if valueWidth > 0 {
|
e,
|
||||||
values[i] = wrap(values[i], valueWidth, 0, e.indent)
|
definition.value,
|
||||||
}
|
padRight(fmt.Sprintf("- %s:", names[i]), maxNameLength + 3),
|
||||||
|
|
||||||
writeLines(
|
|
||||||
w,
|
|
||||||
values[i],
|
|
||||||
maxNameLength-len([]rune(names[i])),
|
|
||||||
nameColWidth+e.indent,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
renderTTYParagraph(w, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYNumberedDefinitions(w *writer, e Entry) {
|
func renderTTYNumberedDefinitions(w writer, e Entry) {
|
||||||
const (
|
names, err := ttyDefinitionNames(e.definitions)
|
||||||
dot = ". "
|
|
||||||
sep = ": "
|
|
||||||
)
|
|
||||||
|
|
||||||
names, values, err := definitionNamesValues(e.definitions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.err = err
|
w.setErr(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxNameLength := maxLength(names)
|
||||||
maxDigits := numDigits(len(e.definitions))
|
maxDigits := numDigits(len(e.definitions))
|
||||||
maxNameLength := maxLength(names)
|
for i, definition := range e.definitions {
|
||||||
nameColWidth := maxNameLength + e.indentFirst + maxDigits + len(dot) + len(sep)
|
|
||||||
valueWidth := e.wrapWidth
|
|
||||||
if valueWidth > 0 {
|
|
||||||
valueWidth -= nameColWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range names {
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
w.write("\n")
|
w.write("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.write(timesn(" ", e.indentFirst), padRight(fmt.Sprintf("%d.", i+1), maxDigits+2), names[i], sep)
|
p := itemToParagraph(
|
||||||
if valueWidth > 0 {
|
e,
|
||||||
values[i] = wrap(values[i], valueWidth, 0, e.indent)
|
definition.value,
|
||||||
}
|
padRight(
|
||||||
|
fmt.Sprintf(
|
||||||
writeLines(
|
"%s %s:",
|
||||||
w,
|
padRight(fmt.Sprintf("%d.", i + 1), maxDigits + 1),
|
||||||
values[i],
|
names[i],
|
||||||
maxNameLength-len([]rune(names[i])),
|
),
|
||||||
nameColWidth+e.indent,
|
maxNameLength + maxDigits + 3,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
renderTTYParagraph(w, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +206,7 @@ func ttyCellTexts(rows []TableRow) ([][]string, error) {
|
|||||||
return cellTexts, nil
|
return cellTexts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYTable(w *writer, e Entry) {
|
func renderTTYTable(w writer, e Entry) {
|
||||||
if len(e.rows) == 0 {
|
if len(e.rows) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -248,7 +218,7 @@ func renderTTYTable(w *writer, e Entry) {
|
|||||||
|
|
||||||
cellTexts, err := ttyCellTexts(e.rows)
|
cellTexts, err := ttyCellTexts(e.rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.err = err
|
w.setErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3
|
totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3
|
||||||
@ -321,19 +291,19 @@ func renderTTYTable(w *writer, e Entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYCode(w *writer, e Entry) {
|
func renderTTYCode(w writer, e Entry) {
|
||||||
e.text.text = escapeTeletype(e.text.text)
|
e.text.text = escapeTeletype(e.text.text)
|
||||||
writeLines(w, e.text.text, e.indent, e.indent)
|
writeLines(w, e.text.text, e.indent, e.indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYMultiple(w *writer, s SyntaxItem) {
|
func renderTTYMultiple(w writer, s SyntaxItem) {
|
||||||
s.topLevel = false
|
s.topLevel = false
|
||||||
s.multiple = false
|
s.multiple = false
|
||||||
renderTTYSyntaxItem(w, s)
|
renderTTYSyntaxItem(w, s)
|
||||||
w.write("...")
|
w.write("...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYRequired(w *writer, s SyntaxItem) {
|
func renderTTYRequired(w writer, s SyntaxItem) {
|
||||||
s.delimited = true
|
s.delimited = true
|
||||||
s.topLevel = false
|
s.topLevel = false
|
||||||
s.required = false
|
s.required = false
|
||||||
@ -342,7 +312,7 @@ func renderTTYRequired(w *writer, s SyntaxItem) {
|
|||||||
w.write(">")
|
w.write(">")
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYOptional(w *writer, s SyntaxItem) {
|
func renderTTYOptional(w writer, s SyntaxItem) {
|
||||||
s.delimited = true
|
s.delimited = true
|
||||||
s.topLevel = false
|
s.topLevel = false
|
||||||
s.optional = false
|
s.optional = false
|
||||||
@ -351,7 +321,7 @@ func renderTTYOptional(w *writer, s SyntaxItem) {
|
|||||||
w.write("]")
|
w.write("]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYSequence(w *writer, s SyntaxItem) {
|
func renderTTYSequence(w writer, s SyntaxItem) {
|
||||||
if !s.delimited && !s.topLevel {
|
if !s.delimited && !s.topLevel {
|
||||||
w.write("(")
|
w.write("(")
|
||||||
}
|
}
|
||||||
@ -370,7 +340,7 @@ func renderTTYSequence(w *writer, s SyntaxItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYChoice(w *writer, s SyntaxItem) {
|
func renderTTYChoice(w writer, s SyntaxItem) {
|
||||||
if !s.delimited && !s.topLevel {
|
if !s.delimited && !s.topLevel {
|
||||||
w.write("(")
|
w.write("(")
|
||||||
}
|
}
|
||||||
@ -394,11 +364,11 @@ func renderTTYChoice(w *writer, s SyntaxItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYSymbol(w *writer, s SyntaxItem) {
|
func renderTTYSymbol(w writer, s SyntaxItem) {
|
||||||
w.write(escapeTeletype(s.symbol))
|
w.write(escapeTeletype(s.symbol))
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYSyntaxItem(w *writer, s SyntaxItem) {
|
func renderTTYSyntaxItem(w writer, s SyntaxItem) {
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
// foo...
|
// foo...
|
||||||
@ -427,14 +397,14 @@ func renderTTYSyntaxItem(w *writer, s SyntaxItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTTYSyntax(w *writer, e Entry) {
|
func renderTTYSyntax(w writer, e Entry) {
|
||||||
s := e.syntax
|
s := e.syntax
|
||||||
s.topLevel = true
|
s.topLevel = true
|
||||||
renderTTYSyntaxItem(w, s)
|
renderTTYSyntaxItem(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTeletype(out io.Writer, d Document) error {
|
func renderTeletype(out io.Writer, d Document) error {
|
||||||
w := writer{w: out}
|
w := ttyWriter{w: out}
|
||||||
for i, e := range d.entries {
|
for i, e := range d.entries {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
w.write("\n\n")
|
w.write("\n\n")
|
||||||
|
|||||||
142
teletype_test.go
142
teletype_test.go
@ -32,8 +32,8 @@ func TestTeletype(t *testing.T) {
|
|||||||
textfmt.Wrap(
|
textfmt.Wrap(
|
||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Paragraph(textfmt.Text("Below you can find some test text, with various text items.")),
|
textfmt.Paragraph(textfmt.Text("Below you can find some test text, with various text items.")),
|
||||||
8,
|
|
||||||
0,
|
0,
|
||||||
|
8,
|
||||||
),
|
),
|
||||||
30,
|
30,
|
||||||
),
|
),
|
||||||
@ -47,8 +47,8 @@ func TestTeletype(t *testing.T) {
|
|||||||
textfmt.ZeroOrMore(textfmt.Symbol("Entry")),
|
textfmt.ZeroOrMore(textfmt.Symbol("Entry")),
|
||||||
textfmt.Symbol(")"),
|
textfmt.Symbol(")"),
|
||||||
),
|
),
|
||||||
8,
|
|
||||||
0,
|
0,
|
||||||
|
8,
|
||||||
),
|
),
|
||||||
|
|
||||||
textfmt.Title(1, "Entries:"),
|
textfmt.Title(1, "Entries:"),
|
||||||
@ -234,7 +234,7 @@ Entry explanations:
|
|||||||
doc := textfmt.Doc(
|
doc := textfmt.Doc(
|
||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\n on multiple lines.")), 15),
|
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\n on multiple lines.")), 15),
|
||||||
4,
|
2,
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -257,7 +257,7 @@ Entry explanations:
|
|||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), 15),
|
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), 15),
|
||||||
2,
|
2,
|
||||||
2,
|
0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ Entry explanations:
|
|||||||
doc := textfmt.Doc(
|
doc := textfmt.Doc(
|
||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), 15),
|
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), 15),
|
||||||
4,
|
2,
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -294,8 +294,8 @@ Entry explanations:
|
|||||||
doc := textfmt.Doc(
|
doc := textfmt.Doc(
|
||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), 15),
|
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), 15),
|
||||||
0,
|
|
||||||
2,
|
2,
|
||||||
|
-2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -365,6 +365,18 @@ Entry explanations:
|
|||||||
t.Fatal(b.String())
|
t.Fatal(b.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("newline in link", func(t *testing.T) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
doc := textfmt.Doc(textfmt.Paragraph(textfmt.Link("a\nlink", "https://sqrndfst.org\n/foo")))
|
||||||
|
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.String() != "a link (https://sqrndfst.org\n/foo)\n" {
|
||||||
|
t.Fatal(b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("title", func(t *testing.T) {
|
t.Run("title", func(t *testing.T) {
|
||||||
@ -410,7 +422,7 @@ Entry explanations:
|
|||||||
t.Run("indent without wrapping", func(t *testing.T) {
|
t.Run("indent without wrapping", func(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
doc := textfmt.Doc(
|
doc := textfmt.Doc(
|
||||||
textfmt.Indent(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 4, 0),
|
textfmt.Indent(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 0, 4),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := textfmt.Teletype(&b, doc); err != nil {
|
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||||
@ -427,8 +439,8 @@ Entry explanations:
|
|||||||
doc := textfmt.Doc(
|
doc := textfmt.Doc(
|
||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 12),
|
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 12),
|
||||||
4,
|
|
||||||
0,
|
0,
|
||||||
|
4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -446,8 +458,8 @@ Entry explanations:
|
|||||||
doc := textfmt.Doc(
|
doc := textfmt.Doc(
|
||||||
textfmt.Indent(
|
textfmt.Indent(
|
||||||
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 12),
|
textfmt.Wrap(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 12),
|
||||||
0,
|
|
||||||
4,
|
4,
|
||||||
|
-4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -509,8 +521,7 @@ Entry explanations:
|
|||||||
another
|
another
|
||||||
item
|
item
|
||||||
- this is a
|
- this is a
|
||||||
third
|
third item
|
||||||
item
|
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if b.String() != expect {
|
||||||
@ -526,8 +537,8 @@ Entry explanations:
|
|||||||
textfmt.Item(textfmt.Text("this is another item")),
|
textfmt.Item(textfmt.Text("this is another item")),
|
||||||
textfmt.Item(textfmt.Text("this is a third item")),
|
textfmt.Item(textfmt.Text("this is a third item")),
|
||||||
),
|
),
|
||||||
4,
|
|
||||||
0,
|
0,
|
||||||
|
4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -557,8 +568,8 @@ Entry explanations:
|
|||||||
),
|
),
|
||||||
18,
|
18,
|
||||||
),
|
),
|
||||||
4,
|
|
||||||
-2,
|
-2,
|
||||||
|
6,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -591,8 +602,8 @@ third item
|
|||||||
),
|
),
|
||||||
18,
|
18,
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
2,
|
2,
|
||||||
|
-2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -659,11 +670,9 @@ third item
|
|||||||
const expect = `1. this is an
|
const expect = `1. this is an
|
||||||
item
|
item
|
||||||
2. this is
|
2. this is
|
||||||
another
|
another item
|
||||||
item
|
|
||||||
3. this is a
|
3. this is a
|
||||||
third
|
third item
|
||||||
item
|
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if b.String() != expect {
|
||||||
@ -679,8 +688,8 @@ third item
|
|||||||
textfmt.Item(textfmt.Text("this is another item")),
|
textfmt.Item(textfmt.Text("this is another item")),
|
||||||
textfmt.Item(textfmt.Text("this is a third item")),
|
textfmt.Item(textfmt.Text("this is a third item")),
|
||||||
),
|
),
|
||||||
4,
|
|
||||||
0,
|
0,
|
||||||
|
4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -710,8 +719,8 @@ third item
|
|||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
4,
|
|
||||||
-3,
|
-3,
|
||||||
|
7,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -744,8 +753,8 @@ third item
|
|||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
3,
|
3,
|
||||||
|
-3,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -871,8 +880,8 @@ third item
|
|||||||
textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")),
|
textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")),
|
||||||
textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")),
|
textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")),
|
||||||
),
|
),
|
||||||
4,
|
|
||||||
0,
|
0,
|
||||||
|
4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -902,8 +911,8 @@ third item
|
|||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
3,
|
-6,
|
||||||
-9,
|
9,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -912,7 +921,8 @@ third item
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const expect = ` - red: looks
|
const expect = `
|
||||||
|
- red: looks
|
||||||
like strawberry
|
like strawberry
|
||||||
- green: looks
|
- green: looks
|
||||||
like grass
|
like grass
|
||||||
@ -920,8 +930,8 @@ third item
|
|||||||
like sky
|
like sky
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if "\n" + b.String() != expect {
|
||||||
t.Fatal(b.String())
|
t.Fatal("\n" + b.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -936,8 +946,8 @@ third item
|
|||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
4,
|
4,
|
||||||
|
-4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -946,7 +956,8 @@ third item
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const expect = `- red: looks like
|
const expect = `
|
||||||
|
- red: looks like
|
||||||
strawberry
|
strawberry
|
||||||
- green: looks like
|
- green: looks like
|
||||||
grass
|
grass
|
||||||
@ -954,8 +965,8 @@ third item
|
|||||||
sky
|
sky
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if "\n" + b.String() != expect {
|
||||||
t.Fatal(b.String())
|
t.Fatal("\n" + b.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1022,8 +1033,8 @@ third item
|
|||||||
textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")),
|
textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")),
|
||||||
textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")),
|
textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")),
|
||||||
),
|
),
|
||||||
4,
|
|
||||||
0,
|
0,
|
||||||
|
4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1053,8 +1064,8 @@ third item
|
|||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
3,
|
-6,
|
||||||
-9,
|
9,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1063,7 +1074,8 @@ third item
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const expect = ` 1. red: looks
|
const expect = `
|
||||||
|
1. red: looks
|
||||||
like strawberry
|
like strawberry
|
||||||
2. green: looks
|
2. green: looks
|
||||||
like grass
|
like grass
|
||||||
@ -1071,8 +1083,8 @@ third item
|
|||||||
like sky
|
like sky
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if "\n" + b.String() != expect {
|
||||||
t.Fatal(b.String())
|
t.Fatal("\n" + b.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1087,8 +1099,8 @@ third item
|
|||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
4,
|
4,
|
||||||
|
-4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1097,7 +1109,8 @@ third item
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const expect = `1. red: looks like
|
const expect = `
|
||||||
|
1. red: looks like
|
||||||
strawberry
|
strawberry
|
||||||
2. green: looks like
|
2. green: looks like
|
||||||
grass
|
grass
|
||||||
@ -1105,8 +1118,8 @@ third item
|
|||||||
sky
|
sky
|
||||||
`
|
`
|
||||||
|
|
||||||
if b.String() != expect {
|
if "\n" + b.String() != expect {
|
||||||
t.Fatal(b.String())
|
t.Fatal("\n" + b.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1556,8 +1569,8 @@ Walking through the mixed forests of Brandenburg in early autumn, one notices th
|
|||||||
textfmt.Cell(textfmt.Text("and shadow on the forest floor")),
|
textfmt.Cell(textfmt.Text("and shadow on the forest floor")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
4,
|
4,
|
||||||
|
0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1652,8 +1665,8 @@ and silver birch | their canopies creating | and shadow on the
|
|||||||
),
|
),
|
||||||
72,
|
72,
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
4,
|
4,
|
||||||
|
0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1683,6 +1696,7 @@ and silver birch | their canopies creating | and shadow on the
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("code", func(t *testing.T) {
|
t.Run("code", func(t *testing.T) {
|
||||||
|
t.Run("unindented", func(t *testing.T) {
|
||||||
const code = `func() textfmt.Document {
|
const code = `func() textfmt.Document {
|
||||||
return textfmt.Document(
|
return textfmt.Document(
|
||||||
textfmt.Paragraph(textfmt.Text("Hello, world!")),
|
textfmt.Paragraph(textfmt.Text("Hello, world!")),
|
||||||
@ -1699,6 +1713,48 @@ and silver birch | their canopies creating | and shadow on the
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("indented", func(t *testing.T) {
|
||||||
|
const code = `func() textfmt.Document {
|
||||||
|
return textfmt.Document(
|
||||||
|
textfmt.Paragraph(textfmt.Text("Hello, world!")),
|
||||||
|
)
|
||||||
|
}`
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := textfmt.Teletype(&b, textfmt.Doc(textfmt.Indent(textfmt.CodeBlock(code), 4, 0))); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const expect = ` func() textfmt.Document {
|
||||||
|
return textfmt.Document(
|
||||||
|
textfmt.Paragraph(textfmt.Text("Hello, world!")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
if b.String() != expect {
|
||||||
|
t.Fatal(b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("wrap has no effect", func(t *testing.T) {
|
||||||
|
const code = `func() textfmt.Document {
|
||||||
|
return textfmt.Document(
|
||||||
|
textfmt.Paragraph(textfmt.Text("Hello, world!")),
|
||||||
|
)
|
||||||
|
}`
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := textfmt.Teletype(&b, textfmt.Doc(textfmt.Wrap(textfmt.CodeBlock(code), 12))); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.String() != code+"\n" {
|
||||||
|
t.Fatal(b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("syntax", func(t *testing.T) {
|
t.Run("syntax", func(t *testing.T) {
|
||||||
t.Run("symbol", func(t *testing.T) {
|
t.Run("symbol", func(t *testing.T) {
|
||||||
doc := textfmt.Doc(textfmt.Syntax(textfmt.Symbol("foo")))
|
doc := textfmt.Doc(textfmt.Syntax(textfmt.Symbol("foo")))
|
||||||
|
|||||||
51
text.go
51
text.go
@ -1,12 +1,20 @@
|
|||||||
package textfmt
|
package textfmt
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
func timesn(s string, n int) string {
|
func timesn(s string, n int) string {
|
||||||
|
if n < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
ss := make([]string, n+1)
|
ss := make([]string, n+1)
|
||||||
return strings.Join(ss, s)
|
return strings.Join(ss, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// non-negative numbers only
|
||||||
func numDigits(n int) int {
|
func numDigits(n int) int {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return 1
|
return 1
|
||||||
@ -33,19 +41,26 @@ func maxLength(names []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func padRight(s string, n int) string {
|
func padRight(s string, n int) string {
|
||||||
if len(s) >= n {
|
if len([]rune(s)) >= n {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
n -= len([]rune(s))
|
n -= len([]rune(s))
|
||||||
return s + timesn(" ", n)
|
return s + timesn("\u00a0", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trim(s string) string {
|
||||||
|
return strings.TrimFunc(
|
||||||
|
s,
|
||||||
|
func(r rune) bool { return r != '\u00a0' && unicode.IsSpace(r) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func singleLine(text string) string {
|
func singleLine(text string) string {
|
||||||
var l []string
|
var l []string
|
||||||
p := strings.Split(text, "\n")
|
p := strings.Split(text, "\n")
|
||||||
for _, part := range p {
|
for _, part := range p {
|
||||||
part = strings.TrimSpace(part)
|
part = trim(part)
|
||||||
if part == "" {
|
if part == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -56,19 +71,27 @@ func singleLine(text string) string {
|
|||||||
return strings.Join(l, " ")
|
return strings.Join(l, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLines(w *writer, txt string, indentFirst, indent int) {
|
func textToString(t Txt) string {
|
||||||
lines := strings.Split(txt, "\n")
|
if len(t.cat) == 0 && t.link == "" {
|
||||||
for i, l := range lines {
|
return trim(t.text)
|
||||||
if i > 0 {
|
|
||||||
w.write("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ind := indentFirst
|
if len(t.cat) == 0 && t.text == "" {
|
||||||
if i > 0 {
|
return trim(t.link)
|
||||||
ind = indent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.write(timesn(" ", ind))
|
if len(t.cat) == 0 {
|
||||||
w.write(l)
|
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 singleLine(b.String())
|
||||||
}
|
}
|
||||||
|
|||||||
8
wrap.go
8
wrap.go
@ -27,7 +27,7 @@ func wrap(text string, width, firstIndent, restIndent int) string {
|
|||||||
for _, w := range words {
|
for _, w := range words {
|
||||||
if len(currentLine) == 0 {
|
if len(currentLine) == 0 {
|
||||||
currentLine = []string{w}
|
currentLine = []string{w}
|
||||||
lineLen = len(w)
|
lineLen = len([]rune(w))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,15 +36,15 @@ func wrap(text string, width, firstIndent, restIndent int) string {
|
|||||||
maxw = width - firstIndent
|
maxw = width - firstIndent
|
||||||
}
|
}
|
||||||
|
|
||||||
if lineLen+1+len(w) > maxw {
|
if lineLen+1+len([]rune(w)) > maxw {
|
||||||
lines = append(lines, strings.Join(currentLine, " "))
|
lines = append(lines, strings.Join(currentLine, " "))
|
||||||
currentLine = []string{w}
|
currentLine = []string{w}
|
||||||
lineLen = len(w)
|
lineLen = len([]rune(w))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
currentLine = append(currentLine, w)
|
currentLine = append(currentLine, w)
|
||||||
lineLen += 1 + len(w)
|
lineLen += 1 + len([]rune(w))
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = append(lines, strings.Join(currentLine, " "))
|
lines = append(lines, strings.Join(currentLine, " "))
|
||||||
|
|||||||
104
write.go
Normal file
104
write.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package textfmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer interface {
|
||||||
|
write(...any)
|
||||||
|
error() error
|
||||||
|
setErr(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ttyWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
internal bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type roffWriter struct {
|
||||||
|
w *bufio.Writer
|
||||||
|
internal bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ttyWriter) 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] = ' '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.w.Write([]byte(string(r))); err != nil {
|
||||||
|
w.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ttyWriter) error() error {
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ttyWriter) setErr(err error) {
|
||||||
|
w.err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *roffWriter) write(a ...any) {
|
||||||
|
for _, ai := range a {
|
||||||
|
if w.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rr []rune
|
||||||
|
s := fmt.Sprint(ai)
|
||||||
|
r := []rune(s)
|
||||||
|
for i := range r {
|
||||||
|
if r[i] == '\u00a0' {
|
||||||
|
rr = append(rr, []rune("\\~")...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rr = append(rr, r[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.w.Write([]byte(string(rr))); err != nil {
|
||||||
|
w.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *roffWriter) error() error {
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *roffWriter) setErr(err error) {
|
||||||
|
w.err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLines(w writer, txt string, indentFirst, indentRest int) {
|
||||||
|
lines := strings.Split(txt, "\n")
|
||||||
|
for i, l := range lines {
|
||||||
|
if i > 0 {
|
||||||
|
w.write("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
indent := indentFirst
|
||||||
|
if i > 0 {
|
||||||
|
indent = indentRest
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write(timesn(" ", indent))
|
||||||
|
w.write(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user