support custom bullet lengths in tty and roff
This commit is contained in:
parent
cff44cc943
commit
0ddb884813
1
lib.go
1
lib.go
@ -70,7 +70,6 @@ type Entry struct {
|
||||
rows []TableRow
|
||||
syntax SyntaxItem
|
||||
wrapWidth int
|
||||
wrapWidthFirst int
|
||||
indent int
|
||||
indentFirst int
|
||||
man struct {
|
||||
|
||||
210
runoff.go
210
runoff.go
@ -13,21 +13,45 @@ func manPageDate(d time.Time) string {
|
||||
return fmt.Sprintf("%v %d", d.Month(), d.Year())
|
||||
}
|
||||
|
||||
func escapeRoffString(s string, additionalEscape ...string) string {
|
||||
var b bytes.Buffer
|
||||
w, f := writeWith(&b, escapeRoff(additionalEscape...))
|
||||
write(w, s)
|
||||
f()
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func roffTextLength(t Txt) int {
|
||||
var l int
|
||||
if len(t.cat) > 0 {
|
||||
for i, tc := range t.cat {
|
||||
if i > 0 {
|
||||
l++
|
||||
}
|
||||
|
||||
l += roffTextLength(tc)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
if t.link == "" {
|
||||
return len([]rune(t.text))
|
||||
}
|
||||
|
||||
if t.text == "" {
|
||||
return len([]rune(t.link))
|
||||
}
|
||||
|
||||
return len([]rune(t.text)) + len([]rune(t.link)) + 3
|
||||
}
|
||||
|
||||
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, roffTextToString(di.name))
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func roffCellTexts(r []TableRow) [][]string {
|
||||
var cellTexts [][]string
|
||||
for _, row := range r {
|
||||
@ -129,90 +153,154 @@ func renderRoffParagraph(w io.Writer, e Entry) {
|
||||
}
|
||||
|
||||
func renderRoffList(w io.Writer, e Entry) {
|
||||
bullets := make([]string, len(e.items))
|
||||
bulletLengths := make([]int, len(e.items))
|
||||
for i := range e.items {
|
||||
itemStyle := mergeItemStyles(e.items[i].style)
|
||||
if itemStyle.noBullet {
|
||||
continue
|
||||
}
|
||||
|
||||
if itemStyle.bullet == "" {
|
||||
bullets[i] = "\\(bu"
|
||||
bulletLengths[i] = 1
|
||||
continue
|
||||
}
|
||||
|
||||
bullets[i] = escapeRoffString(itemStyle.bullet, " ", "\\~")
|
||||
bulletLengths[i] = len([]rune(itemStyle.bullet))
|
||||
}
|
||||
|
||||
var maxBulletLength int
|
||||
for _, l := range bulletLengths {
|
||||
if l > maxBulletLength {
|
||||
maxBulletLength = l
|
||||
}
|
||||
}
|
||||
|
||||
for i := range bullets {
|
||||
if bulletLengths[i] == maxBulletLength {
|
||||
continue
|
||||
}
|
||||
|
||||
bullets[i] = fmt.Sprintf(
|
||||
"%s%s",
|
||||
bullets[i],
|
||||
timesn("\\~", maxBulletLength-bulletLengths[i]),
|
||||
)
|
||||
}
|
||||
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
var (
|
||||
bullet string
|
||||
bulletIndent int
|
||||
)
|
||||
|
||||
itemStyle := mergeItemStyles(item.style)
|
||||
if !itemStyle.noBullet {
|
||||
if itemStyle.bullet == "" {
|
||||
bullet = "\\(bu "
|
||||
bulletIndent = 2
|
||||
} else {
|
||||
bullet = itemStyle.bullet + " "
|
||||
bulletIndent = len([]rune(bullet))
|
||||
}
|
||||
indent := e.indent
|
||||
if maxBulletLength > 0 {
|
||||
indent += maxBulletLength + 1
|
||||
}
|
||||
|
||||
write(w, ".in ", indent, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, bullets[i])
|
||||
if maxBulletLength > 0 {
|
||||
write(w, "\\~")
|
||||
}
|
||||
|
||||
write(w, ".in ", e.indent+bulletIndent, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, bullet)
|
||||
renderRoffText(w, item.text)
|
||||
}
|
||||
}
|
||||
|
||||
func renderRoffNumberedList(w io.Writer, e Entry) {
|
||||
maxDigits := numDigits(len(e.items))
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
write(w, "\n.br\n")
|
||||
items := make([]ListItem, len(e.items))
|
||||
for i := range e.items {
|
||||
items[i] = Item(
|
||||
e.items[i].text,
|
||||
append(e.items[i].style, Bullet(fmt.Sprintf("%d.", i+1)))...,
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
e.typ = list
|
||||
e.items = items
|
||||
renderRoffList(w, e)
|
||||
}
|
||||
|
||||
func renderRoffDefinitions(w io.Writer, e Entry) {
|
||||
names := roffDefinitionNames(e.definitions)
|
||||
maxNameLength := maxLength(names)
|
||||
itemStyles := make([]ItemStyle, len(e.definitions))
|
||||
for i := range e.definitions {
|
||||
itemStyles[i] = mergeItemStyles(e.definitions[i].style)
|
||||
}
|
||||
|
||||
bullets := make([]string, len(itemStyles))
|
||||
bulletLengths := make([]int, len(itemStyles))
|
||||
for i := range itemStyles {
|
||||
if itemStyles[i].noBullet {
|
||||
continue
|
||||
}
|
||||
|
||||
if itemStyles[i].bullet == "" {
|
||||
bullets[i] = "\\(bu"
|
||||
bulletLengths[i] = 1
|
||||
continue
|
||||
}
|
||||
|
||||
bullets[i] = escapeRoffString(itemStyles[i].bullet, " ", "\\~")
|
||||
bulletLengths[i] = len([]rune(itemStyles[i].bullet))
|
||||
}
|
||||
|
||||
var maxBulletLength int
|
||||
for i := range bulletLengths {
|
||||
if bulletLengths[i] > maxBulletLength {
|
||||
maxBulletLength = bulletLengths[i]
|
||||
}
|
||||
}
|
||||
|
||||
nameLengths := make([]int, len(e.definitions))
|
||||
for i := range e.definitions {
|
||||
nameLengths[i] = roffTextLength(e.definitions[i].name)
|
||||
}
|
||||
|
||||
var maxNameLength int
|
||||
for i := range nameLengths {
|
||||
if nameLengths[i] > maxNameLength {
|
||||
maxNameLength = nameLengths[i]
|
||||
}
|
||||
}
|
||||
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
var bullet string
|
||||
padLength := 2
|
||||
itemStyle := mergeItemStyles(definition.style)
|
||||
if !itemStyle.noBullet {
|
||||
if itemStyle.bullet == "" {
|
||||
bullet = "\\(bu "
|
||||
padLength = 4
|
||||
} else {
|
||||
bullet = itemStyle.bullet + " "
|
||||
padLength = len([]rune(bullet)) + 2
|
||||
}
|
||||
indent := e.indent + maxNameLength + 2
|
||||
if maxBulletLength > 0 {
|
||||
indent += maxBulletLength + 1
|
||||
}
|
||||
|
||||
write(w, ".in ", indent, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, bullets[i])
|
||||
if maxBulletLength > 0 {
|
||||
write(w, timesn("\\~", maxBulletLength-bulletLengths[i]+1))
|
||||
}
|
||||
|
||||
write(w, ".in ", e.indent+maxNameLength+padLength, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, bullet)
|
||||
renderRoffText(w, definition.name)
|
||||
write(w, ":", timesn("\\~", maxNameLength-len([]rune(names[i]))+1))
|
||||
write(w, ":", timesn("\\~", maxNameLength-nameLengths[i]+1))
|
||||
renderRoffText(w, definition.value)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
write(w, "\n.br\n")
|
||||
defs := make([]DefinitionItem, len(e.definitions))
|
||||
for i := range e.definitions {
|
||||
defs[i] = Definition(
|
||||
e.definitions[i].name,
|
||||
e.definitions[i].value,
|
||||
append(e.definitions[i].style, Bullet(fmt.Sprintf("%d.", i+1)))...,
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
write(w, ":", timesn("\\~", maxNameLength-len([]rune(names[i]))+1))
|
||||
renderRoffText(w, definition.value)
|
||||
}
|
||||
e.typ = definitions
|
||||
e.definitions = defs
|
||||
renderRoffDefinitions(w, e)
|
||||
}
|
||||
|
||||
func renderRoffTable(w io.Writer, e Entry) {
|
||||
|
||||
300
runoff_test.go
300
runoff_test.go
@ -144,39 +144,39 @@ textfmt supports the following entries:
|
||||
.sp 1v
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu CodeBlock
|
||||
\(bu\~CodeBlock
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu DefinitionList
|
||||
\(bu\~DefinitionList
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu List
|
||||
\(bu\~List
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu NumberedDefinitionList
|
||||
\(bu\~NumberedDefinitionList
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu NumberedList
|
||||
\(bu\~NumberedList
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Paragraph
|
||||
\(bu\~Paragraph
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Syntax
|
||||
\(bu\~Syntax
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Table
|
||||
\(bu\~Table
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Title
|
||||
\(bu\~Title
|
||||
.br
|
||||
.sp 1v
|
||||
.in 0
|
||||
@ -186,39 +186,39 @@ textfmt supports the following entries:
|
||||
.sp 1v
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu CodeBlock:\~\~\~\~\~\~\~\~\~\~\~\~\~\~a multiline block of code
|
||||
\(bu\~CodeBlock:\~\~\~\~\~\~\~\~\~\~\~\~\~\~a multiline block of code
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu DefinitionList:\~\~\~\~\~\~\~\~\~a list of definitions like this one
|
||||
\(bu\~DefinitionList:\~\~\~\~\~\~\~\~\~a list of definitions like this one
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu List:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a list of items
|
||||
\(bu\~List:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a list of items
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu NumberedDefinitionList:\~numbered definitions
|
||||
\(bu\~NumberedDefinitionList:\~numbered definitions
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu NumberedList:\~\~\~\~\~\~\~\~\~\~\~numbered list
|
||||
\(bu\~NumberedList:\~\~\~\~\~\~\~\~\~\~\~numbered list
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Paragraph:\~\~\~\~\~\~\~\~\~\~\~\~\~\~paragraph of text
|
||||
\(bu\~Paragraph:\~\~\~\~\~\~\~\~\~\~\~\~\~\~paragraph of text
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Syntax:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a syntax expression
|
||||
\(bu\~Syntax:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a syntax expression
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Table:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a table
|
||||
\(bu\~Table:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a table
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Title:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a title
|
||||
\(bu\~Title:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a title
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -354,39 +354,39 @@ textfmt supports the following entries:
|
||||
.sp 1v
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu CodeBlock
|
||||
\(bu\~CodeBlock
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu DefinitionList
|
||||
\(bu\~DefinitionList
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu List
|
||||
\(bu\~List
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu NumberedDefinitionList
|
||||
\(bu\~NumberedDefinitionList
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu NumberedList
|
||||
\(bu\~NumberedList
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Paragraph
|
||||
\(bu\~Paragraph
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Syntax
|
||||
\(bu\~Syntax
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Table
|
||||
\(bu\~Table
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu Title
|
||||
\(bu\~Title
|
||||
.br
|
||||
.sp 1v
|
||||
.in 0
|
||||
@ -396,39 +396,39 @@ textfmt supports the following entries:
|
||||
.sp 1v
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu CodeBlock:\~\~\~\~\~\~\~\~\~\~\~\~\~\~a multiline block of code
|
||||
\(bu\~CodeBlock:\~\~\~\~\~\~\~\~\~\~\~\~\~\~a multiline block of code
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu DefinitionList:\~\~\~\~\~\~\~\~\~a list of definitions like this one
|
||||
\(bu\~DefinitionList:\~\~\~\~\~\~\~\~\~a list of definitions like this one
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu List:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a list of items
|
||||
\(bu\~List:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a list of items
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu NumberedDefinitionList:\~numbered definitions
|
||||
\(bu\~NumberedDefinitionList:\~numbered definitions
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu NumberedList:\~\~\~\~\~\~\~\~\~\~\~numbered list
|
||||
\(bu\~NumberedList:\~\~\~\~\~\~\~\~\~\~\~numbered list
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Paragraph:\~\~\~\~\~\~\~\~\~\~\~\~\~\~paragraph of text
|
||||
\(bu\~Paragraph:\~\~\~\~\~\~\~\~\~\~\~\~\~\~paragraph of text
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Syntax:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a syntax expression
|
||||
\(bu\~Syntax:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a syntax expression
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Table:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a table
|
||||
\(bu\~Table:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a table
|
||||
.br
|
||||
.in 26
|
||||
.ti 0
|
||||
\(bu Title:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a title
|
||||
\(bu\~Title:\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~\~a title
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -781,15 +781,15 @@ This is a paragraph.
|
||||
|
||||
const expect = `.in 2
|
||||
.ti 0
|
||||
\(bu this is an item
|
||||
\(bu\~this is an item
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu this is another item
|
||||
\(bu\~this is another item
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
\(bu this is a third item
|
||||
\(bu\~this is a third item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -817,15 +817,15 @@ This is a paragraph.
|
||||
|
||||
const expect = `.in 6
|
||||
.ti 4
|
||||
\(bu this is an item
|
||||
\(bu\~this is an item
|
||||
.br
|
||||
.in 6
|
||||
.ti 4
|
||||
\(bu this is another item
|
||||
\(bu\~this is another item
|
||||
.br
|
||||
.in 6
|
||||
.ti 4
|
||||
\(bu this is a third item
|
||||
\(bu\~this is a third item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -853,15 +853,15 @@ This is a paragraph.
|
||||
|
||||
const expect = `.in 0
|
||||
.ti 4
|
||||
\(bu this is an item
|
||||
\(bu\~this is an item
|
||||
.br
|
||||
.in 0
|
||||
.ti 4
|
||||
\(bu this is another item
|
||||
\(bu\~this is another item
|
||||
.br
|
||||
.in 0
|
||||
.ti 4
|
||||
\(bu this is a third item
|
||||
\(bu\~this is a third item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -889,15 +889,15 @@ This is a paragraph.
|
||||
|
||||
const expect = `.in 4
|
||||
.ti 0
|
||||
\(bu this is an item
|
||||
\(bu\~this is an item
|
||||
.br
|
||||
.in 4
|
||||
.ti 0
|
||||
\(bu this is another item
|
||||
\(bu\~this is another item
|
||||
.br
|
||||
.in 4
|
||||
.ti 0
|
||||
\(bu this is a third item
|
||||
\(bu\~this is a third item
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -922,15 +922,15 @@ This is a paragraph.
|
||||
const expect = `
|
||||
.in 2
|
||||
.ti 0
|
||||
* foo bar baz
|
||||
*\~foo bar baz
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
* qux
|
||||
*\~qux
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
* quux
|
||||
*\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -955,15 +955,15 @@ This is a paragraph.
|
||||
const expect = `
|
||||
.in 3
|
||||
.ti 0
|
||||
=> foo bar baz
|
||||
=>\~foo bar baz
|
||||
.br
|
||||
.in 3
|
||||
.ti 0
|
||||
=> qux
|
||||
=>\~qux
|
||||
.br
|
||||
.in 3
|
||||
.ti 0
|
||||
=> quux
|
||||
=>\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -997,6 +997,72 @@ qux
|
||||
.in 0
|
||||
.ti 0
|
||||
quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("varying bullet length", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.List(
|
||||
textfmt.Item(textfmt.Text("foo bar baz"), textfmt.Bullet("=>")),
|
||||
textfmt.Item(textfmt.Text("qux"), textfmt.Bullet("==>")),
|
||||
textfmt.Item(textfmt.Text("quux"), textfmt.Bullet("=>")),
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Runoff(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
.in 4
|
||||
.ti 0
|
||||
=>\~\~foo bar baz
|
||||
.br
|
||||
.in 4
|
||||
.ti 0
|
||||
==>\~qux
|
||||
.br
|
||||
.in 4
|
||||
.ti 0
|
||||
=>\~\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("escape custom bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.List(
|
||||
textfmt.Item(textfmt.Text("foo bar baz"), textfmt.Bullet(".>")),
|
||||
textfmt.Item(textfmt.Text("qux"), textfmt.Bullet("=>")),
|
||||
textfmt.Item(textfmt.Text("quux"), textfmt.Bullet("=>")),
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Runoff(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
.in 3
|
||||
.ti 0
|
||||
\&.>\~foo bar baz
|
||||
.br
|
||||
.in 3
|
||||
.ti 0
|
||||
=>\~qux
|
||||
.br
|
||||
.in 3
|
||||
.ti 0
|
||||
=>\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -1241,15 +1307,15 @@ quux
|
||||
|
||||
const expect = `.in 9
|
||||
.ti 0
|
||||
\(bu red:\~\~\~looks like strawberry
|
||||
\(bu\~red:\~\~\~looks like strawberry
|
||||
.br
|
||||
.in 9
|
||||
.ti 0
|
||||
\(bu green:\~looks like grass
|
||||
\(bu\~green:\~looks like grass
|
||||
.br
|
||||
.in 9
|
||||
.ti 0
|
||||
\(bu blue:\~\~looks like sky
|
||||
\(bu\~blue:\~\~looks like sky
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -1277,15 +1343,15 @@ quux
|
||||
|
||||
const expect = `.in 13
|
||||
.ti 4
|
||||
\(bu red:\~\~\~looks like strawberry
|
||||
\(bu\~red:\~\~\~looks like strawberry
|
||||
.br
|
||||
.in 13
|
||||
.ti 4
|
||||
\(bu green:\~looks like grass
|
||||
\(bu\~green:\~looks like grass
|
||||
.br
|
||||
.in 13
|
||||
.ti 4
|
||||
\(bu blue:\~\~looks like sky
|
||||
\(bu\~blue:\~\~looks like sky
|
||||
`
|
||||
|
||||
if b.String() != expect {
|
||||
@ -1314,15 +1380,15 @@ quux
|
||||
const expect = `
|
||||
.in 3
|
||||
.ti 3
|
||||
\(bu red:\~\~\~looks like strawberry
|
||||
\(bu\~red:\~\~\~looks like strawberry
|
||||
.br
|
||||
.in 3
|
||||
.ti 3
|
||||
\(bu green:\~looks like grass
|
||||
\(bu\~green:\~looks like grass
|
||||
.br
|
||||
.in 3
|
||||
.ti 3
|
||||
\(bu blue:\~\~looks like sky
|
||||
\(bu\~blue:\~\~looks like sky
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -1351,15 +1417,15 @@ quux
|
||||
const expect = `
|
||||
.in 13
|
||||
.ti 0
|
||||
\(bu red:\~\~\~looks like strawberry
|
||||
\(bu\~red:\~\~\~looks like strawberry
|
||||
.br
|
||||
.in 13
|
||||
.ti 0
|
||||
\(bu green:\~looks like grass
|
||||
\(bu\~green:\~looks like grass
|
||||
.br
|
||||
.in 13
|
||||
.ti 0
|
||||
\(bu blue:\~\~looks like sky
|
||||
\(bu\~blue:\~\~looks like sky
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -1396,15 +1462,15 @@ quux
|
||||
const expect = `
|
||||
.in 9
|
||||
.ti 0
|
||||
* one:\~\~\~foo bar baz
|
||||
*\~one:\~\~\~foo bar baz
|
||||
.br
|
||||
.in 9
|
||||
.ti 0
|
||||
* two:\~\~\~qux
|
||||
*\~two:\~\~\~qux
|
||||
.br
|
||||
.in 9
|
||||
.ti 0
|
||||
* three:\~quux
|
||||
*\~three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -1441,15 +1507,15 @@ quux
|
||||
const expect = `
|
||||
.in 10
|
||||
.ti 0
|
||||
=> one:\~\~\~foo bar baz
|
||||
=>\~one:\~\~\~foo bar baz
|
||||
.br
|
||||
.in 10
|
||||
.ti 0
|
||||
=> two:\~\~\~qux
|
||||
=>\~two:\~\~\~qux
|
||||
.br
|
||||
.in 10
|
||||
.ti 0
|
||||
=> three:\~quux
|
||||
=>\~three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -1495,6 +1561,96 @@ two:\~\~\~qux
|
||||
.in 7
|
||||
.ti 0
|
||||
three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("varying bullet length", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.DefinitionList(
|
||||
textfmt.Definition(
|
||||
textfmt.Text("one"),
|
||||
textfmt.Text("foo bar baz"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("two"),
|
||||
textfmt.Text("qux"),
|
||||
textfmt.Bullet("==>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("three"),
|
||||
textfmt.Text("quux"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Runoff(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
.in 11
|
||||
.ti 0
|
||||
=>\~\~one:\~\~\~foo bar baz
|
||||
.br
|
||||
.in 11
|
||||
.ti 0
|
||||
==>\~two:\~\~\~qux
|
||||
.br
|
||||
.in 11
|
||||
.ti 0
|
||||
=>\~\~three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("escape custom bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.DefinitionList(
|
||||
textfmt.Definition(
|
||||
textfmt.Text("one"),
|
||||
textfmt.Text("foo bar baz"),
|
||||
textfmt.Bullet(".>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("two"),
|
||||
textfmt.Text("qux"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("three"),
|
||||
textfmt.Text("quux"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Runoff(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
.in 10
|
||||
.ti 0
|
||||
\&.>\~one:\~\~\~foo bar baz
|
||||
.br
|
||||
.in 10
|
||||
.ti 0
|
||||
=>\~two:\~\~\~qux
|
||||
.br
|
||||
.in 10
|
||||
.ti 0
|
||||
=>\~three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
|
||||
125
teletype.go
125
teletype.go
@ -65,11 +65,10 @@ func renderTTYTitle(w io.Writer, e Entry) {
|
||||
func renderTTYParagraph(w io.Writer, e Entry) {
|
||||
var indentation wrapper
|
||||
indentFirst := e.indent + e.indentFirst
|
||||
wrapWidthFirst := e.wrapWidth + e.wrapWidthFirst
|
||||
if e.wrapWidth == 0 {
|
||||
indentation = indent(indentFirst, e.indent)
|
||||
} else {
|
||||
indentation = wrapIndent(indentFirst, e.indent, wrapWidthFirst, e.wrapWidth)
|
||||
indentation = wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth)
|
||||
}
|
||||
|
||||
w, f := writeWith(w, indentation)
|
||||
@ -78,95 +77,111 @@ func renderTTYParagraph(w io.Writer, e Entry) {
|
||||
}
|
||||
|
||||
func renderTTYList(w io.Writer, e Entry) {
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
var p Entry
|
||||
var bullets []string
|
||||
for _, item := range e.items {
|
||||
itemStyle := mergeItemStyles(item.style)
|
||||
if itemStyle.noBullet {
|
||||
p = itemToParagraph(e, item.text)
|
||||
bullets = append(bullets, "")
|
||||
} else {
|
||||
bullet := "-"
|
||||
if itemStyle.bullet != "" {
|
||||
bullet = itemStyle.bullet
|
||||
}
|
||||
|
||||
p = itemToParagraph(e, item.text, bullet)
|
||||
bullets = append(bullets, bullet)
|
||||
}
|
||||
}
|
||||
|
||||
renderTTYParagraph(w, p)
|
||||
maxBulletLength := maxLength(bullets)
|
||||
for i := range bullets {
|
||||
bullets[i] = padRight(bullets[i], maxBulletLength)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYNumberedList(w io.Writer, e Entry) {
|
||||
maxDigits := numDigits(len(e.items))
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
p := itemToParagraph(e, item.text, padRight(fmt.Sprintf("%d.", i+1), maxDigits+1))
|
||||
p := itemToParagraph(e, item.text, bullets[i])
|
||||
renderTTYParagraph(w, p)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func renderTTYDefinitions(w io.Writer, e Entry) {
|
||||
names := ttyDefinitionNames(e.definitions)
|
||||
maxNameLength := maxLength(names)
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
write(w, "\n")
|
||||
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
|
||||
}
|
||||
|
||||
if itemStyles[i].bullet == "" {
|
||||
bullets[i] = "-"
|
||||
continue
|
||||
}
|
||||
|
||||
bullets[i] = itemStyles[i].bullet
|
||||
}
|
||||
|
||||
maxBulletLength := maxLength(bullets)
|
||||
items := make([]ListItem, len(e.definitions))
|
||||
for i := range e.definitions {
|
||||
var bullet string
|
||||
padLength := 1
|
||||
itemStyle := mergeItemStyles(definition.style)
|
||||
if !itemStyle.noBullet {
|
||||
if itemStyle.bullet == "" {
|
||||
bullet = "- "
|
||||
padLength = 3
|
||||
if maxBulletLength == 0 {
|
||||
bullet = fmt.Sprintf("%s:", ttyTextToString(e.definitions[i].name))
|
||||
} else {
|
||||
bullet = itemStyle.bullet + " "
|
||||
padLength = len([]rune(bullet)) + 1
|
||||
}
|
||||
}
|
||||
|
||||
p := itemToParagraph(
|
||||
e,
|
||||
definition.value,
|
||||
padRight(fmt.Sprintf("%s%s:", bullet, names[i]), maxNameLength+padLength),
|
||||
bullet = fmt.Sprintf(
|
||||
"%s %s:",
|
||||
padRight(bullets[i], maxBulletLength),
|
||||
ttyTextToString(e.definitions[i].name),
|
||||
)
|
||||
|
||||
renderTTYParagraph(w, p)
|
||||
}
|
||||
|
||||
items[i] = Item(e.definitions[i].value, Bullet(bullet))
|
||||
}
|
||||
|
||||
e.typ = list
|
||||
e.items = items
|
||||
renderTTYList(w, e)
|
||||
}
|
||||
|
||||
func renderTTYNumberedDefinitions(w io.Writer, e Entry) {
|
||||
names := ttyDefinitionNames(e.definitions)
|
||||
maxNameLength := maxLength(names)
|
||||
maxDigits := numDigits(len(e.definitions))
|
||||
var defs []DefinitionItem
|
||||
for i, definition := range e.definitions {
|
||||
if i > 0 {
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
p := itemToParagraph(
|
||||
e,
|
||||
defs = append(
|
||||
defs,
|
||||
Definition(
|
||||
definition.name,
|
||||
definition.value,
|
||||
padRight(
|
||||
fmt.Sprintf(
|
||||
"%s %s:",
|
||||
padRight(fmt.Sprintf("%d.", i+1), maxDigits+1),
|
||||
names[i],
|
||||
),
|
||||
maxNameLength+maxDigits+3,
|
||||
append(definition.style, Bullet(fmt.Sprintf("%d.", i+1)))...,
|
||||
),
|
||||
)
|
||||
|
||||
renderTTYParagraph(w, p)
|
||||
}
|
||||
|
||||
e.typ = definitions
|
||||
e.definitions = defs
|
||||
renderTTYDefinitions(w, e)
|
||||
}
|
||||
|
||||
func ttyCellTexts(rows []TableRow) [][]string {
|
||||
|
||||
141
teletype_test.go
141
teletype_test.go
@ -724,6 +724,65 @@ foo bar
|
||||
baz
|
||||
qux
|
||||
quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("varying length bullets", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Wrap(
|
||||
textfmt.List(
|
||||
textfmt.Item(textfmt.Text("foo bar baz"), textfmt.Bullet("=>")),
|
||||
textfmt.Item(textfmt.Text("qux"), textfmt.Bullet("==>")),
|
||||
textfmt.Item(textfmt.Text("quux"), textfmt.Bullet("=>")),
|
||||
),
|
||||
10,
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
=> foo
|
||||
bar
|
||||
baz
|
||||
==> qux
|
||||
=> quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("escape custom bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Wrap(
|
||||
textfmt.List(
|
||||
textfmt.Item(textfmt.Text("foo bar baz"), textfmt.Bullet("\x00>")),
|
||||
textfmt.Item(textfmt.Text("qux"), textfmt.Bullet("=>")),
|
||||
textfmt.Item(textfmt.Text("quux"), textfmt.Bullet("=>")),
|
||||
),
|
||||
10,
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
·> foo bar
|
||||
baz
|
||||
=> qux
|
||||
=> quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
@ -1193,6 +1252,88 @@ one: foo bar
|
||||
baz
|
||||
two: qux
|
||||
three: quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("varying length bullets", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Wrap(
|
||||
textfmt.DefinitionList(
|
||||
textfmt.Definition(
|
||||
textfmt.Text("one"),
|
||||
textfmt.Text("foo bar baz"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("two"),
|
||||
textfmt.Text("qux"),
|
||||
textfmt.Bullet("==>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("three"),
|
||||
textfmt.Text("quux"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
),
|
||||
18,
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
=> one: foo bar
|
||||
baz
|
||||
==> two: qux
|
||||
=> three: quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("escape custom bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Wrap(
|
||||
textfmt.DefinitionList(
|
||||
textfmt.Definition(
|
||||
textfmt.Text("one"),
|
||||
textfmt.Text("foo bar baz"),
|
||||
textfmt.Bullet("\x00>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("two"),
|
||||
textfmt.Text("qux"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("three"),
|
||||
textfmt.Text("quux"),
|
||||
textfmt.Bullet("=>"),
|
||||
),
|
||||
),
|
||||
18,
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Teletype(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
·> one: foo bar
|
||||
baz
|
||||
=> two: qux
|
||||
=> three: quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
|
||||
8
text.go
8
text.go
@ -60,11 +60,19 @@ func itemToParagraph(list Entry, itemText Txt, prefix ...string) Entry {
|
||||
|
||||
var prefixLength int
|
||||
for _, p := range prefix {
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixLength += len([]rune(p)) + 1
|
||||
}
|
||||
|
||||
var prefixText []Txt
|
||||
for _, p := range prefix {
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixText = append(prefixText, Text(p))
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user