File: column.go

package info (click to toggle)
golang-text 0.0~git20130502-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 92 kB
  • ctags: 44
  • sloc: makefile: 8
file content (147 lines) | stat: -rw-r--r-- 3,299 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 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
}