diff options
Diffstat (limited to 'src/pkg/tabwriter')
-rw-r--r-- | src/pkg/tabwriter/Makefile | 2 | ||||
-rw-r--r-- | src/pkg/tabwriter/tabwriter.go | 122 | ||||
-rw-r--r-- | src/pkg/tabwriter/tabwriter_test.go | 164 |
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<o\t<b>bar</b>\t\n", "f) f<o..<b>bar</b>.....\n", }, - entry{ + { "7g", 8, 0, 1, '.', FilterHTML, "g) f<o\t<b>bar</b>\t non-terminated entity &", "g) f<o..<b>bar</b>..... non-terminated entity &", }, - entry{ + { "7g debug", 8, 0, 1, '.', FilterHTML | Debug, "g) f<o\t<b>bar</b>\t non-terminated entity &", "g) f<o..|<b>bar</b>.....| non-terminated entity &", }, - 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 |