| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- // Package colwriter provides a write filter that formats
- // input lines in multiple columns.
- //
- // The package is a straightforward translation from
- // /src/cmd/draw/mc.c in Plan 9 from User Space.
- package colwriter
- import (
- "bytes"
- "io"
- "unicode/utf8"
- )
- const (
- tab = 4
- )
- const (
- // Print each input line ending in a colon ':' separately.
- BreakOnColon uint = 1 << iota
- )
- // A Writer is a filter that arranges input lines in as many columns as will
- // fit in its width. Tab '\t' chars in the input are translated to sequences
- // of spaces ending at multiples of 4 positions.
- //
- // If BreakOnColon is set, each input line ending in a colon ':' is written
- // separately.
- //
- // The Writer assumes that all Unicode code points have the same width; this
- // may not be true in some fonts.
- type Writer struct {
- w io.Writer
- buf []byte
- width int
- flag uint
- }
- // NewWriter allocates and initializes a new Writer writing to w.
- // Parameter width controls the total number of characters on each line
- // across all columns.
- func NewWriter(w io.Writer, width int, flag uint) *Writer {
- return &Writer{
- w: w,
- width: width,
- flag: flag,
- }
- }
- // Write writes p to the writer w. The only errors returned are ones
- // encountered while writing to the underlying output stream.
- func (w *Writer) Write(p []byte) (n int, err error) {
- var linelen int
- var lastWasColon bool
- for i, c := range p {
- w.buf = append(w.buf, c)
- linelen++
- if c == '\t' {
- w.buf[len(w.buf)-1] = ' '
- for linelen%tab != 0 {
- w.buf = append(w.buf, ' ')
- linelen++
- }
- }
- if w.flag&BreakOnColon != 0 && c == ':' {
- lastWasColon = true
- } else if lastWasColon {
- if c == '\n' {
- pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'})
- if pos < 0 {
- pos = 0
- }
- line := w.buf[pos:]
- w.buf = w.buf[:pos]
- if err = w.columnate(); err != nil {
- if len(line) < i {
- return i - len(line), err
- }
- return 0, err
- }
- if n, err := w.w.Write(line); err != nil {
- if r := len(line) - n; r < i {
- return i - r, err
- }
- return 0, err
- }
- }
- lastWasColon = false
- }
- if c == '\n' {
- linelen = 0
- }
- }
- return len(p), nil
- }
- // Flush should be called after the last call to Write to ensure that any data
- // buffered in the Writer is written to output.
- func (w *Writer) Flush() error {
- return w.columnate()
- }
- func (w *Writer) columnate() error {
- words := bytes.Split(w.buf, []byte{'\n'})
- w.buf = nil
- if len(words[len(words)-1]) == 0 {
- words = words[:len(words)-1]
- }
- maxwidth := 0
- for _, wd := range words {
- if n := utf8.RuneCount(wd); n > maxwidth {
- maxwidth = n
- }
- }
- maxwidth++ // space char
- wordsPerLine := w.width / maxwidth
- if wordsPerLine <= 0 {
- wordsPerLine = 1
- }
- nlines := (len(words) + wordsPerLine - 1) / wordsPerLine
- for i := 0; i < nlines; i++ {
- col := 0
- endcol := 0
- for j := i; j < len(words); j += nlines {
- endcol += maxwidth
- _, err := w.w.Write(words[j])
- if err != nil {
- return err
- }
- col += utf8.RuneCount(words[j])
- if j+nlines < len(words) {
- for col < endcol {
- _, err := w.w.Write([]byte{' '})
- if err != nil {
- return err
- }
- col++
- }
- }
- }
- _, err := w.w.Write([]byte{'\n'})
- if err != nil {
- return err
- }
- }
- return nil
- }
|