package tabwriter
import (
- "bytes";
"container/vector";
"io";
"os";
)
-// ----------------------------------------------------------------------------
-// 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
// 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.
// 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
// 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
}
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);
}
-// debugging support
+// debugging support (keep code around)
+/*
func (b *Writer) dump() {
pos := 0;
for i := 0; i < b.lines_size.Len(); i++ {
}
print("\n");
}
+*/
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;
}
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++ {
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;
}
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;
+ if err := b.write0(newline); err != nil {
+ return pos, err;
+ }
}
}
-
-exit:
- return pos, err;
+ return pos, nil;
}
// print unprinted lines until beginning of block
pos, err = b.writeLines(pos, last, this);
if err != nil {
- goto exit;
+ return pos, err;
}
last = this;
}
// print unprinted lines until end
- pos, err = b.writeLines(pos, last, line1);
-
-exit:
- return pos, err;
+ return b.writeLines(pos, last, line1);
}
// 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);
func (b *Writer) append(buf []byte) {
- b.buf.append(buf);
+ b.buf.Write(buf);
b.size += len(buf);
}
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);
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;
}
}
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 = '>';
if b.html_char == ';' {
b.width++; // count as one char
}
- b.pos = b.buf.len();
+ b.pos = b.buf.Len();
b.html_char = 0;
}
}
}
-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<o\t<b>bar</b>\t\n",
- "e) f<o..<b>bar</b>.....\n"
- );
+ entry{
+ 8, 1, '.', tabwriter.FilterHTML,
+ "f) f<o\t<b>bar</b>\t\n",
+ "f) f<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",
"本.......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",
" 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",
"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",
"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"
"\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"
"\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"
"\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"
" .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);
+ }
}