mirror of
https://github.com/wader/fq.git
synced 2024-12-26 15:02:28 +03:00
170 lines
2.9 KiB
Go
170 lines
2.9 KiB
Go
package columnwriter
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type Column struct {
|
|
Width int
|
|
Lines []string
|
|
Buf bytes.Buffer
|
|
Wrap bool
|
|
}
|
|
|
|
func divideString(s string, l int) []string {
|
|
var ss []string
|
|
parts := len(s) / l
|
|
for i := 0; i < parts; i++ {
|
|
ss = append(ss, s[i*l:(i+1)*l])
|
|
}
|
|
if len(s)%l != 0 {
|
|
ss = append(ss, s[parts*l:])
|
|
}
|
|
|
|
return ss
|
|
}
|
|
|
|
func (c *Column) Write(p []byte) (int, error) {
|
|
bb := &c.Buf
|
|
|
|
bb.Write(p)
|
|
|
|
b := bb.Bytes()
|
|
pos := 0
|
|
|
|
for {
|
|
i := indexByteSet(b[pos:], []byte{'\n'})
|
|
if i < 0 {
|
|
break
|
|
}
|
|
|
|
line := string([]rune(string(b[pos : pos+i])))
|
|
if c.Wrap && len(line) > c.Width {
|
|
c.Lines = append(c.Lines, divideString(line, c.Width)...)
|
|
} else {
|
|
c.Lines = append(c.Lines, line)
|
|
}
|
|
|
|
pos += i + 1
|
|
}
|
|
bb.Reset()
|
|
bb.Write(b[pos:])
|
|
|
|
return len(p), nil
|
|
}
|
|
|
|
func (c *Column) Flush() {
|
|
if c.Buf.Len() > 0 {
|
|
_, _ = c.Write([]byte{'\n'})
|
|
}
|
|
}
|
|
|
|
// Writer maintins multiple column io.Writer:s. On Flush() row align them.
|
|
type Writer struct {
|
|
Columns []*Column
|
|
DisplayLenFn func(s string) int
|
|
DisplayTruncateFn func(s string, l int) string
|
|
|
|
w io.Writer
|
|
}
|
|
|
|
func indexByteSet(s []byte, cs []byte) int {
|
|
ri := -1
|
|
|
|
for _, c := range cs {
|
|
i := bytes.IndexByte(s, c)
|
|
if i != -1 && (ri == -1 || i < ri) {
|
|
ri = i
|
|
}
|
|
}
|
|
|
|
return ri
|
|
}
|
|
|
|
func New(w io.Writer, widths []int) *Writer {
|
|
var columns []*Column
|
|
for _, w := range widths {
|
|
columns = append(columns, &Column{Width: w})
|
|
}
|
|
|
|
return &Writer{
|
|
Columns: columns,
|
|
w: w,
|
|
}
|
|
}
|
|
|
|
func (w *Writer) Flush() error {
|
|
const whitespace = " "
|
|
|
|
// TODO: both fn assume fixed width runes
|
|
lenFn := w.DisplayLenFn
|
|
if lenFn == nil {
|
|
lenFn = func(s string) int { return utf8.RuneCountInString(s) }
|
|
}
|
|
truncateFn := w.DisplayTruncateFn
|
|
if truncateFn == nil {
|
|
truncateFn = func(s string, l int) string {
|
|
return string(([]rune(s))[0:l])
|
|
}
|
|
}
|
|
|
|
for _, c := range w.Columns {
|
|
c.Flush()
|
|
}
|
|
|
|
maxLines := 0
|
|
for _, c := range w.Columns {
|
|
lenLines := len(c.Lines)
|
|
if lenLines > maxLines {
|
|
maxLines = len(c.Lines)
|
|
}
|
|
}
|
|
|
|
for i := 0; i < maxLines; i++ {
|
|
for ci, c := range w.Columns {
|
|
var s string
|
|
if i < len(c.Lines) {
|
|
s = c.Lines[i]
|
|
if c.Width != -1 && lenFn(s) > c.Width {
|
|
s = truncateFn(s, c.Width)
|
|
}
|
|
}
|
|
|
|
if _, err := w.w.Write([]byte(s)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if ci < len(w.Columns)-1 && c.Width != -1 {
|
|
l := lenFn(s)
|
|
if l < c.Width {
|
|
n := c.Width - l
|
|
for n > 0 {
|
|
r := n
|
|
if r > len(whitespace) {
|
|
r = len(whitespace)
|
|
}
|
|
n -= r
|
|
|
|
if _, err := w.w.Write([]byte(whitespace[0:r])); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
if _, err := w.w.Write([]byte{'\n'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, c := range w.Columns {
|
|
c.Lines = nil
|
|
c.Buf.Reset()
|
|
}
|
|
|
|
return nil
|
|
}
|