wip
This commit is contained in:
commit
dda5dc6884
9
Makefile
Normal file
9
Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
SOURCES = $(shell find . -name "*.go")
|
||||
|
||||
default: build
|
||||
|
||||
build: $(SOURCES)
|
||||
go build
|
||||
|
||||
fmt: $(SOURCES)
|
||||
go fmt
|
||||
3
go.mod
Normal file
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module code.squareroundforest.org/arpio/textfmt
|
||||
|
||||
go 1.25.0
|
||||
224
lib.go
Normal file
224
lib.go
Normal file
@ -0,0 +1,224 @@
|
||||
package textfmt
|
||||
|
||||
import "io"
|
||||
|
||||
const (
|
||||
invalid = iota
|
||||
title
|
||||
paragraph
|
||||
list
|
||||
numberedList
|
||||
definitions
|
||||
numberedDefinitions
|
||||
table
|
||||
code
|
||||
syntax
|
||||
)
|
||||
|
||||
type Txt struct {
|
||||
text string
|
||||
link string
|
||||
bold, italic bool
|
||||
cat []Txt
|
||||
}
|
||||
|
||||
type ListItem struct {
|
||||
text Txt
|
||||
}
|
||||
|
||||
type DefinitionItem struct {
|
||||
name, value Txt
|
||||
}
|
||||
|
||||
type TableCell struct {
|
||||
text Txt
|
||||
}
|
||||
|
||||
type TableRow struct {
|
||||
cells []TableCell
|
||||
header bool
|
||||
}
|
||||
|
||||
type SyntaxItem struct {
|
||||
symbol string
|
||||
multiple bool
|
||||
required bool
|
||||
optional bool
|
||||
sequence []SyntaxItem
|
||||
choice []SyntaxItem
|
||||
topLevel bool
|
||||
delimited bool
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
typ int
|
||||
text Txt
|
||||
titleLevel int
|
||||
items []ListItem
|
||||
definitions []DefinitionItem
|
||||
rows []TableRow
|
||||
syntax SyntaxItem
|
||||
indentFirst int
|
||||
indentRest int
|
||||
wrapWidth int
|
||||
}
|
||||
|
||||
type Document struct {
|
||||
entries []Entry
|
||||
}
|
||||
|
||||
func Text(text string) Txt {
|
||||
return Txt{text: text}
|
||||
}
|
||||
|
||||
func Link(title, uri string) Txt {
|
||||
return Txt{text: title, link: uri}
|
||||
}
|
||||
|
||||
func Bold(t Txt) Txt {
|
||||
t.bold = true
|
||||
return t
|
||||
}
|
||||
|
||||
func Italic(t Txt) Txt {
|
||||
t.italic = true
|
||||
return t
|
||||
}
|
||||
|
||||
func Cat(t ...Txt) Txt {
|
||||
return Txt{cat: t}
|
||||
}
|
||||
|
||||
func Title(level int, text string) Entry {
|
||||
return Entry{
|
||||
typ: title,
|
||||
titleLevel: level,
|
||||
text: Text(text),
|
||||
}
|
||||
}
|
||||
|
||||
func Paragraph(t Txt) Entry {
|
||||
return Entry{typ: paragraph, text: t}
|
||||
}
|
||||
|
||||
func Item(text Txt) ListItem {
|
||||
return ListItem{text: text}
|
||||
}
|
||||
|
||||
func List(items ...ListItem) Entry {
|
||||
return Entry{typ: list, items: items}
|
||||
}
|
||||
|
||||
func NumberedList(items ...ListItem) Entry {
|
||||
return Entry{typ: numberedList, items: items}
|
||||
}
|
||||
|
||||
func Definition(name, value Txt) DefinitionItem {
|
||||
return DefinitionItem{name: name, value: value}
|
||||
}
|
||||
|
||||
func DefinitionList(items ...DefinitionItem) Entry {
|
||||
return Entry{typ: definitions, definitions: items}
|
||||
}
|
||||
|
||||
func NumberedDefinitionList(items ...DefinitionItem) Entry {
|
||||
return Entry{typ: numberedDefinitions, definitions: items}
|
||||
}
|
||||
|
||||
func Cell(text Txt) TableCell {
|
||||
return TableCell{text: text}
|
||||
}
|
||||
|
||||
func Header(cells ...TableCell) TableRow {
|
||||
return TableRow{cells: cells, header: true}
|
||||
}
|
||||
|
||||
func Row(cells ...TableCell) TableRow {
|
||||
return TableRow{cells: cells}
|
||||
}
|
||||
|
||||
func Table(rows ...TableRow) Entry {
|
||||
return Entry{typ: table, rows: rows}
|
||||
}
|
||||
|
||||
func CodeBlock(codeBlock string) Entry {
|
||||
return Entry{typ: code, text: Text(codeBlock)}
|
||||
}
|
||||
|
||||
func Symbol(text string) SyntaxItem {
|
||||
return SyntaxItem{symbol: text}
|
||||
}
|
||||
|
||||
func OneOrMore(item SyntaxItem) SyntaxItem {
|
||||
item.required = true
|
||||
item.optional = false
|
||||
item.multiple = true
|
||||
return item
|
||||
}
|
||||
|
||||
func ZeroOrMore(item SyntaxItem) SyntaxItem {
|
||||
item.required = false
|
||||
item.optional = true
|
||||
item.multiple = true
|
||||
return item
|
||||
}
|
||||
|
||||
func Required(item SyntaxItem) SyntaxItem {
|
||||
item.required = true
|
||||
item.optional = false
|
||||
return item
|
||||
}
|
||||
|
||||
func Optional(item SyntaxItem) SyntaxItem {
|
||||
item.required = false
|
||||
item.optional = true
|
||||
return item
|
||||
}
|
||||
|
||||
func Sequence(items ...SyntaxItem) SyntaxItem {
|
||||
return SyntaxItem{sequence: items}
|
||||
}
|
||||
|
||||
func Choice(items ...SyntaxItem) SyntaxItem {
|
||||
return SyntaxItem{choice: items}
|
||||
}
|
||||
|
||||
func Syntax(items ...SyntaxItem) Entry {
|
||||
return Entry{typ: syntax, syntax: Sequence(items...)}
|
||||
}
|
||||
|
||||
func Indent(e Entry, first, rest int) Entry {
|
||||
e.indentFirst, e.indentRest = first, rest
|
||||
return e
|
||||
}
|
||||
|
||||
func Wrap(e Entry, width int) Entry {
|
||||
e.wrapWidth = width
|
||||
return e
|
||||
}
|
||||
|
||||
func Doc(e ...Entry) Document {
|
||||
return Document{entries: e}
|
||||
}
|
||||
|
||||
func Teletype(out io.Writer, d Document) error {
|
||||
return renderTeletype(out, d)
|
||||
}
|
||||
|
||||
func Roff(io.Writer, Document) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Markdown(io.Writer, Document) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func HTML(io.Writer, Document) error {
|
||||
// with the won HTML library
|
||||
return nil
|
||||
}
|
||||
|
||||
func HTMLFragment(io.Writer, Document) error {
|
||||
// with the won HTML library
|
||||
return nil
|
||||
}
|
||||
78
table.go
Normal file
78
table.go
Normal file
@ -0,0 +1,78 @@
|
||||
package textfmt
|
||||
|
||||
import "strings"
|
||||
|
||||
func normalizeTable(rows []TableRow) []TableRow {
|
||||
var maxColumns int
|
||||
for _, row := range rows {
|
||||
if len(row.cells) > maxColumns {
|
||||
maxColumns = len(row.cells)
|
||||
}
|
||||
}
|
||||
|
||||
var normalized []TableRow
|
||||
for _, row := range rows {
|
||||
row.cells = append(
|
||||
row.cells,
|
||||
make([]TableCell, maxColumns-len(row.cells))...,
|
||||
)
|
||||
|
||||
normalized = append(normalized, row)
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
func columnWeights(cells [][]string) []int {
|
||||
if len(cells) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := make([]int, len(cells[0]))
|
||||
for _, row := range cells {
|
||||
for i, cell := range row {
|
||||
w[i] += len([]rune(cell))
|
||||
}
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func targetColumnWidths(tableWidth int, weights []int) []int {
|
||||
var weightSum int
|
||||
for _, w := range weights {
|
||||
weightSum += w
|
||||
}
|
||||
|
||||
widths := make([]int, len(weights))
|
||||
for i := range weights {
|
||||
widths[i] = (weights[i] * tableWidth) / weightSum
|
||||
}
|
||||
|
||||
return widths
|
||||
}
|
||||
|
||||
func columnWidths(rows [][]string) []int {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(rows[0]) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
widths := make([]int, len(rows[0]))
|
||||
for i := range rows {
|
||||
for j := range rows[i] {
|
||||
l := strings.Split(rows[i][j], "\n")
|
||||
for k := range l {
|
||||
lk := len([]rune(l[k]))
|
||||
if lk > widths[j] {
|
||||
widths[j] = lk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return widths
|
||||
}
|
||||
440
teletype.go
Normal file
440
teletype.go
Normal file
@ -0,0 +1,440 @@
|
||||
package textfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func escapeTeletype(s string) string {
|
||||
r := []rune(s)
|
||||
for i := range r {
|
||||
if r[i] >= 0x00 && r[i] <= 0x1f && r[i] != '\n' && r[i] != '\t' {
|
||||
r[i] = 0xb7
|
||||
}
|
||||
|
||||
if r[i] >= 0x7f && r[i] <= 0x9f {
|
||||
r[i] = 0xb7
|
||||
}
|
||||
}
|
||||
|
||||
return string(r)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYText(w *writer, text Txt) {
|
||||
if len(text.cat) > 0 {
|
||||
for i, tc := range text.cat {
|
||||
if i > 0 {
|
||||
w.write(" ")
|
||||
}
|
||||
|
||||
renderTTYText(w, tc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
text.text = singleLine(text.text)
|
||||
text.text = escapeTeletype(text.text)
|
||||
if text.link != "" {
|
||||
if text.text != "" {
|
||||
w.write(text.text)
|
||||
w.write(" (")
|
||||
w.write(text.link)
|
||||
w.write(")")
|
||||
return
|
||||
}
|
||||
|
||||
w.write(text.link)
|
||||
return
|
||||
}
|
||||
|
||||
w.write(text.text)
|
||||
}
|
||||
|
||||
func ttyTextToString(text Txt) (string, error) {
|
||||
var b bytes.Buffer
|
||||
w := writer{w: &b}
|
||||
renderTTYText(&w, text)
|
||||
if w.err != nil {
|
||||
return "", w.err
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func definitionNames(d []DefinitionItem) ([]string, error) {
|
||||
var n []string
|
||||
for _, di := range d {
|
||||
name, err := ttyTextToString(di.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n = append(n, name)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func renderTTYTitle(w *writer, e Entry) {
|
||||
w.write(timesn(" ", e.indentFirst))
|
||||
renderTTYText(w, e.text)
|
||||
}
|
||||
|
||||
func renderTTYParagraph(w *writer, e Entry) {
|
||||
var txt string
|
||||
txt, w.err = ttyTextToString(e.text)
|
||||
if e.wrapWidth > 0 {
|
||||
txt = wrap(txt, e.wrapWidth, e.indentFirst, e.indentRest)
|
||||
}
|
||||
|
||||
writeLines(w, txt, e.indentFirst, e.indentRest)
|
||||
}
|
||||
|
||||
func renderTTYList(w *writer, e Entry) {
|
||||
const bullet = "- "
|
||||
indentFirst := e.indentFirst
|
||||
indentRest := e.indentRest + len(bullet)
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
var txt string
|
||||
txt, w.err = ttyTextToString(item.text)
|
||||
if e.wrapWidth > 0 {
|
||||
txt = wrap(txt, e.wrapWidth-len(bullet), indentFirst, indentRest)
|
||||
}
|
||||
|
||||
w.write(timesn(" ", indentFirst))
|
||||
w.write(bullet)
|
||||
writeLines(w, txt, 0, indentRest)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYNumberedList(w *writer, e Entry) {
|
||||
maxDigits := maxDigits(len(e.items))
|
||||
indentFirst := e.indentFirst
|
||||
indentRest := e.indentRest + maxDigits + 2
|
||||
for i, item := range e.items {
|
||||
if i > 0 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
var txt string
|
||||
txt, w.err = ttyTextToString(item.text)
|
||||
if e.wrapWidth > 0 {
|
||||
txt = wrap(txt, e.wrapWidth-maxDigits-2, indentFirst, indentRest)
|
||||
}
|
||||
|
||||
w.write(timesn(" ", indentFirst))
|
||||
w.write(fmt.Sprintf("%d. ", i))
|
||||
writeLines(w, txt, 0, indentRest)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYDefinitions(w *writer, e Entry) {
|
||||
names, err := definitionNames(e.definitions)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
return
|
||||
}
|
||||
|
||||
maxLength := maxLength(names)
|
||||
indentFirst := e.indentFirst
|
||||
indentRest := e.indentRest + maxLength + 4
|
||||
for i, def := range e.definitions {
|
||||
if i > 0 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
var value string
|
||||
value, w.err = ttyTextToString(def.value)
|
||||
if e.wrapWidth > 0 {
|
||||
value = wrap(value, e.wrapWidth-maxLength-4, indentFirst, indentRest)
|
||||
}
|
||||
|
||||
name := names[i]
|
||||
w.write("- ")
|
||||
w.write(name)
|
||||
w.write(": ")
|
||||
writeLines(w, value, 0, indentRest)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYNumberedDefinitions(w *writer, e Entry) {
|
||||
maxDigits := maxDigits(len(e.definitions))
|
||||
names, err := definitionNames(e.definitions)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
return
|
||||
}
|
||||
|
||||
maxLength := maxLength(names)
|
||||
indentFirst := e.indentFirst
|
||||
indentRest := e.indentRest + maxLength + maxDigits + 4
|
||||
for i, def := range e.definitions {
|
||||
if i > 0 {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
var value string
|
||||
value, w.err = ttyTextToString(def.value)
|
||||
if e.wrapWidth > 0 {
|
||||
value = wrap(value, e.wrapWidth-maxLength-4, indentFirst, indentRest)
|
||||
}
|
||||
|
||||
name := names[i]
|
||||
w.write(fmt.Sprintf("%d. ", i))
|
||||
w.write(name)
|
||||
w.write(": ")
|
||||
writeLines(w, value, 0, indentRest)
|
||||
}
|
||||
}
|
||||
|
||||
func ttyCellTexts(rows []TableRow) ([][]string, error) {
|
||||
var cellTexts [][]string
|
||||
for _, row := range rows {
|
||||
var c []string
|
||||
for _, cell := range row.cells {
|
||||
txt, err := ttyTextToString(cell.text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c = append(c, txt)
|
||||
}
|
||||
|
||||
cellTexts = append(cellTexts, c)
|
||||
}
|
||||
|
||||
return cellTexts, nil
|
||||
}
|
||||
|
||||
func renderTTYTable(w *writer, e Entry) {
|
||||
if len(e.rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
e.rows = normalizeTable(e.rows)
|
||||
if len(e.rows[0].cells) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cellTexts, err := ttyCellTexts(e.rows)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
}
|
||||
|
||||
totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3
|
||||
if e.wrapWidth > 0 {
|
||||
allocatedWidth := e.wrapWidth - e.indentFirst - 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(sep, totalWidth))
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
for j := range cellTexts[i] {
|
||||
if j > 0 {
|
||||
w.write(" | ")
|
||||
}
|
||||
|
||||
w.write(padRight(cellTexts[i][j], columnWidths[j]))
|
||||
}
|
||||
}
|
||||
|
||||
if hasHeader && len(cellTexts) == 1 {
|
||||
w.write(timesn("=", totalWidth))
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYCode(w *writer, e Entry) {
|
||||
var txt string
|
||||
txt, w.err = ttyTextToString(e.text)
|
||||
writeLines(w, txt, e.indentFirst, e.indentFirst)
|
||||
}
|
||||
|
||||
func renderTTYMultiple(w *writer, s SyntaxItem) {
|
||||
s.topLevel = false
|
||||
s.multiple = false
|
||||
renderTTYSyntaxItem(w, s)
|
||||
w.write("...")
|
||||
}
|
||||
|
||||
func renderTTYRequired(w *writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.required = false
|
||||
w.write("<")
|
||||
renderTTYSyntaxItem(w, s)
|
||||
w.write(">")
|
||||
}
|
||||
|
||||
func renderTTYOptional(w *writer, s SyntaxItem) {
|
||||
s.delimited = true
|
||||
s.topLevel = false
|
||||
s.optional = false
|
||||
w.write("[")
|
||||
renderTTYSyntaxItem(w, s)
|
||||
w.write("]")
|
||||
}
|
||||
|
||||
func renderTTYSequence(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
|
||||
renderTTYSyntaxItem(w, item)
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYChoice(w *writer, s SyntaxItem) {
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write("(")
|
||||
}
|
||||
|
||||
for i, item := range s.sequence {
|
||||
if i > 0 {
|
||||
separator := "|"
|
||||
if s.topLevel {
|
||||
separator = "\n"
|
||||
}
|
||||
|
||||
w.write(separator)
|
||||
}
|
||||
|
||||
item.delimited = false
|
||||
renderTTYSyntaxItem(w, item)
|
||||
}
|
||||
|
||||
if !s.delimited && !s.topLevel {
|
||||
w.write(")")
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYSymbol(w *writer, s SyntaxItem) {
|
||||
w.write(escapeTeletype(s.symbol))
|
||||
}
|
||||
|
||||
func renderTTYSyntaxItem(w *writer, s SyntaxItem) {
|
||||
switch {
|
||||
|
||||
// foo...
|
||||
case s.multiple:
|
||||
renderTTYMultiple(w, s)
|
||||
|
||||
// <foo>
|
||||
case s.required:
|
||||
renderTTYRequired(w, s)
|
||||
|
||||
// [foo]
|
||||
case s.optional:
|
||||
renderTTYOptional(w, s)
|
||||
|
||||
// foo bar baz or (foo bar baz)
|
||||
case len(s.sequence) > 0:
|
||||
renderTTYSequence(w, s)
|
||||
|
||||
// foo|bar|baz or (foo|bar|baz)
|
||||
case len(s.choice) > 0:
|
||||
renderTTYChoice(w, s)
|
||||
|
||||
// foo
|
||||
default:
|
||||
renderTTYSymbol(w, s)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTTYSyntax(w *writer, e Entry) {
|
||||
s := e.syntax
|
||||
s.topLevel = true
|
||||
renderTTYSyntaxItem(w, s)
|
||||
}
|
||||
|
||||
func renderTeletype(out io.Writer, d Document) error {
|
||||
w := writer{w: out}
|
||||
for i, e := range d.entries {
|
||||
if i > 0 {
|
||||
w.write("\n\n")
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case invalid:
|
||||
return errors.New("invalid entry")
|
||||
case title:
|
||||
renderTTYTitle(&w, e)
|
||||
case paragraph:
|
||||
renderTTYParagraph(&w, e)
|
||||
case list:
|
||||
renderTTYList(&w, e)
|
||||
case numberedList:
|
||||
renderTTYNumberedList(&w, e)
|
||||
case definitions:
|
||||
renderTTYDefinitions(&w, e)
|
||||
case numberedDefinitions:
|
||||
renderTTYNumberedDefinitions(&w, e)
|
||||
case table:
|
||||
renderTTYTable(&w, e)
|
||||
case code:
|
||||
renderTTYCode(&w, e)
|
||||
case syntax:
|
||||
renderTTYSyntax(&w, e)
|
||||
}
|
||||
}
|
||||
|
||||
w.write("\n")
|
||||
return w.err
|
||||
}
|
||||
38
text.go
Normal file
38
text.go
Normal file
@ -0,0 +1,38 @@
|
||||
package textfmt
|
||||
|
||||
import "strings"
|
||||
|
||||
func timesn(s string, n int) string {
|
||||
ss := make([]string, n+1)
|
||||
return strings.Join(ss, s)
|
||||
}
|
||||
|
||||
func maxDigits(n int) int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
var d int
|
||||
for n > 0 {
|
||||
d++
|
||||
n /= 10
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func maxLength(names []string) int {
|
||||
var m int
|
||||
for _, n := range names {
|
||||
if len([]rune(n)) > m {
|
||||
m = len([]rune(n))
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func padRight(s string, n int) string {
|
||||
n -= len([]rune(s))
|
||||
return s + timesn(" ", n)
|
||||
}
|
||||
75
wrap.go
Normal file
75
wrap.go
Normal file
@ -0,0 +1,75 @@
|
||||
package textfmt
|
||||
|
||||
import "strings"
|
||||
|
||||
func getWords(text string) []string {
|
||||
var words []string
|
||||
raw := strings.Split(text, " ")
|
||||
for _, r := range raw {
|
||||
if r == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
words = append(words, r)
|
||||
}
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
func lineLength(words []string) int {
|
||||
if len(words) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var l int
|
||||
for _, w := range words {
|
||||
r := []rune(w)
|
||||
l += len(r)
|
||||
}
|
||||
|
||||
return l + len(words) - 1
|
||||
}
|
||||
|
||||
func singleLine(text string) string {
|
||||
var l []string
|
||||
p := strings.Split(text, "\n")
|
||||
for _, part := range p {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
l = append(l, part)
|
||||
}
|
||||
|
||||
return strings.Join(l, " ")
|
||||
}
|
||||
|
||||
func wrap(text string, width, firstIndent, restIndent int) string {
|
||||
var (
|
||||
lines []string
|
||||
currentLine []string
|
||||
currentLen int
|
||||
)
|
||||
|
||||
words := getWords(text)
|
||||
for _, w := range words {
|
||||
maxw := width - restIndent
|
||||
if len(lines) == 0 {
|
||||
maxw = width - firstIndent
|
||||
}
|
||||
|
||||
currentLine = append(currentLine, w)
|
||||
if lineLength(currentLine) > maxw {
|
||||
currentLine = currentLine[:len(currentLine)-1]
|
||||
lines = append(lines, strings.Join(currentLine, " "))
|
||||
currentLine = []string{w}
|
||||
}
|
||||
}
|
||||
|
||||
if len(currentLine) > 0 {
|
||||
lines = append(lines, strings.Join(currentLine, " "))
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user