1
0
mirror of https://github.com/golang/go synced 2024-11-18 10:14:45 -07:00

- interpret form feed char as newline + flush

- cleanups:
  - replaced internal byte buffer implementation with io.ByteBuffer (now that we have one)
  - removed all uses of goto statements in favor of structured code
  - converted tests into a table-driven test

R=r
DELTA=277  (48 added, 67 deleted, 162 changed)
OCL=29890
CL=29901
This commit is contained in:
Robert Griesemer 2009-06-04 15:47:57 -07:00
parent d30f80bad2
commit 4019259411
2 changed files with 176 additions and 195 deletions

View File

@ -10,7 +10,6 @@
package tabwriter package tabwriter
import ( import (
"bytes";
"container/vector"; "container/vector";
"io"; "io";
"os"; "os";
@ -18,57 +17,6 @@ import (
) )
// ----------------------------------------------------------------------------
// Basic byteArray support
type byteArray struct {
a []byte;
}
func (b *byteArray) init(initial_size int) {
b.a = make([]byte, initial_size)[0 : 0];
}
func (b *byteArray) len() int {
return len(b.a);
}
func (b *byteArray) clear() {
b.a = b.a[0 : 0];
}
func (b *byteArray) slice(i, j int) []byte {
return b.a[i : j]; // BUG should really be &b.a[i : j]
}
func (b *byteArray) append(s []byte) {
a := b.a;
n := len(a);
m := n + len(s);
if m > cap(a) {
n2 := 2*n;
if m > n2 {
n2 = m;
}
b := make([]byte, n2);
bytes.Copy(b, a);
a = b;
}
a = a[0 : m];
for i := len(s) - 1; i >= 0; i-- {
a[n + i] = s[i];
}
b.a = a;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Filter implementation // Filter implementation
@ -88,6 +36,16 @@ func (b *byteArray) append(s []byte) {
// this may not be true in some fonts, especially with certain // this may not be true in some fonts, especially with certain
// UTF-8 characters. // UTF-8 characters.
// //
// If a Writer is configured to filter HTML, HTML tags and entities
// are simply passed through and their widths are assumed to be zero
// for formatting purposes.
//
// The form feed 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
// inside an HTML tag, form feed characters appear as newlines in
// the output.
//
// The Writer must buffer input internally, because proper spacing // The Writer must buffer input internally, because proper spacing
// of one line may depend on the cells in future lines. Clients must // of one line may depend on the cells in future lines. Clients must
// call Flush when done calling Write. // call Flush when done calling Write.
@ -102,7 +60,7 @@ type Writer struct {
// current state // current state
html_char byte; // terminating char of html tag/entity, or 0 ('>', ';', or 0) html_char byte; // terminating char of html tag/entity, or 0 ('>', ';', or 0)
buf byteArray; // collected text w/o tabs and newlines buf io.ByteBuffer; // collected text w/o tabs, newlines, or form feed chars
size int; // size of incomplete cell in bytes size int; // size of incomplete cell in bytes
width int; // width of incomplete cell in runes up to buf[pos] w/o ignored sections width int; // width of incomplete cell in runes up to buf[pos] w/o ignored sections
pos int; // buffer position up to which width of incomplete cell has been computed pos int; // buffer position up to which width of incomplete cell has been computed
@ -114,7 +72,7 @@ type Writer struct {
// Internal representation (current state): // Internal representation (current state):
// //
// - all text written is appended to buf; tabs and newlines are stripped away // - all text written is appended to buf; form feed chars, tabs and newlines are stripped away
// - at any given time there is a (possibly empty) incomplete cell at the end // - at any given time there is a (possibly empty) incomplete cell at the end
// (the cell starts after a tab or newline) // (the cell starts after a tab or newline)
// - size is the number of bytes belonging to the cell so far // - size is the number of bytes belonging to the cell so far
@ -182,12 +140,10 @@ func (b *Writer) Init(output io.Writer, cellwidth, padding int, padchar byte, fl
} }
if padchar == '\t' { if padchar == '\t' {
// tab enforces left-alignment // tab enforces left-alignment
t := ^AlignRight; // TODO 6g bug flags &^= AlignRight;
flags &= uint(t);
} }
b.flags = flags; b.flags = flags;
b.buf.init(1024);
b.lines_size.Init(0); b.lines_size.Init(0);
b.lines_width.Init(0); b.lines_width.Init(0);
b.widths.Init(0); b.widths.Init(0);
@ -204,7 +160,8 @@ func (b *Writer) line(i int) (*vector.IntVector, *vector.IntVector) {
} }
// debugging support // debugging support (keep code around)
/*
func (b *Writer) dump() { func (b *Writer) dump() {
pos := 0; pos := 0;
for i := 0; i < b.lines_size.Len(); i++ { for i := 0; i < b.lines_size.Len(); i++ {
@ -219,6 +176,7 @@ func (b *Writer) dump() {
} }
print("\n"); print("\n");
} }
*/
func (b *Writer) write0(buf []byte) os.Error { func (b *Writer) write0(buf []byte) os.Error {
@ -232,7 +190,7 @@ func (b *Writer) write0(buf []byte) os.Error {
var newline = []byte{'\n'} var newline = []byte{'\n'}
func (b *Writer) writePadding(textw, cellw int) (err os.Error) { func (b *Writer) writePadding(textw, cellw int) os.Error {
if b.padbytes[0] == '\t' { if b.padbytes[0] == '\t' {
// make cell width a multiple of cellwidth // make cell width a multiple of cellwidth
cellw = ((cellw + b.cellwidth - 1) / b.cellwidth) * b.cellwidth; cellw = ((cellw + b.cellwidth - 1) / b.cellwidth) * b.cellwidth;
@ -248,21 +206,18 @@ func (b *Writer) writePadding(textw, cellw int) (err os.Error) {
} }
for n > len(b.padbytes) { for n > len(b.padbytes) {
err = b.write0(&b.padbytes); if err := b.write0(&b.padbytes); err != nil {
if err != nil { return err;
goto exit;
} }
n -= len(b.padbytes); n -= len(b.padbytes);
} }
err = b.write0(b.padbytes[0 : n]);
exit: return b.write0(b.padbytes[0 : n]);
return err;
} }
func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err os.Error) { func (b *Writer) writeLines(pos0 int, line0, line1 int) (int, os.Error) {
pos = pos0; pos := pos0;
for i := line0; i < line1; i++ { for i := line0; i < line1; i++ {
line_size, line_width := b.line(i); line_size, line_width := b.line(i);
for j := 0; j < line_size.Len(); j++ { for j := 0; j < line_size.Len(); j++ {
@ -271,29 +226,25 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err os.Error)
switch { switch {
default: // align left default: // align left
err = b.write0(b.buf.slice(pos, pos + s)); if err := b.write0(b.buf.Data()[pos : pos + s]); err != nil {
if err != nil { return pos, err;
goto exit;
} }
pos += s; pos += s;
if j < b.widths.Len() { if j < b.widths.Len() {
err = b.writePadding(w, b.widths.At(j)); if err := b.writePadding(w, b.widths.At(j)); err != nil {
if err != nil { return pos, err;
goto exit;
} }
} }
case b.flags & AlignRight != 0: // align right case b.flags & AlignRight != 0: // align right
if j < b.widths.Len() { if j < b.widths.Len() {
err = b.writePadding(w, b.widths.At(j)); if err := b.writePadding(w, b.widths.At(j)); err != nil {
if err != nil { return pos, err;
goto exit;
} }
} }
err = b.write0(b.buf.slice(pos, pos + s)); if err := b.write0(b.buf.Data()[pos : pos + s]); err != nil {
if err != nil { return pos, err;
goto exit;
} }
pos += s; pos += s;
} }
@ -302,19 +253,18 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err os.Error)
if i+1 == b.lines_size.Len() { if i+1 == b.lines_size.Len() {
// last buffered line - we don't have a newline, so just write // last buffered line - we don't have a newline, so just write
// any outstanding buffered data // any outstanding buffered data
err = b.write0(b.buf.slice(pos, pos + b.size)); if err := b.write0(b.buf.Data()[pos : pos + b.size]); err != nil {
return pos, err;
}
pos += b.size; pos += b.size;
} else { } else {
// not the last line - write newline // not the last line - write newline
err = b.write0(newline); if err := b.write0(newline); err != nil {
} return pos, err;
if err != nil { }
goto exit;
} }
} }
return pos, nil;
exit:
return pos, err;
} }
@ -332,7 +282,7 @@ func (b *Writer) format(pos0 int, line0, line1 int) (pos int, err os.Error) {
// print unprinted lines until beginning of block // print unprinted lines until beginning of block
pos, err = b.writeLines(pos, last, this); pos, err = b.writeLines(pos, last, this);
if err != nil { if err != nil {
goto exit; return pos, err;
} }
last = this; last = this;
@ -362,10 +312,7 @@ func (b *Writer) format(pos0 int, line0, line1 int) (pos int, err os.Error) {
} }
// print unprinted lines until end // print unprinted lines until end
pos, err = b.writeLines(pos, last, line1); return b.writeLines(pos, last, line1);
exit:
return pos, err;
} }
@ -373,9 +320,9 @@ exit:
// that any data buffered in the Writer is written to output. // that any data buffered in the Writer is written to output.
// //
func (b *Writer) Flush() os.Error { func (b *Writer) Flush() os.Error {
dummy, err := b.format(0, 0, b.lines_size.Len()); _, err := b.format(0, 0, b.lines_size.Len());
// reset (even in the presence of errors) // reset (even in the presence of errors)
b.buf.clear(); b.buf.Reset();
b.size, b.width = 0, 0; b.size, b.width = 0, 0;
b.pos = 0; b.pos = 0;
b.lines_size.Init(0); b.lines_size.Init(0);
@ -401,7 +348,7 @@ func unicodeLen(buf []byte) int {
func (b *Writer) append(buf []byte) { func (b *Writer) append(buf []byte) {
b.buf.append(buf); b.buf.Write(buf);
b.size += len(buf); b.size += len(buf);
} }
@ -420,11 +367,11 @@ func (b *Writer) Write(buf []byte) (written int, err os.Error) {
if b.html_char == 0 { if b.html_char == 0 {
// outside html tag/entity // outside html tag/entity
switch ch { switch ch {
case '\t', '\n': case '\t', '\n', '\f':
b.append(buf[i0 : i]); b.append(buf[i0 : i]);
i0 = i + 1; // exclude ch from (next) cell i0 = i + 1; // exclude ch from (next) cell
b.width += unicodeLen(b.buf.slice(b.pos, b.buf.len())); b.width += unicodeLen(b.buf.Data()[b.pos : b.buf.Len()]);
b.pos = b.buf.len(); b.pos = b.buf.Len();
// terminate cell // terminate cell
last_size, last_width := b.line(b.lines_size.Len() - 1); last_size, last_width := b.line(b.lines_size.Len() - 1);
@ -432,15 +379,16 @@ func (b *Writer) Write(buf []byte) (written int, err os.Error) {
last_width.Push(b.width); last_width.Push(b.width);
b.size, b.width = 0, 0; b.size, b.width = 0, 0;
if ch == '\n' { if ch != '\t' {
// terminate line
b.addLine(); b.addLine();
if last_size.Len() == 1 { if ch == '\f' || last_size.Len() == 1 {
// The previous line has only one cell which does not have // A '\f' always forces a flush. Otherwise, if the previous
// an impact on the formatting of the following lines (the // line has only one cell which does not have an impact on
// last cell per line is ignored by format()), thus we can // the formatting of the following lines (the last cell per
// flush the Writer contents. // line is ignored by format()), thus we can flush the
err = b.Flush(); // Writer contents.
if err != nil { if err = b.Flush(); err != nil {
return i0, err; return i0, err;
} }
} }
@ -450,7 +398,7 @@ func (b *Writer) Write(buf []byte) (written int, err os.Error) {
if b.flags & FilterHTML != 0 { if b.flags & FilterHTML != 0 {
b.append(buf[i0 : i]); b.append(buf[i0 : i]);
i0 = i; i0 = i;
b.width += unicodeLen(b.buf.slice(b.pos, b.buf.len())); b.width += unicodeLen(b.buf.Data()[b.pos : b.buf.Len()]);
b.pos = -1; // preventative - should not be used (will cause index out of bounds) b.pos = -1; // preventative - should not be used (will cause index out of bounds)
if ch == '<' { if ch == '<' {
b.html_char = '>'; b.html_char = '>';
@ -469,7 +417,7 @@ func (b *Writer) Write(buf []byte) (written int, err os.Error) {
if b.html_char == ';' { if b.html_char == ';' {
b.width++; // count as one char b.width++; // count as one char
} }
b.pos = b.buf.len(); b.pos = b.buf.Len();
b.html_char = 0; b.html_char = 0;
} }
} }

View File

@ -103,132 +103,158 @@ func check(t *testing.T, tabwidth, padding int, padchar byte, flags uint, src, e
} }
func Test(t *testing.T) { type entry struct {
check( tabwidth, padding int;
t, 8, 1, '.', 0, padchar byte;
flags uint;
src, expected string;
}
var tests = []entry {
entry{
8, 1, '.', 0,
"", "",
"" ""
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"\n\n\n", "\n\n\n",
"\n\n\n" "\n\n\n"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"a\nb\nc", "a\nb\nc",
"a\nb\nc" "a\nb\nc"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"\t", // '\t' terminates an empty cell on last line - nothing to print "\t", // '\t' terminates an empty cell on last line - nothing to print
"" ""
); },
check( entry{
t, 8, 1, '.', tabwriter.AlignRight, 8, 1, '.', tabwriter.AlignRight,
"\t", // '\t' terminates an empty cell on last line - nothing to print "\t", // '\t' terminates an empty cell on last line - nothing to print
"" ""
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"*\t*", "*\t*",
"**" "**"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"*\t*\n", "*\t*\n",
"*.......*\n" "*.......*\n"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"*\t*\t", "*\t*\t",
"*.......*" "*.......*"
); },
check( entry{
t, 8, 1, '.', tabwriter.AlignRight, 8, 1, '.', tabwriter.AlignRight,
"*\t*\t", "*\t*\t",
".......**" ".......**"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"\t\n", "\t\n",
"........\n" "........\n"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"a) foo", "a) foo",
"a) foo" "a) foo"
); },
check( entry{
t, 8, 1, ' ', 0, 8, 1, ' ', 0,
"b) foo\tbar", // "bar" is not in any cell - not formatted, just flushed "b) foo\tbar", // "bar" is not in any cell - not formatted, just flushed
"b) foobar" "b) foobar"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"c) foo\tbar\t", "c) foo\tbar\t",
"c) foo..bar" "c) foo..bar"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"d) foo\tbar\n", "d) foo\tbar\n",
"d) foo..bar\n" "d) foo..bar\n"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"e) foo\tbar\t\n", "e) foo\tbar\t\n",
"e) foo..bar.....\n" "e) foo..bar.....\n"
); },
check( entry{
t, 8, 1, '.', tabwriter.FilterHTML, 8, 1, '.', tabwriter.FilterHTML,
"e) f&lt;o\t<b>bar</b>\t\n", "f) f&lt;o\t<b>bar</b>\t\n",
"e) f&lt;o..<b>bar</b>.....\n" "f) f&lt;o..<b>bar</b>.....\n"
); },
check( entry{
t, 8, 1, '*', 0, 8, 1, '*', 0,
"Hello, world!\n", "Hello, world!\n",
"Hello, world!\n" "Hello, world!\n"
); },
check( entry{
t, 0, 0, '.', 0, 0, 0, '.', 0,
"1\t2\t3\t4\n" "1\t2\t3\t4\n"
"11\t222\t3333\t44444\n", "11\t222\t3333\t44444\n",
"1.2..3...4\n" "1.2..3...4\n"
"11222333344444\n" "11222333344444\n"
); },
check( entry{
t, 5, 0, '.', 0, 0, 0, '.', tabwriter.FilterHTML,
"1\t2<!---\f--->\t3\t4\n" // \f inside HTML is ignored
"11\t222\t3333\t44444\n",
"1.2<!---\f--->..3...4\n"
"11222333344444\n"
},
entry{
0, 0, '.', 0,
"1\t2\t3\t4\f" // \f causes a newline and flush
"11\t222\t3333\t44444\n",
"1234\n"
"11222333344444\n"
},
entry{
5, 0, '.', 0,
"1\t2\t3\t4\n", "1\t2\t3\t4\n",
"1....2....3....4\n" "1....2....3....4\n"
); },
check( entry{
t, 5, 0, '.', 0, 5, 0, '.', 0,
"1\t2\t3\t4\t\n", "1\t2\t3\t4\t\n",
"1....2....3....4....\n" "1....2....3....4....\n"
); },
check( entry{
t, 8, 1, '.', 0, 8, 1, '.', 0,
"本\tb\tc\n" "本\tb\tc\n"
"aa\t\u672c\u672c\u672c\tcccc\tddddd\n" "aa\t\u672c\u672c\u672c\tcccc\tddddd\n"
"aaa\tbbbb\n", "aaa\tbbbb\n",
@ -236,10 +262,10 @@ func Test(t *testing.T) {
"本.......b.......c\n" "本.......b.......c\n"
"aa......本本本.....cccc....ddddd\n" "aa......本本本.....cccc....ddddd\n"
"aaa.....bbbb\n" "aaa.....bbbb\n"
); },
check( entry{
t, 8, 1, ' ', tabwriter.AlignRight, 8, 1, ' ', tabwriter.AlignRight,
"a\tè\tc\t\n" "a\tè\tc\t\n"
"aa\tèèè\tcccc\tddddd\t\n" "aa\tèèè\tcccc\tddddd\t\n"
"aaa\tèèèè\t\n", "aaa\tèèèè\t\n",
@ -247,10 +273,10 @@ func Test(t *testing.T) {
" a è c\n" " a è c\n"
" aa èèè cccc ddddd\n" " aa èèè cccc ddddd\n"
" aaa èèèè\n" " aaa èèèè\n"
); },
check( entry{
t, 2, 0, ' ', 0, 2, 0, ' ', 0,
"a\tb\tc\n" "a\tb\tc\n"
"aa\tbbb\tcccc\n" "aa\tbbb\tcccc\n"
"aaa\tbbbb\n", "aaa\tbbbb\n",
@ -258,10 +284,10 @@ func Test(t *testing.T) {
"a b c\n" "a b c\n"
"aa bbbcccc\n" "aa bbbcccc\n"
"aaabbbb\n" "aaabbbb\n"
); },
check( entry{
t, 8, 1, '_', 0, 8, 1, '_', 0,
"a\tb\tc\n" "a\tb\tc\n"
"aa\tbbb\tcccc\n" "aa\tbbb\tcccc\n"
"aaa\tbbbb\n", "aaa\tbbbb\n",
@ -269,10 +295,10 @@ func Test(t *testing.T) {
"a_______b_______c\n" "a_______b_______c\n"
"aa______bbb_____cccc\n" "aa______bbb_____cccc\n"
"aaa_____bbbb\n" "aaa_____bbbb\n"
); },
check( entry{
t, 4, 1, '-', 0, 4, 1, '-', 0,
"4444\t日本語\t22\t1\t333\n" "4444\t日本語\t22\t1\t333\n"
"999999999\t22\n" "999999999\t22\n"
"7\t22\n" "7\t22\n"
@ -288,10 +314,10 @@ func Test(t *testing.T) {
"\n" "\n"
"666666-666666-666666----4444\n" "666666-666666-666666----4444\n"
"1------1------999999999-0000000000\n" "1------1------999999999-0000000000\n"
); },
check( entry{
t, 4, 3, '.', 0, 4, 3, '.', 0,
"4444\t333\t22\t1\t333\n" "4444\t333\t22\t1\t333\n"
"999999999\t22\n" "999999999\t22\n"
"7\t22\n" "7\t22\n"
@ -307,10 +333,10 @@ func Test(t *testing.T) {
"\n" "\n"
"666666...666666...666666......4444\n" "666666...666666...666666......4444\n"
"1........1........999999999...0000000000\n" "1........1........999999999...0000000000\n"
); },
check( entry{
t, 8, 1, '\t', tabwriter.FilterHTML, 8, 1, '\t', tabwriter.FilterHTML,
"4444\t333\t22\t1\t333\n" "4444\t333\t22\t1\t333\n"
"999999999\t22\n" "999999999\t22\n"
"7\t22\n" "7\t22\n"
@ -326,10 +352,10 @@ func Test(t *testing.T) {
"\n" "\n"
"666666\t666666\t666666\t\t4444\n" "666666\t666666\t666666\t\t4444\n"
"1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n" "1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n"
); },
check( entry{
t, 0, 2, ' ', tabwriter.AlignRight, 0, 2, ' ', tabwriter.AlignRight,
".0\t.3\t2.4\t-5.1\t\n" ".0\t.3\t2.4\t-5.1\t\n"
"23.0\t12345678.9\t2.4\t-989.4\t\n" "23.0\t12345678.9\t2.4\t-989.4\t\n"
"5.1\t12.0\t2.4\t-7.0\t\n" "5.1\t12.0\t2.4\t-7.0\t\n"
@ -343,5 +369,12 @@ func Test(t *testing.T) {
" .0 0.0 332.0 8908.0\n" " .0 0.0 332.0 8908.0\n"
" .0 -.3 456.4 22.1\n" " .0 -.3 456.4 22.1\n"
" .0 1.2 44.4 -13.3" " .0 1.2 44.4 -13.3"
); },
}
func Test(t *testing.T) {
for _, e := range tests {
check(t, e.tabwidth, e.padding, e.padchar, e.flags, e.src, e.expected);
}
} }