aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2008-11-20 17:39:41 -0800
committerRobert Griesemer <gri@golang.org>2008-11-20 17:39:41 -0800
commit01b44bbfc8ca90d9eb3418ad47d9d7a472bb4cde (patch)
tree2323261e60d048d3c4bcef358862e4f63fc506a0 /src/lib
parent67addd4e11f147125952b0d4b50c1ed2563129e9 (diff)
downloadgo-01b44bbfc8ca90d9eb3418ad47d9d7a472bb4cde.tar.xz
- move tabwriter into library
- added preliminary tests (more to do) - renamed type from TabWriter -> Writer - adjusted my code where necessary R=r DELTA=825 (474 added, 346 deleted, 5 changed) OCL=19744 CL=19753
Diffstat (limited to 'src/lib')
-rwxr-xr-xsrc/lib/clean.bash6
-rwxr-xr-xsrc/lib/make.bash3
-rw-r--r--src/lib/tabwriter/Makefile55
-rw-r--r--src/lib/tabwriter/tabwriter.go343
-rw-r--r--src/lib/tabwriter/tabwriter_test.go81
5 files changed, 483 insertions, 5 deletions
diff --git a/src/lib/clean.bash b/src/lib/clean.bash
index e32e2eb07b..02f5aab49f 100755
--- a/src/lib/clean.bash
+++ b/src/lib/clean.bash
@@ -6,10 +6,8 @@
rm -f $GOROOT/pkg/*
-for i in syscall os math fmt net time http reflect regexp
+for i in syscall math os strconv container/array reflect fmt tabwriter net time http regexp
do
- cd $i
- make nuke
- cd ..
+ (cd $i; make nuke)
done
diff --git a/src/lib/make.bash b/src/lib/make.bash
index 76ae0404e4..4398d44e3f 100755
--- a/src/lib/make.bash
+++ b/src/lib/make.bash
@@ -38,7 +38,8 @@ builddirs syscall\
buildfiles io.go
-builddirs fmt
+builddirs fmt\
+ tabwriter\
buildfiles flag.go\
container/vector.go\
diff --git a/src/lib/tabwriter/Makefile b/src/lib/tabwriter/Makefile
new file mode 100644
index 0000000000..7fbdf5546a
--- /dev/null
+++ b/src/lib/tabwriter/Makefile
@@ -0,0 +1,55 @@
+# Copyright 2009 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# DO NOT EDIT. Automatically generated by gobuild.
+# gobuild -m >Makefile
+O=6
+GC=$(O)g
+CC=$(O)c -w
+AS=$(O)a
+AR=$(O)ar
+
+default: packages
+
+clean:
+ rm -f *.$O *.a $O.out
+
+test: packages
+ gotest
+
+coverage: packages
+ gotest
+ 6cov -g `pwd` | grep -v '_test\.go:'
+
+%.$O: %.go
+ $(GC) $*.go
+
+%.$O: %.c
+ $(CC) $*.c
+
+%.$O: %.s
+ $(AS) $*.s
+
+O1=\
+ tabwriter.$O\
+
+tabwriter.a: a1
+
+a1: $(O1)
+ $(AR) grc tabwriter.a tabwriter.$O
+ rm -f $(O1)
+
+newpkg: clean
+ $(AR) grc tabwriter.a
+
+$(O1): newpkg
+
+nuke: clean
+ rm -f $(GOROOT)/pkg/tabwriter.a
+
+packages: tabwriter.a
+
+install: packages
+ cp tabwriter.a $(GOROOT)/pkg/tabwriter.a
+
diff --git a/src/lib/tabwriter/tabwriter.go b/src/lib/tabwriter/tabwriter.go
new file mode 100644
index 0000000000..3eb0ba195b
--- /dev/null
+++ b/src/lib/tabwriter/tabwriter.go
@@ -0,0 +1,343 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tabwriter
+
+import (
+ "os";
+ "io";
+ "array";
+)
+
+
+// ----------------------------------------------------------------------------
+// ByteArray
+// TODO should use a ByteArray library eventually
+
+type ByteArray struct {
+ a *[]byte;
+}
+
+
+func (b *ByteArray) Init(initial_size int) {
+ b.a = new([]byte, initial_size)[0 : 0];
+}
+
+
+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 := new([]byte, n2);
+ for i := 0; i < n; i++ {
+ b[i] = a[i];
+ }
+ a = b;
+ }
+
+ a = a[0 : m];
+ for i := len(s) - 1; i >= 0; i-- {
+ a[n + i] = s[i];
+ }
+ b.a = a;
+}
+
+
+// ----------------------------------------------------------------------------
+// Writer is a filter implementing the io.Write interface. It assumes
+// that the incoming bytes represent ASCII encoded text consisting of
+// lines of tab-separated "cells". Cells in adjacent lines constitute
+// a column. Writer rewrites the incoming text such that all cells in
+// a column have the same width; thus it effectively aligns cells. It
+// does this by adding padding where necessary.
+//
+// Formatting can be controlled via parameters:
+//
+// tabwidth the minimal with of a cell
+// padding additional padding
+// usetabs use tabs instead of blanks for padding
+// (for correct-looking results, tabwidth must correspond
+// to the tabwidth in the editor used to look at the result)
+//
+// (See alse http://nickgravgaard.com/elastictabstops/index.html)
+
+// TODO Should support UTF-8
+// TODO Should probably implement a couple of trivial customization options
+// such as arbitrary padding character, left/right alignment, and inde-
+// pendant cell and tab width.
+
+
+export type Writer struct {
+ // TODO should not export any of the fields
+ // configuration
+ writer io.Write;
+ tabwidth int;
+ padding int;
+ usetabs bool;
+
+ // current state
+ buf ByteArray; // the collected text w/o tabs and newlines
+ width int; // width of last incomplete cell
+ lines array.Array; // list of lines; each line is a list of cell widths
+ widths array.IntArray; // list of column widths - re-used during formatting
+}
+
+
+func (b *Writer) AddLine() {
+ b.lines.Push(array.NewIntArray(0));
+}
+
+
+func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer {
+ b.writer = writer;
+ b.tabwidth = tabwidth;
+ b.padding = padding;
+ b.usetabs = usetabs;
+
+ b.buf.Init(1024);
+ b.lines.Init(0);
+ b.widths.Init(0);
+ b.AddLine(); // the very first line
+
+ return b;
+}
+
+
+func (b *Writer) Line(i int) *array.IntArray {
+ return b.lines.At(i).(*array.IntArray);
+}
+
+
+func (b *Writer) LastLine() *array.IntArray {
+ return b.lines.At(b.lines.Len() - 1).(*array.IntArray);
+}
+
+
+// debugging support
+func (b *Writer) Dump() {
+ pos := 0;
+ for i := 0; i < b.lines.Len(); i++ {
+ line := b.Line(i);
+ print("(", i, ") ");
+ for j := 0; j < line.Len(); j++ {
+ w := line.At(j);
+ print("[", string(b.buf.Slice(pos, pos + w)), "]");
+ pos += w;
+ }
+ print("\n");
+ }
+ print("\n");
+}
+
+
+func (b *Writer) Write0(buf *[]byte) *os.Error {
+ n, err := b.writer.Write(buf);
+ if n != len(buf) && err == nil {
+ err = os.EIO;
+ }
+ return err;
+}
+
+
+var Tabs = &[]byte{'\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'}
+var Blanks = &[]byte{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}
+var Newline = &[]byte{'\n'}
+
+
+func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) {
+ if b.usetabs {
+ // make cell width a multiple of tabwidth
+ cellw = ((cellw + b.tabwidth - 1) / b.tabwidth) * b.tabwidth;
+ }
+
+ n := cellw - textw;
+ if n < 0 {
+ panic("internal error");
+ }
+
+ padding := Blanks;
+ if b.usetabs {
+ n = (n + b.tabwidth - 1) / b.tabwidth;
+ padding = Tabs;
+ }
+
+ for n > len(padding) {
+ err = b.Write0(padding);
+ if err != nil {
+ goto exit;
+ }
+ n -= len(padding);
+ }
+ err = b.Write0(padding[0 : n]);
+
+exit:
+ return err;
+}
+
+
+func (b *Writer) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) {
+ pos = pos0;
+ for i := line0; i < line1; i++ {
+ line := b.Line(i);
+ for j := 0; j < line.Len(); j++ {
+ w := line.At(j);
+ err = b.Write0(b.buf.a[pos : pos + w]);
+ if err != nil {
+ goto exit;
+ }
+ pos += w;
+ if j < b.widths.Len() {
+ err = b.WritePadding(w, b.widths.At(j));
+ if err != nil {
+ goto exit;
+ }
+ }
+ }
+ err = b.Write0(Newline);
+ if err != nil {
+ goto exit;
+ }
+ }
+
+exit:
+ return pos, err;
+}
+
+
+// TODO use utflen for correct formatting
+func utflen(buf *[]byte) int {
+ n := 0;
+ for i := 0; i < len(buf); i++ {
+ if buf[i]&0xC0 != 0x80 {
+ n++
+ }
+ }
+ return n
+}
+
+
+func (b *Writer) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) {
+ pos = pos0;
+ column := b.widths.Len();
+ last := line0;
+ for this := line0; this < line1; this++ {
+ line := b.Line(this);
+
+ if column < line.Len() - 1 {
+ // cell exists in this column
+ // (note that the last cell per line is ignored)
+
+ // print unprinted lines until beginning of block
+ pos, err = b.WriteLines(pos, last, this);
+ if err != nil {
+ goto exit;
+ }
+ last = this;
+
+ // column block begin
+ width := b.tabwidth; // minimal width
+ for ; this < line1; this++ {
+ line = b.Line(this);
+ if column < line.Len() - 1 {
+ // cell exists in this column => update width
+ w := line.At(column) + b.padding;
+ if w > width {
+ width = w;
+ }
+ } else {
+ break
+ }
+ }
+ // column block end
+
+ // 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);
+ pos, err = b.Format(pos, last, this);
+ b.widths.Pop();
+ last = this;
+ }
+ }
+
+ // print unprinted lines until end
+ pos, err = b.WriteLines(pos, last, line1);
+
+exit:
+ return pos, err;
+}
+
+
+func (b *Writer) Append(buf *[]byte) {
+ b.buf.Append(buf);
+ b.width += len(buf);
+}
+
+
+/* export */ func (b *Writer) Flush() *os.Error {
+ dummy, err := b.Format(0, 0, b.lines.Len());
+ // reset (even in the presence of errors)
+ b.buf.Clear();
+ b.width = 0;
+ b.lines.Init(0);
+ b.AddLine();
+ return err;
+}
+
+
+/* export */ func (b *Writer) Write(buf *[]byte) (written int, err *os.Error) {
+ i0, n := 0, len(buf);
+
+ // split text into cells
+ for i := 0; i < n; i++ {
+ if ch := buf[i]; ch == '\t' || ch == '\n' {
+ b.Append(buf[i0 : i]);
+ i0 = i + 1; // exclude ch from (next) cell
+
+ // terminate cell
+ b.LastLine().Push(b.width);
+ b.width = 0;
+
+ if ch == '\n' {
+ if b.LastLine().Len() == 1 {
+ // The last 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 {
+ return i0, err;
+ }
+ } else {
+ // We can't flush yet - just add a new line.
+ b.AddLine();
+ }
+ }
+ }
+ }
+
+ // append leftover text
+ b.Append(buf[i0 : n]);
+ return n, nil;
+}
+
+
+export func New(writer io.Write, tabwidth, padding int, usetabs bool) *Writer {
+ return new(Writer).Init(writer, tabwidth, padding, usetabs)
+}
diff --git a/src/lib/tabwriter/tabwriter_test.go b/src/lib/tabwriter/tabwriter_test.go
new file mode 100644
index 0000000000..42c443f78a
--- /dev/null
+++ b/src/lib/tabwriter/tabwriter_test.go
@@ -0,0 +1,81 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tabwriter
+
+import (
+ "os";
+ "io";
+ "tabwriter";
+ "testing";
+)
+
+
+type Buffer struct {
+ a *[]byte;
+}
+
+
+func (b *Buffer) Init(n int) {
+ b.a = new([]byte, n)[0 : 0];
+}
+
+
+func (b *Buffer) Write(buf *[]byte) (written int, err *os.Error) {
+ n := len(b.a);
+ m := len(buf);
+ if n + m <= cap(b.a) {
+ b.a = b.a[0 : n + m];
+ for i := 0; i < m; i++ {
+ b.a[n+i] = buf[i];
+ }
+ } else {
+ panicln("buffer too small", n, m, cap(b.a));
+ }
+ return len(buf), nil;
+}
+
+
+func (b *Buffer) String() string {
+ return string(b.a);
+}
+
+
+func Check(t *testing.T, tabwidth, padding int, usetabs bool, src, expected string) {
+ var b Buffer;
+ b.Init(1000);
+
+ var w tabwriter.Writer;
+ w.Init(&b, tabwidth, padding, usetabs);
+
+ io.WriteString(&w, src);
+
+ res := b.String();
+ if res != expected {
+ t.Errorf("src:\n%s\nfound:\n%s\nexpected:\n%s\n", src, res, expected)
+ }
+}
+
+
+export func Test1(t *testing.T) {
+ Check(
+ t, 8, 1, false,
+ "\n",
+ "\n"
+ );
+
+ Check(
+ t, 8, 1, false,
+ "Hello, world!\n",
+ "Hello, world!\n"
+ );
+
+ Check(
+ t, 8, 1, false,
+ "a\tb\tc\naa\tbbb\tcccc\naaa\tbbbb\n\n",
+ "a b c\n"
+ "aa bbb cccc\n"
+ "aaa bbbb\n\n"
+ );
+}