support custom bullets in lists and definition lists
This commit is contained in:
parent
78d3e0e284
commit
cff44cc943
29
lib.go
29
lib.go
@ -26,12 +26,19 @@ type Txt struct {
|
||||
cat []Txt
|
||||
}
|
||||
|
||||
type ItemStyle struct {
|
||||
bullet string
|
||||
noBullet bool
|
||||
}
|
||||
|
||||
type ListItem struct {
|
||||
text Txt
|
||||
text Txt
|
||||
style []ItemStyle
|
||||
}
|
||||
|
||||
type DefinitionItem struct {
|
||||
name, value Txt
|
||||
style []ItemStyle
|
||||
}
|
||||
|
||||
type TableCell struct {
|
||||
@ -168,8 +175,18 @@ func Paragraph(t Txt) Entry {
|
||||
return Entry{typ: paragraph, text: t}
|
||||
}
|
||||
|
||||
func Item(text Txt) ListItem {
|
||||
return ListItem{text: text}
|
||||
func Bullet(b string) ItemStyle {
|
||||
return ItemStyle{bullet: b}
|
||||
}
|
||||
|
||||
func NoBullet() ItemStyle {
|
||||
return ItemStyle{noBullet: true}
|
||||
}
|
||||
|
||||
// Item creates a list item for bulleted or numbered lists. Item style may behave different depending ont the
|
||||
// output format.
|
||||
func Item(text Txt, style ...ItemStyle) ListItem {
|
||||
return ListItem{text: text, style: style}
|
||||
}
|
||||
|
||||
func List(items ...ListItem) Entry {
|
||||
@ -180,8 +197,10 @@ func NumberedList(items ...ListItem) Entry {
|
||||
return Entry{typ: numberedList, items: items}
|
||||
}
|
||||
|
||||
func Definition(name, value Txt) DefinitionItem {
|
||||
return DefinitionItem{name: name, value: value}
|
||||
// Definition creates a definition list item for bulleted or numbered definition lists. Item style may behave
|
||||
// different depending ont the output format.
|
||||
func Definition(name, value Txt, style ...ItemStyle) DefinitionItem {
|
||||
return DefinitionItem{name: name, value: value, style: style}
|
||||
}
|
||||
|
||||
func DefinitionList(items ...DefinitionItem) Entry {
|
||||
|
||||
37
runoff.go
37
runoff.go
@ -134,8 +134,24 @@ func renderRoffList(w io.Writer, e Entry) {
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
write(w, ".in ", e.indent+2, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, "\\(bu ")
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
write(w, ".in ", e.indent+bulletIndent, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, bullet)
|
||||
renderRoffText(w, item.text)
|
||||
}
|
||||
}
|
||||
@ -161,8 +177,21 @@ func renderRoffDefinitions(w io.Writer, e Entry) {
|
||||
write(w, "\n.br\n")
|
||||
}
|
||||
|
||||
write(w, ".in ", e.indent+maxNameLength+4, "\n.ti ", e.indent+e.indentFirst, "\n")
|
||||
write(w, "\\(bu ")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
renderRoffText(w, definition.value)
|
||||
|
||||
234
runoff_test.go
234
runoff_test.go
@ -904,6 +904,105 @@ This is a paragraph.
|
||||
t.Fatal(b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("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 2
|
||||
.ti 0
|
||||
* foo bar baz
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
* qux
|
||||
.br
|
||||
.in 2
|
||||
.ti 0
|
||||
* quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom bullet longer", 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 {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.List(
|
||||
textfmt.Item(textfmt.Text("foo bar baz"), textfmt.NoBullet()),
|
||||
textfmt.Item(textfmt.Text("qux"), textfmt.NoBullet()),
|
||||
textfmt.Item(textfmt.Text("quux"), textfmt.NoBullet()),
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Runoff(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
.in 0
|
||||
.ti 0
|
||||
foo bar baz
|
||||
.br
|
||||
.in 0
|
||||
.ti 0
|
||||
qux
|
||||
.br
|
||||
.in 0
|
||||
.ti 0
|
||||
quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("numbered list", func(t *testing.T) {
|
||||
@ -1261,6 +1360,141 @@ This is a paragraph.
|
||||
.in 13
|
||||
.ti 0
|
||||
\(bu blue:\~\~looks like sky
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("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 9
|
||||
.ti 0
|
||||
* one:\~\~\~foo bar baz
|
||||
.br
|
||||
.in 9
|
||||
.ti 0
|
||||
* two:\~\~\~qux
|
||||
.br
|
||||
.in 9
|
||||
.ti 0
|
||||
* three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom bullet longer", 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 {
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.DefinitionList(
|
||||
textfmt.Definition(
|
||||
textfmt.Text("one"),
|
||||
textfmt.Text("foo bar baz"),
|
||||
textfmt.NoBullet(),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("two"),
|
||||
textfmt.Text("qux"),
|
||||
textfmt.NoBullet(),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("three"),
|
||||
textfmt.Text("quux"),
|
||||
textfmt.NoBullet(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := textfmt.Runoff(&b, doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const expect = `
|
||||
.in 7
|
||||
.ti 0
|
||||
one:\~\~\~foo bar baz
|
||||
.br
|
||||
.in 7
|
||||
.ti 0
|
||||
two:\~\~\~qux
|
||||
.br
|
||||
.in 7
|
||||
.ti 0
|
||||
three:\~quux
|
||||
`
|
||||
|
||||
if "\n"+b.String() != expect {
|
||||
|
||||
29
teletype.go
29
teletype.go
@ -83,7 +83,19 @@ func renderTTYList(w io.Writer, e Entry) {
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
p := itemToParagraph(e, item.text, "-")
|
||||
var p Entry
|
||||
itemStyle := mergeItemStyles(item.style)
|
||||
if itemStyle.noBullet {
|
||||
p = itemToParagraph(e, item.text)
|
||||
} else {
|
||||
bullet := "-"
|
||||
if itemStyle.bullet != "" {
|
||||
bullet = itemStyle.bullet
|
||||
}
|
||||
|
||||
p = itemToParagraph(e, item.text, bullet)
|
||||
}
|
||||
|
||||
renderTTYParagraph(w, p)
|
||||
}
|
||||
}
|
||||
@ -108,10 +120,23 @@ func renderTTYDefinitions(w io.Writer, e Entry) {
|
||||
write(w, "\n")
|
||||
}
|
||||
|
||||
var bullet string
|
||||
padLength := 1
|
||||
itemStyle := mergeItemStyles(definition.style)
|
||||
if !itemStyle.noBullet {
|
||||
if itemStyle.bullet == "" {
|
||||
bullet = "- "
|
||||
padLength = 3
|
||||
} else {
|
||||
bullet = itemStyle.bullet + " "
|
||||
padLength = len([]rune(bullet)) + 1
|
||||
}
|
||||
}
|
||||
|
||||
p := itemToParagraph(
|
||||
e,
|
||||
definition.value,
|
||||
padRight(fmt.Sprintf("- %s:", names[i]), maxNameLength+3),
|
||||
padRight(fmt.Sprintf("%s%s:", bullet, names[i]), maxNameLength+padLength),
|
||||
)
|
||||
|
||||
renderTTYParagraph(w, p)
|
||||
|
||||
210
teletype_test.go
210
teletype_test.go
@ -643,6 +643,93 @@ third item
|
||||
t.Fatal(b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom bullet", 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("custom bullet longer", 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("no bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Wrap(
|
||||
textfmt.List(
|
||||
textfmt.Item(textfmt.Text("foo bar baz"), textfmt.NoBullet()),
|
||||
textfmt.Item(textfmt.Text("qux"), textfmt.NoBullet()),
|
||||
textfmt.Item(textfmt.Text("quux"), textfmt.NoBullet()),
|
||||
),
|
||||
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("numbered list", func(t *testing.T) {
|
||||
@ -989,6 +1076,129 @@ third item
|
||||
t.Fatal("\n" + b.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("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("*"),
|
||||
),
|
||||
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("custom bullet longer", 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("no bullet", func(t *testing.T) {
|
||||
doc := textfmt.Doc(
|
||||
textfmt.Wrap(
|
||||
textfmt.DefinitionList(
|
||||
textfmt.Definition(
|
||||
textfmt.Text("one"),
|
||||
textfmt.Text("foo bar baz"),
|
||||
textfmt.NoBullet(),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("two"),
|
||||
textfmt.Text("qux"),
|
||||
textfmt.NoBullet(),
|
||||
),
|
||||
textfmt.Definition(
|
||||
textfmt.Text("three"),
|
||||
textfmt.Text("quux"),
|
||||
textfmt.NoBullet(),
|
||||
),
|
||||
),
|
||||
15,
|
||||
),
|
||||
)
|
||||
|
||||
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("numbered definition list", func(t *testing.T) {
|
||||
|
||||
10
text.go
10
text.go
@ -73,3 +73,13 @@ func itemToParagraph(list Entry, itemText Txt, prefix ...string) Entry {
|
||||
p.text.cat = append(prefixText, itemText)
|
||||
return p
|
||||
}
|
||||
|
||||
func mergeItemStyles(s []ItemStyle) ItemStyle {
|
||||
var m ItemStyle
|
||||
for _, si := range s {
|
||||
m.bullet = si.bullet
|
||||
m.noBullet = m.noBullet && si.bullet == "" || si.noBullet
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user