1
0
mirror of https://github.com/golang/go synced 2024-09-30 16:18:35 -06: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
import (
"bytes";
"container/vector";
"io";
"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
@ -88,6 +36,16 @@ func (b *byteArray) append(s []byte) {
// 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 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
// of one line may depend on the cells in future lines. Clients must
// call Flush when done calling Write.
@ -102,7 +60,7 @@ type Writer struct {
// current state
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
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
@ -114,7 +72,7 @@ type Writer struct {
// 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
// (the cell starts after a tab or newline)
// - 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' {
// tab enforces left-alignment
t := ^AlignRight; // TODO 6g bug
flags &= uint(t);
flags &^= AlignRight;
}
b.flags = flags;
b.buf.init(1024);
b.lines_size.Init(0);
b.lines_width.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() {
pos := 0;
for i := 0; i < b.lines_size.Len(); i++ {
@ -219,6 +176,7 @@ func (b *Writer) dump() {
}
print("\n");
}
*/
func (b *Writer) write0(buf []byte) os.Error {
@ -232,7 +190,7 @@ func (b *Writer) write0(buf []byte) os.Error {
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' {
// make cell width a multiple of 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) {
err = b.write0(&b.padbytes);
if err != nil {
goto exit;
if err := b.write0(&b.padbytes); err != nil {
return err;
}
n -= len(b.padbytes);
}
err = b.write0(b.padbytes[0 : n]);
exit:
return err;
return b.write0(b.padbytes[0 : n]);
}
func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err os.Error) {
pos = pos0;
func (b *Writer) writeLines(pos0 int, line0, line1 int) (int, os.Error) {
pos := pos0;
for i := line0; i < line1; i++ {
line_size, line_width := b.line(i);
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 {
default: // align left
err = b.write0(b.buf.slice(pos, pos + s));
if err != nil {
goto exit;
if err := b.write0(b.buf.Data()[pos : pos + s]); err != nil {
return pos, err;
}
pos += s;
if j < b.widths.Len() {
err = b.writePadding(w, b.widths.At(j));
if err != nil {
goto exit;
if err := b.writePadding(w, b.widths.At(j)); err != nil {
return pos, err;
}
}
case b.flags & AlignRight != 0: // align right
if j < b.widths.Len() {
err = b.writePadding(w, b.widths.At(j));
if err != nil {
goto exit;
if err := b.writePadding(w, b.widths.At(j)); err != nil {
return pos, err;
}
}
err = b.write0(b.buf.slice(pos, pos + s));
if err != nil {
goto exit;
if err := b.write0(b.buf.Data()[pos : pos + s]); err != nil {
return pos, err;
}
pos += s;
}
@ -302,20 +253,19 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err os.Error)
if i+1 == b.lines_size.Len() {
// last buffered line - we don't have a newline, so just write
// 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;
} else {
// not the last line - write newline
err = b.write0(newline);
}
if err != nil {
goto exit;
}
}
exit:
if err := b.write0(newline); err != nil {
return pos, err;
}
}
}
return pos, nil;
}
func (b *Writer) format(pos0 int, line0, line1 int) (pos int, err os.Error) {
@ -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
pos, err = b.writeLines(pos, last, this);
if err != nil {
goto exit;
return pos, err;
}
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
pos, err = b.writeLines(pos, last, line1);
exit:
return pos, err;
return b.writeLines(pos, last, line1);
}
@ -373,9 +320,9 @@ exit:
// that any data buffered in the Writer is written to output.
//
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)
b.buf.clear();
b.buf.Reset();
b.size, b.width = 0, 0;
b.pos = 0;
b.lines_size.Init(0);
@ -401,7 +348,7 @@ func unicodeLen(buf []byte) int {
func (b *Writer) append(buf []byte) {
b.buf.append(buf);
b.buf.Write(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 {
// outside html tag/entity
switch ch {
case '\t', '\n':
case '\t', '\n', '\f':
b.append(buf[i0 : i]);
i0 = i + 1; // exclude ch from (next) cell
b.width += unicodeLen(b.buf.slice(b.pos, b.buf.len()));
b.pos = b.buf.len();
b.width += unicodeLen(b.buf.Data()[b.pos : b.buf.Len()]);
b.pos = b.buf.Len();
// terminate cell
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);
b.size, b.width = 0, 0;
if ch == '\n' {
if ch != '\t' {
// terminate line
b.addLine();
if last_size.Len() == 1 {
// The previous line has only one cell which does not have
// an impact on the formatting of the following lines (the
// last cell per line is ignored by format()), thus we can
// flush the Writer contents.
err = b.Flush();
if err != nil {
if ch == '\f' || last_size.Len() == 1 {
// A '\f' always forces a flush. Otherwise, if the previous
// line has only one cell which does not have an impact on
// the formatting of the following lines (the last cell per
// line is ignored by format()), thus we can flush the
// Writer contents.
if err = b.Flush(); err != nil {
return i0, err;
}
}
@ -450,7 +398,7 @@ func (b *Writer) Write(buf []byte) (written int, err os.Error) {
if b.flags & FilterHTML != 0 {
b.append(buf[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)
if ch == '<' {
b.html_char = '>';
@ -469,7 +417,7 @@ func (b *Writer) Write(buf []byte) (written int, err os.Error) {
if b.html_char == ';' {
b.width++; // count as one char
}
b.pos = b.buf.len();
b.pos = b.buf.Len();
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) {
check(
t, 8, 1, '.', 0,
type entry struct {
tabwidth, padding int;
padchar byte;
flags uint;
src, expected string;
}
var tests = []entry {
entry{
8, 1, '.', 0,
"",
""
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"\n\n\n",
"\n\n\n"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"a\nb\nc",
"a\nb\nc"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"\t", // '\t' terminates an empty cell on last line - nothing to print
""
);
},
check(
t, 8, 1, '.', tabwriter.AlignRight,
entry{
8, 1, '.', tabwriter.AlignRight,
"\t", // '\t' terminates an empty cell on last line - nothing to print
""
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"*\t*",
"**"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"*\t*\n",
"*.......*\n"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"*\t*\t",
"*.......*"
);
},
check(
t, 8, 1, '.', tabwriter.AlignRight,
entry{
8, 1, '.', tabwriter.AlignRight,
"*\t*\t",
".......**"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"\t\n",
"........\n"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"a) foo",
"a) foo"
);
},
check(
t, 8, 1, ' ', 0,
entry{
8, 1, ' ', 0,
"b) foo\tbar", // "bar" is not in any cell - not formatted, just flushed
"b) foobar"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"c) foo\tbar\t",
"c) foo..bar"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"d) foo\tbar\n",
"d) foo..bar\n"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"e) foo\tbar\t\n",
"e) foo..bar.....\n"
);
},
check(
t, 8, 1, '.', tabwriter.FilterHTML,
"e) f&lt;o\t<b>bar</b>\t\n",
"e) f&lt;o..<b>bar</b>.....\n"
);
entry{
8, 1, '.', tabwriter.FilterHTML,
"f) f&lt;o\t<b>bar</b>\t\n",
"f) f&lt;o..<b>bar</b>.....\n"
},
check(
t, 8, 1, '*', 0,
entry{
8, 1, '*', 0,
"Hello, world!\n",
"Hello, world!\n"
);
},
check(
t, 0, 0, '.', 0,
entry{
0, 0, '.', 0,
"1\t2\t3\t4\n"
"11\t222\t3333\t44444\n",
"1.2..3...4\n"
"11222333344444\n"
);
},
check(
t, 5, 0, '.', 0,
entry{
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....2....3....4\n"
);
},
check(
t, 5, 0, '.', 0,
entry{
5, 0, '.', 0,
"1\t2\t3\t4\t\n",
"1....2....3....4....\n"
);
},
check(
t, 8, 1, '.', 0,
entry{
8, 1, '.', 0,
"本\tb\tc\n"
"aa\t\u672c\u672c\u672c\tcccc\tddddd\n"
"aaa\tbbbb\n",
@ -236,10 +262,10 @@ func Test(t *testing.T) {
"本.......b.......c\n"
"aa......本本本.....cccc....ddddd\n"
"aaa.....bbbb\n"
);
},
check(
t, 8, 1, ' ', tabwriter.AlignRight,
entry{
8, 1, ' ', tabwriter.AlignRight,
"a\tè\tc\t\n"
"aa\tèèè\tcccc\tddddd\t\n"
"aaa\tèèèè\t\n",
@ -247,10 +273,10 @@ func Test(t *testing.T) {
" a è c\n"
" aa èèè cccc ddddd\n"
" aaa èèèè\n"
);
},
check(
t, 2, 0, ' ', 0,
entry{
2, 0, ' ', 0,
"a\tb\tc\n"
"aa\tbbb\tcccc\n"
"aaa\tbbbb\n",
@ -258,10 +284,10 @@ func Test(t *testing.T) {
"a b c\n"
"aa bbbcccc\n"
"aaabbbb\n"
);
},
check(
t, 8, 1, '_', 0,
entry{
8, 1, '_', 0,
"a\tb\tc\n"
"aa\tbbb\tcccc\n"
"aaa\tbbbb\n",
@ -269,10 +295,10 @@ func Test(t *testing.T) {
"a_______b_______c\n"
"aa______bbb_____cccc\n"
"aaa_____bbbb\n"
);
},
check(
t, 4, 1, '-', 0,
entry{
4, 1, '-', 0,
"4444\t日本語\t22\t1\t333\n"
"999999999\t22\n"
"7\t22\n"
@ -288,10 +314,10 @@ func Test(t *testing.T) {
"\n"
"666666-666666-666666----4444\n"
"1------1------999999999-0000000000\n"
);
},
check(
t, 4, 3, '.', 0,
entry{
4, 3, '.', 0,
"4444\t333\t22\t1\t333\n"
"999999999\t22\n"
"7\t22\n"
@ -307,10 +333,10 @@ func Test(t *testing.T) {
"\n"
"666666...666666...666666......4444\n"
"1........1........999999999...0000000000\n"
);
},
check(
t, 8, 1, '\t', tabwriter.FilterHTML,
entry{
8, 1, '\t', tabwriter.FilterHTML,
"4444\t333\t22\t1\t333\n"
"999999999\t22\n"
"7\t22\n"
@ -326,10 +352,10 @@ func Test(t *testing.T) {
"\n"
"666666\t666666\t666666\t\t4444\n"
"1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n"
);
},
check(
t, 0, 2, ' ', tabwriter.AlignRight,
entry{
0, 2, ' ', tabwriter.AlignRight,
".0\t.3\t2.4\t-5.1\t\n"
"23.0\t12345678.9\t2.4\t-989.4\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 -.3 456.4 22.1\n"
" .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);
}
}