From 130e6f42f1b993c1764dc1c346c9af222e59e1d2 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 21 Nov 2008 09:35:49 -0800 Subject: [PATCH] - implemented arbitrary padding char for tabwriter - implemented right-to-left alignment (numerical results) - better comments and error handling - added more tests - updated dependent files R=r DELTA=232 (175 added, 11 deleted, 46 changed) OCL=19761 CL=19780 --- src/lib/tabwriter/tabwriter.go | 107 ++++++++++++-------- src/lib/tabwriter/tabwriter_test.go | 147 ++++++++++++++++++++++++++-- usr/gri/pretty/printer.go | 6 +- usr/gri/pretty/untab.go | 6 +- 4 files changed, 215 insertions(+), 51 deletions(-) diff --git a/src/lib/tabwriter/tabwriter.go b/src/lib/tabwriter/tabwriter.go index 3eb0ba195b..2615186e7a 100644 --- a/src/lib/tabwriter/tabwriter.go +++ b/src/lib/tabwriter/tabwriter.go @@ -63,34 +63,38 @@ func (b *ByteArray) Append(s *[]byte) { // ---------------------------------------------------------------------------- // 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 +// lines of tab-terminated "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: +// Note that any text at the end of a line that is not tab-terminated +// is not a cell and does not enforce alignment of cells in adjacent +// rows. To make it a cell it needs to be tab-terminated. (For more +// information see http://nickgravgaard.com/elastictabstops/index.html) // -// 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) +// Formatting can be controlled via parameters: // -// (See alse http://nickgravgaard.com/elastictabstops/index.html) +// cellwidth minimal cell width +// padding additional cell padding +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwith, +// and cells are left-aligned independent of align_left +// (for correct-looking results, cellwidth must correspond +// to the tabwidth in the editor used to look at the result) -// 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. +// TODO Should support UTF-8 (requires more complicated width bookkeeping) export type Writer struct { // TODO should not export any of the fields // configuration writer io.Write; - tabwidth int; + cellwidth int; padding int; - usetabs bool; + padbytes [8]byte; + align_left bool; // current state buf ByteArray; // the collected text w/o tabs and newlines @@ -105,11 +109,20 @@ func (b *Writer) AddLine() { } -func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer { +func (b *Writer) Init(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer { + if cellwidth < 0 { + panic("negative cellwidth"); + } + if padding < 0 { + panic("negative padding"); + } b.writer = writer; - b.tabwidth = tabwidth; + b.cellwidth = cellwidth; b.padding = padding; - b.usetabs = usetabs; + for i := len(b.padbytes) - 1; i >= 0; i-- { + b.padbytes[i] = padchar; + } + b.align_left = align_left || padchar == '\t'; // tab enforces left-alignment b.buf.Init(1024); b.lines.Init(0); @@ -156,15 +169,12 @@ func (b *Writer) Write0(buf *[]byte) *os.Error { } -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; + if b.padbytes[0] == '\t' { + // make cell width a multiple of cellwidth + cellw = ((cellw + b.cellwidth - 1) / b.cellwidth) * b.cellwidth; } n := cellw - textw; @@ -172,20 +182,18 @@ func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) { panic("internal error"); } - padding := Blanks; - if b.usetabs { - n = (n + b.tabwidth - 1) / b.tabwidth; - padding = Tabs; + if b.padbytes[0] == '\t' { + n = (n + b.cellwidth - 1) / b.cellwidth; } - for n > len(padding) { - err = b.Write0(padding); + for n > len(b.padbytes) { + err = b.Write0(&b.padbytes); if err != nil { goto exit; } - n -= len(padding); + n -= len(b.padbytes); } - err = b.Write0(padding[0 : n]); + err = b.Write0((&b.padbytes)[0 : n]); // BUG 6g should not require ()'s exit: return err; @@ -198,16 +206,33 @@ func (b *Writer) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) 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 b.align_left { + 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; + } + } + + } else { // align right + + if j < b.widths.Len() { + err = b.WritePadding(w, b.widths.At(j)); + if err != nil { + goto exit; + } + } + err = b.Write0(b.buf.a[pos : pos + w]); if err != nil { goto exit; } + pos += w; } } err = b.Write0(Newline); @@ -252,7 +277,7 @@ func (b *Writer) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) { last = this; // column block begin - width := b.tabwidth; // minimal width + width := b.cellwidth; // minimal width for ; this < line1; this++ { line = b.Line(this); if column < line.Len() - 1 { @@ -338,6 +363,6 @@ func (b *Writer) Append(buf *[]byte) { } -export func New(writer io.Write, tabwidth, padding int, usetabs bool) *Writer { - return new(Writer).Init(writer, tabwidth, padding, usetabs) +export func New(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer { + return new(Writer).Init(writer, cellwidth, padding, padchar, align_left) } diff --git a/src/lib/tabwriter/tabwriter_test.go b/src/lib/tabwriter/tabwriter_test.go index 42c443f78a..980cc73369 100644 --- a/src/lib/tabwriter/tabwriter_test.go +++ b/src/lib/tabwriter/tabwriter_test.go @@ -42,40 +42,171 @@ func (b *Buffer) String() string { } -func Check(t *testing.T, tabwidth, padding int, usetabs bool, src, expected string) { +func Check(t *testing.T, tabwidth, padding int, padchar byte, align_left bool, src, expected string) { var b Buffer; b.Init(1000); var w tabwriter.Writer; - w.Init(&b, tabwidth, padding, usetabs); + w.Init(&b, tabwidth, padding, padchar, align_left); 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) + t.Errorf("--- src:\n%s\n--- found:\n%s\n--- expected:\n%s\n", src, res, expected) } } export func Test1(t *testing.T) { Check( - t, 8, 1, false, + t, 8, 1, ' ', true, "\n", "\n" ); Check( - t, 8, 1, false, + t, 8, 1, '*', true, "Hello, world!\n", "Hello, world!\n" ); Check( - t, 8, 1, false, - "a\tb\tc\naa\tbbb\tcccc\naaa\tbbbb\n\n", + t, 0, 0, '.', true, + "1\t2\t3\t4\n" + "11\t222\t3333\t44444\n\n", + + "1.2..3...4\n" + "11222333344444\n\n" + ); + + Check( + t, 5, 0, '.', true, + "1\t2\t3\t4\n\n", + "1....2....3....4\n\n" + ); + + Check( + t, 5, 0, '.', true, + "1\t2\t3\t4\t\n\n", + "1....2....3....4....\n\n" + ); + + Check( + t, 8, 1, ' ', true, + "a\tb\tc\n" + "aa\tbbb\tcccc\tddddd\n" + "aaa\tbbbb\n\n", + "a b c\n" - "aa bbb cccc\n" + "aa bbb cccc ddddd\n" "aaa bbbb\n\n" ); + + Check( + t, 8, 1, ' ', false, + "a\tb\tc\t\n" + "aa\tbbb\tcccc\tddddd\t\n" + "aaa\tbbbb\t\n\n", + + " a b c\n" + " aa bbb cccc ddddd\n" + " aaa bbbb\n\n" + ); + + Check( + t, 2, 0, ' ', true, + "a\tb\tc\n" + "aa\tbbb\tcccc\n" + "aaa\tbbbb\n\n", + + "a b c\n" + "aa bbbcccc\n" + "aaabbbb\n\n" + ); + + Check( + t, 8, 1, '_', true, + "a\tb\tc\n" + "aa\tbbb\tcccc\n" + "aaa\tbbbb\n\n", + + "a_______b_______c\n" + "aa______bbb_____cccc\n" + "aaa_____bbbb\n\n" + ); + + Check( + t, 4, 1, '-', true, + "4444\t333\t22\t1\t333\n" + "999999999\t22\n" + "7\t22\n" + "\t\t\t88888888\n" + "\n" + "666666\t666666\t666666\t4444\n" + "1\t1\t999999999\t0000000000\n\n", + + "4444------333-22--1---333\n" + "999999999-22\n" + "7---------22\n" + "------------------88888888\n" + "\n" + "666666-666666-666666----4444\n" + "1------1------999999999-0000000000\n\n" + ); + + Check( + t, 4, 3, '.', true, + "4444\t333\t22\t1\t333\n" + "999999999\t22\n" + "7\t22\n" + "\t\t\t88888888\n" + "\n" + "666666\t666666\t666666\t4444\n" + "1\t1\t999999999\t0000000000\n\n", + + "4444........333...22...1...333\n" + "999999999...22\n" + "7...........22\n" + "....................88888888\n" + "\n" + "666666...666666...666666......4444\n" + "1........1........999999999...0000000000\n\n" + ); + + Check( + t, 8, 1, '\t', true, + "4444\t333\t22\t1\t333\n" + "999999999\t22\n" + "7\t22\n" + "\t\t\t88888888\n" + "\n" + "666666\t666666\t666666\t4444\n" + "1\t1\t999999999\t0000000000\n\n", + + "4444\t\t333\t22\t1\t333\n" + "999999999\t22\n" + "7\t\t22\n" + "\t\t\t\t88888888\n" + "\n" + "666666\t666666\t666666\t\t4444\n" + "1\t1\t999999999\t0000000000\n\n" + ); + + Check( + t, 4, 2, ' ', false, + ".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\t0.0\t332.0\t8908.0\t\n" + ".0\t-.3\t456.4\t22.1\t\n" + ".0\t1.2\t44.4\t-13.3\t\n\n", + + " .0 .3 2.4 -5.1\n" + " 23.0 12345678.9 2.4 -989.4\n" + " 5.1 12.0 2.4 -7.0\n" + " .0 0.0 332.0 8908.0\n" + " .0 -.3 456.4 22.1\n" + " .0 1.2 44.4 -13.3\n\n" + ); } diff --git a/usr/gri/pretty/printer.go b/usr/gri/pretty/printer.go index cefabb66f9..6bb755b4b7 100644 --- a/usr/gri/pretty/printer.go +++ b/usr/gri/pretty/printer.go @@ -604,7 +604,11 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) { func (P *Printer) Program(p *AST.Program) { // TODO should initialize all fields? - P.writer = TabWriter.New(OS.Stdout, int(tabwidth.IVal()), 1, usetabs.BVal()); + padchar := byte(' '); + if usetabs.BVal() { + padchar = '\t'; + } + P.writer = TabWriter.New(OS.Stdout, int(tabwidth.IVal()), 1, padchar, true); P.clist = p.comments; P.cindex = 0; diff --git a/usr/gri/pretty/untab.go b/usr/gri/pretty/untab.go index cd5981b539..af4814c1de 100644 --- a/usr/gri/pretty/untab.go +++ b/usr/gri/pretty/untab.go @@ -36,7 +36,11 @@ func Untab(name string, src *os.FD, dst *tabwriter.Writer) { func main() { flag.Parse(); - dst := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, usetabs.BVal()); + padchar := byte(' '); + if usetabs.BVal() { + padchar = '\t'; + } + dst := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true); if flag.NArg() > 0 { for i := 0; i < flag.NArg(); i++ { name := flag.Arg(i); -- 2.48.1