summaryrefslogtreecommitdiff
path: root/src/pkg/tabwriter
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/tabwriter')
-rw-r--r--src/pkg/tabwriter/Makefile2
-rw-r--r--src/pkg/tabwriter/tabwriter.go122
-rw-r--r--src/pkg/tabwriter/tabwriter_test.go164
3 files changed, 160 insertions, 128 deletions
diff --git a/src/pkg/tabwriter/Makefile b/src/pkg/tabwriter/Makefile
index 5beb88f0f..bdc888784 100644
--- a/src/pkg/tabwriter/Makefile
+++ b/src/pkg/tabwriter/Makefile
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-include ../../Make.$(GOARCH)
+include ../../Make.inc
TARG=tabwriter
GOFILES=\
diff --git a/src/pkg/tabwriter/tabwriter.go b/src/pkg/tabwriter/tabwriter.go
index e6ce3232a..848703e8c 100644
--- a/src/pkg/tabwriter/tabwriter.go
+++ b/src/pkg/tabwriter/tabwriter.go
@@ -12,7 +12,6 @@ package tabwriter
import (
"bytes"
- "container/vector"
"io"
"os"
"utf8"
@@ -34,9 +33,8 @@ type cell struct {
}
-// A Writer is a filter that inserts padding around
-// tab-delimited columns in its input to align them
-// in the output.
+// A Writer is a filter that inserts padding around tab-delimited
+// columns in its input to align them in the output.
//
// The Writer treats incoming bytes as UTF-8 encoded text consisting
// of cells terminated by (horizontal or vertical) tabs or line
@@ -48,24 +46,27 @@ type cell struct {
// Note that cells are tab-terminated, not tab-separated: trailing
// non-tab text at the end of a line does not form a column cell.
//
+// The Writer assumes that all Unicode code points have the same width;
+// this may not be true in some fonts.
+//
// If DiscardEmptyColumns is set, empty columns that are terminated
// entirely by vertical (or "soft") tabs are discarded. Columns
// terminated by horizontal (or "hard") tabs are not affected by
// this flag.
//
-// A segment of text may be escaped by bracketing it with Escape
-// characters. The tabwriter strips the Escape characters but otherwise
-// passes escaped text segments through unchanged. In particular, it
-// does not interpret any tabs or line breaks within the segment.
-//
-// The Writer assumes that all characters have the same width;
-// this may not be true in some fonts, especially with certain
-// UTF-8 characters.
-//
// If a Writer is configured to filter HTML, HTML tags and entities
// are simply passed through. The widths of tags and entities are
// assumed to be zero (tags) and one (entities) for formatting purposes.
//
+// A segment of text may be escaped by bracketing it with Escape
+// characters. The tabwriter passes escaped text segments through
+// unchanged. In particular, it does not interpret any tabs or line
+// breaks within the segment. If the StripEscape flag is set, the
+// Escape characters are stripped from the output; otherwise they
+// are passed through as well. For the purpose of formatting, the
+// width of the escaped text is always computed excluding the Escape
+// characters.
+//
// The formfeed character ('\f') acts like a newline but it also
// terminates all columns in the current line (effectively calling
// Flush). Cells in the next line start new columns. Unless found
@@ -86,19 +87,16 @@ type Writer struct {
flags uint
// current state
- buf bytes.Buffer // collected text excluding tabs or line breaks
- pos int // buffer position up to which cell.width of incomplete cell has been computed
- cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections
- endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0)
- lines vector.Vector // list of lines; each line is a list of cells
- widths vector.IntVector // list of column widths in runes - re-used during formatting
+ buf bytes.Buffer // collected text excluding tabs or line breaks
+ pos int // buffer position up to which cell.width of incomplete cell has been computed
+ cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections
+ endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0)
+ lines [][]cell // list of lines; each line is a list of cells
+ widths []int // list of column widths in runes - re-used during formatting
}
-func (b *Writer) addLine() { b.lines.Push(new(vector.Vector)) }
-
-
-func (b *Writer) line(i int) *vector.Vector { return b.lines.At(i).(*vector.Vector) }
+func (b *Writer) addLine() { b.lines = append(b.lines, []cell{}) }
// Reset the current state.
@@ -107,8 +105,8 @@ func (b *Writer) reset() {
b.pos = 0
b.cell = cell{}
b.endChar = 0
- b.lines.Resize(0, 0)
- b.widths.Resize(0, 0)
+ b.lines = b.lines[0:0]
+ b.widths = b.widths[0:0]
b.addLine()
}
@@ -122,9 +120,9 @@ func (b *Writer) reset() {
// - cell.width is text width in runes of that cell from the start of the cell to
// position pos; html tags and entities are excluded from this width if html
// filtering is enabled
-// - the sizes and widths of processed text are kept in the lines vector
-// which contains a vector of cells for each line
-// - the widths vector is a temporary vector with current widths used during
+// - the sizes and widths of processed text are kept in the lines list
+// which contains a list of cells for each line
+// - the widths list is a temporary list with current widths used during
// formatting; it is kept in Writer because it's re-used
//
// |<---------- size ---------->|
@@ -143,6 +141,10 @@ const (
// and ending in ';') as single characters (width = 1).
FilterHTML uint = 1 << iota
+ // Strip Escape characters bracketing escaped text segments
+ // instead of passing them through unchanged with the text.
+ StripEscape
+
// Force right-alignment of cell content.
// Default is left-alignment.
AlignRight
@@ -165,7 +167,7 @@ const (
// specifies the filter output. The remaining parameters control the formatting:
//
// minwidth minimal cell width including any padding
-// tabwidth width of tab characters (equivalent number of spaces)
+// tabwidth width of tab characters (equivalent number of spaces)
// padding padding added to a cell before computing its width
// padchar ASCII char used for padding
// if padchar == '\t', the Writer will assume that the
@@ -207,11 +209,9 @@ func (b *Writer) Init(output io.Writer, minwidth, tabwidth, padding int, padchar
// debugging support (keep code around)
func (b *Writer) dump() {
pos := 0
- for i := 0; i < b.lines.Len(); i++ {
- line := b.line(i)
+ for i, line := range b.lines {
print("(", i, ") ")
- for j := 0; j < line.Len(); j++ {
- c := line.At(j).(cell)
+ for _, c := range line {
print("[", string(b.buf.Bytes()[pos:pos+c.size]), "]")
pos += c.size
}
@@ -280,14 +280,12 @@ var vbar = []byte{'|'}
func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
pos = pos0
for i := line0; i < line1; i++ {
- line := b.line(i)
+ line := b.lines[i]
// if TabIndent is set, use tabs to pad leading empty cells
useTabs := b.flags&TabIndent != 0
- for j := 0; j < line.Len(); j++ {
- c := line.At(j).(cell)
-
+ for j, c := range line {
if j > 0 && b.flags&Debug != 0 {
// indicate column break
b.write0(vbar)
@@ -295,8 +293,8 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
if c.size == 0 {
// empty cell
- if j < b.widths.Len() {
- b.writePadding(c.width, b.widths.At(j), useTabs)
+ if j < len(b.widths) {
+ b.writePadding(c.width, b.widths[j], useTabs)
}
} else {
// non-empty cell
@@ -304,12 +302,12 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
if b.flags&AlignRight == 0 { // align left
b.write0(b.buf.Bytes()[pos : pos+c.size])
pos += c.size
- if j < b.widths.Len() {
- b.writePadding(c.width, b.widths.At(j), false)
+ if j < len(b.widths) {
+ b.writePadding(c.width, b.widths[j], false)
}
} else { // align right
- if j < b.widths.Len() {
- b.writePadding(c.width, b.widths.At(j), false)
+ if j < len(b.widths) {
+ b.writePadding(c.width, b.widths[j], false)
}
b.write0(b.buf.Bytes()[pos : pos+c.size])
pos += c.size
@@ -317,7 +315,7 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
}
}
- if i+1 == b.lines.Len() {
+ if i+1 == len(b.lines) {
// last buffered line - we don't have a newline, so just write
// any outstanding buffered data
b.write0(b.buf.Bytes()[pos : pos+b.cell.size])
@@ -338,11 +336,11 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
//
func (b *Writer) format(pos0 int, line0, line1 int) (pos int) {
pos = pos0
- column := b.widths.Len()
+ column := len(b.widths)
for this := line0; this < line1; this++ {
- line := b.line(this)
+ line := b.lines[this]
- if column < line.Len()-1 {
+ if column < len(line)-1 {
// cell exists in this column => this line
// has more cells than the previous line
// (the last cell per line is ignored because cells are
@@ -358,10 +356,10 @@ func (b *Writer) format(pos0 int, line0, line1 int) (pos int) {
width := b.minwidth // minimal column width
discardable := true // true if all cells in this column are empty and "soft"
for ; this < line1; this++ {
- line = b.line(this)
- if column < line.Len()-1 {
+ line = b.lines[this]
+ if column < len(line)-1 {
// cell exists in this column
- c := line.At(column).(cell)
+ c := line[column]
// update width
if w := c.width + b.padding; w > width {
width = w
@@ -383,9 +381,9 @@ func (b *Writer) format(pos0 int, line0, line1 int) (pos int) {
// format and print all columns to the right of this column
// (we know the widths of this column and all columns to the left)
- b.widths.Push(width)
+ b.widths = append(b.widths, width) // push width
pos = b.format(pos, line0, this)
- b.widths.Pop()
+ b.widths = b.widths[0 : len(b.widths)-1] // pop width
line0 = this
}
}
@@ -441,6 +439,9 @@ func (b *Writer) endEscape() {
switch b.endChar {
case Escape:
b.updateWidth()
+ if b.flags&StripEscape == 0 {
+ b.cell.width -= 2 // don't count the Escape chars
+ }
case '>': // tag of zero width
case ';':
b.cell.width++ // entity, count as one rune
@@ -455,10 +456,10 @@ func (b *Writer) endEscape() {
//
func (b *Writer) terminateCell(htab bool) int {
b.cell.htab = htab
- line := b.line(b.lines.Len() - 1)
- line.Push(b.cell)
+ line := &b.lines[len(b.lines)-1]
+ *line = append(*line, b.cell)
b.cell = cell{}
- return line.Len()
+ return len(*line)
}
@@ -488,7 +489,7 @@ func (b *Writer) Flush() (err os.Error) {
}
// format contents of buffer
- b.format(0, 0, b.lines.Len())
+ b.format(0, 0, len(b.lines))
return
}
@@ -538,7 +539,10 @@ func (b *Writer) Write(buf []byte) (n int, err os.Error) {
// start of escaped sequence
b.append(buf[n:i])
b.updateWidth()
- n = i + 1 // exclude Escape
+ n = i
+ if b.flags&StripEscape != 0 {
+ n++ // strip Escape
+ }
b.startEscape(Escape)
case '<', '&':
@@ -557,8 +561,8 @@ func (b *Writer) Write(buf []byte) (n int, err os.Error) {
if ch == b.endChar {
// end of tag/entity
j := i + 1
- if ch == Escape {
- j = i // exclude Escape
+ if ch == Escape && b.flags&StripEscape != 0 {
+ j = i // strip Escape
}
b.append(buf[n:j])
n = i + 1 // ch consumed
diff --git a/src/pkg/tabwriter/tabwriter_test.go b/src/pkg/tabwriter/tabwriter_test.go
index 1cad62530..043d9154e 100644
--- a/src/pkg/tabwriter/tabwriter_test.go
+++ b/src/pkg/tabwriter/tabwriter_test.go
@@ -43,10 +43,10 @@ func (b *buffer) String() string { return string(b.a) }
func write(t *testing.T, testname string, w *Writer, src string) {
written, err := io.WriteString(w, src)
if err != nil {
- t.Errorf("--- test: %s\n--- src:\n%s\n--- write error: %v\n", testname, src, err)
+ t.Errorf("--- test: %s\n--- src:\n%q\n--- write error: %v\n", testname, src, err)
}
if written != len(src) {
- t.Errorf("--- test: %s\n--- src:\n%s\n--- written = %d, len(src) = %d\n", testname, src, written, len(src))
+ t.Errorf("--- test: %s\n--- src:\n%q\n--- written = %d, len(src) = %d\n", testname, src, written, len(src))
}
}
@@ -54,12 +54,12 @@ func write(t *testing.T, testname string, w *Writer, src string) {
func verify(t *testing.T, testname string, w *Writer, b *buffer, src, expected string) {
err := w.Flush()
if err != nil {
- t.Errorf("--- test: %s\n--- src:\n%s\n--- flush error: %v\n", testname, src, err)
+ t.Errorf("--- test: %s\n--- src:\n%q\n--- flush error: %v\n", testname, src, err)
}
res := b.String()
if res != expected {
- t.Errorf("--- test: %s\n--- src:\n%s\n--- found:\n%s\n--- expected:\n%s\n", testname, src, res, expected)
+ t.Errorf("--- test: %s\n--- src:\n%q\n--- found:\n%q\n--- expected:\n%q\n", testname, src, res, expected)
}
}
@@ -72,216 +72,244 @@ func check(t *testing.T, testname string, minwidth, tabwidth, padding int, padch
w.Init(&b, minwidth, tabwidth, padding, padchar, flags)
// write all at once
+ title := testname + " (written all at once)"
b.clear()
- write(t, testname, &w, src)
- verify(t, testname, &w, &b, src, expected)
+ write(t, title, &w, src)
+ verify(t, title, &w, &b, src, expected)
// write byte-by-byte
+ title = testname + " (written byte-by-byte)"
b.clear()
for i := 0; i < len(src); i++ {
- write(t, testname, &w, src[i:i+1])
+ write(t, title, &w, src[i:i+1])
}
- verify(t, testname, &w, &b, src, expected)
+ verify(t, title, &w, &b, src, expected)
// write using Fibonacci slice sizes
+ title = testname + " (written in fibonacci slices)"
b.clear()
for i, d := 0, 0; i < len(src); {
- write(t, testname, &w, src[i:i+d])
+ write(t, title, &w, src[i:i+d])
i, d = i+d, d+1
if i+d > len(src) {
d = len(src) - i
}
}
- verify(t, testname, &w, &b, src, expected)
+ verify(t, title, &w, &b, src, expected)
}
-type entry struct {
+var tests = []struct {
testname string
minwidth, tabwidth, padding int
padchar byte
flags uint
src, expected string
-}
-
-
-var tests = []entry{
- entry{
+}{
+ {
"1a",
8, 0, 1, '.', 0,
"",
"",
},
- entry{
+ {
"1a debug",
8, 0, 1, '.', Debug,
"",
"",
},
- entry{
+ {
+ "1b esc stripped",
+ 8, 0, 1, '.', StripEscape,
+ "\xff\xff",
+ "",
+ },
+
+ {
"1b esc",
8, 0, 1, '.', 0,
"\xff\xff",
- "",
+ "\xff\xff",
+ },
+
+ {
+ "1c esc stripped",
+ 8, 0, 1, '.', StripEscape,
+ "\xff\t\xff",
+ "\t",
},
- entry{
+ {
"1c esc",
8, 0, 1, '.', 0,
"\xff\t\xff",
- "\t",
+ "\xff\t\xff",
},
- entry{
+ {
+ "1d esc stripped",
+ 8, 0, 1, '.', StripEscape,
+ "\xff\"foo\t\n\tbar\"\xff",
+ "\"foo\t\n\tbar\"",
+ },
+
+ {
"1d esc",
8, 0, 1, '.', 0,
"\xff\"foo\t\n\tbar\"\xff",
- "\"foo\t\n\tbar\"",
+ "\xff\"foo\t\n\tbar\"\xff",
+ },
+
+ {
+ "1e esc stripped",
+ 8, 0, 1, '.', StripEscape,
+ "abc\xff\tdef", // unterminated escape
+ "abc\tdef",
},
- entry{
+ {
"1e esc",
8, 0, 1, '.', 0,
"abc\xff\tdef", // unterminated escape
- "abc\tdef",
+ "abc\xff\tdef",
},
- entry{
+ {
"2",
8, 0, 1, '.', 0,
"\n\n\n",
"\n\n\n",
},
- entry{
+ {
"3",
8, 0, 1, '.', 0,
"a\nb\nc",
"a\nb\nc",
},
- entry{
+ {
"4a",
8, 0, 1, '.', 0,
"\t", // '\t' terminates an empty cell on last line - nothing to print
"",
},
- entry{
+ {
"4b",
8, 0, 1, '.', AlignRight,
"\t", // '\t' terminates an empty cell on last line - nothing to print
"",
},
- entry{
+ {
"5",
8, 0, 1, '.', 0,
"*\t*",
"*.......*",
},
- entry{
+ {
"5b",
8, 0, 1, '.', 0,
"*\t*\n",
"*.......*\n",
},
- entry{
+ {
"5c",
8, 0, 1, '.', 0,
"*\t*\t",
"*.......*",
},
- entry{
+ {
"5c debug",
8, 0, 1, '.', Debug,
"*\t*\t",
"*.......|*",
},
- entry{
+ {
"5d",
8, 0, 1, '.', AlignRight,
"*\t*\t",
".......**",
},
- entry{
+ {
"6",
8, 0, 1, '.', 0,
"\t\n",
"........\n",
},
- entry{
+ {
"7a",
8, 0, 1, '.', 0,
"a) foo",
"a) foo",
},
- entry{
+ {
"7b",
8, 0, 1, ' ', 0,
"b) foo\tbar",
"b) foo bar",
},
- entry{
+ {
"7c",
8, 0, 1, '.', 0,
"c) foo\tbar\t",
"c) foo..bar",
},
- entry{
+ {
"7d",
8, 0, 1, '.', 0,
"d) foo\tbar\n",
"d) foo..bar\n",
},
- entry{
+ {
"7e",
8, 0, 1, '.', 0,
"e) foo\tbar\t\n",
"e) foo..bar.....\n",
},
- entry{
+ {
"7f",
8, 0, 1, '.', FilterHTML,
"f) f&lt;o\t<b>bar</b>\t\n",
"f) f&lt;o..<b>bar</b>.....\n",
},
- entry{
+ {
"7g",
8, 0, 1, '.', FilterHTML,
"g) f&lt;o\t<b>bar</b>\t non-terminated entity &amp",
"g) f&lt;o..<b>bar</b>..... non-terminated entity &amp",
},
- entry{
+ {
"7g debug",
8, 0, 1, '.', FilterHTML | Debug,
"g) f&lt;o\t<b>bar</b>\t non-terminated entity &amp",
"g) f&lt;o..|<b>bar</b>.....| non-terminated entity &amp",
},
- entry{
+ {
"8",
8, 0, 1, '*', 0,
"Hello, world!\n",
"Hello, world!\n",
},
- entry{
+ {
"9a",
1, 0, 0, '.', 0,
"1\t2\t3\t4\n" +
@@ -291,7 +319,7 @@ var tests = []entry{
"11222333344444\n",
},
- entry{
+ {
"9b",
1, 0, 0, '.', FilterHTML,
"1\t2<!---\f--->\t3\t4\n" + // \f inside HTML is ignored
@@ -301,7 +329,7 @@ var tests = []entry{
"11222333344444\n",
},
- entry{
+ {
"9c",
1, 0, 0, '.', 0,
"1\t2\t3\t4\f" + // \f causes a newline and flush
@@ -311,7 +339,7 @@ var tests = []entry{
"11222333344444\n",
},
- entry{
+ {
"9c debug",
1, 0, 0, '.', Debug,
"1\t2\t3\t4\f" + // \f causes a newline and flush
@@ -322,21 +350,21 @@ var tests = []entry{
"11|222|3333|44444\n",
},
- entry{
+ {
"10a",
5, 0, 0, '.', 0,
"1\t2\t3\t4\n",
"1....2....3....4\n",
},
- entry{
+ {
"10b",
5, 0, 0, '.', 0,
"1\t2\t3\t4\t\n",
"1....2....3....4....\n",
},
- entry{
+ {
"11",
8, 0, 1, '.', 0,
"本\tb\tc\n" +
@@ -348,7 +376,7 @@ var tests = []entry{
"aaa.....bbbb\n",
},
- entry{
+ {
"12a",
8, 0, 1, ' ', AlignRight,
"a\tè\tc\t\n" +
@@ -360,7 +388,7 @@ var tests = []entry{
" aaa èèèè\n",
},
- entry{
+ {
"12b",
2, 0, 0, ' ', 0,
"a\tb\tc\n" +
@@ -372,7 +400,7 @@ var tests = []entry{
"aaabbbb\n",
},
- entry{
+ {
"12c",
8, 0, 1, '_', 0,
"a\tb\tc\n" +
@@ -384,7 +412,7 @@ var tests = []entry{
"aaa_____bbbb\n",
},
- entry{
+ {
"13a",
4, 0, 1, '-', 0,
"4444\t日本語\t22\t1\t333\n" +
@@ -404,7 +432,7 @@ var tests = []entry{
"1------1------999999999-0000000000\n",
},
- entry{
+ {
"13b",
4, 0, 3, '.', 0,
"4444\t333\t22\t1\t333\n" +
@@ -424,7 +452,7 @@ var tests = []entry{
"1........1........999999999...0000000000\n",
},
- entry{
+ {
"13c",
8, 8, 1, '\t', FilterHTML,
"4444\t333\t22\t1\t333\n" +
@@ -444,7 +472,7 @@ var tests = []entry{
"1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n",
},
- entry{
+ {
"14",
1, 0, 2, ' ', AlignRight,
".0\t.3\t2.4\t-5.1\t\n" +
@@ -462,7 +490,7 @@ var tests = []entry{
" .0 1.2 44.4 -13.3",
},
- entry{
+ {
"14 debug",
1, 0, 2, ' ', AlignRight | Debug,
".0\t.3\t2.4\t-5.1\t\n" +
@@ -480,35 +508,35 @@ var tests = []entry{
" .0| 1.2| 44.4| -13.3|",
},
- entry{
+ {
"15a",
4, 0, 0, '.', 0,
"a\t\tb",
"a.......b",
},
- entry{
+ {
"15b",
4, 0, 0, '.', DiscardEmptyColumns,
"a\t\tb", // htabs - do not discard column
"a.......b",
},
- entry{
+ {
"15c",
4, 0, 0, '.', DiscardEmptyColumns,
"a\v\vb",
"a...b",
},
- entry{
+ {
"15d",
4, 0, 0, '.', AlignRight | DiscardEmptyColumns,
"a\v\vb",
"...ab",
},
- entry{
+ {
"16a",
100, 100, 0, '\t', 0,
"a\tb\t\td\n" +
@@ -524,7 +552,7 @@ var tests = []entry{
"a\tb\tc\td\te\n",
},
- entry{
+ {
"16b",
100, 100, 0, '\t', DiscardEmptyColumns,
"a\vb\v\vd\n" +
@@ -540,7 +568,7 @@ var tests = []entry{
"a\tb\tc\td\te\n",
},
- entry{
+ {
"16b debug",
100, 100, 0, '\t', DiscardEmptyColumns | Debug,
"a\vb\v\vd\n" +
@@ -556,7 +584,7 @@ var tests = []entry{
"a\t|b\t|c\t|d\t|e\n",
},
- entry{
+ {
"16c",
100, 100, 0, '\t', DiscardEmptyColumns,
"a\tb\t\td\n" + // hard tabs - do not discard column
@@ -572,7 +600,7 @@ var tests = []entry{
"a\tb\tc\td\te\n",
},
- entry{
+ {
"16c debug",
100, 100, 0, '\t', DiscardEmptyColumns | Debug,
"a\tb\t\td\n" + // hard tabs - do not discard column