package text import ( "bytes" "io" ) // NewColumnWriter wraps w such that only lines of length (including sep) at // most col, terminated by sep are written. This is achieved by injecting sep. // After closing with Close, if any bytes were written, sep will have been the // last bytes written. // ColumnWriter panics if col < 0, len(sep) <= 0 or col <= len(sep). // // One rule for Go text recoders is that the empty input is transformed to the // empty output. func ColumnWriter(col int, sep string, w io.Writer) *columnwriter { if col < 0 || len(sep) <= 0 || col <= len(sep) { panic("unsatisfiable") } return &columnwriter{col: col, sep: sep, w: w,} } type columnwriter struct { col int sep string line bytes.Buffer pending bytes.Buffer w io.Writer } func (cw *columnwriter) Write(p []byte) (int, error) { var N int for { err := cw.flushpending(false) if err != nil { return N, err } if len(p) <= 0 { return N, nil } k := min(len(p), cw.linecap() - cw.line.Len()) cw.line.Write(p[:k]) p = p[k:] N += k } } func (cw *columnwriter) Close() error { return cw.flushpending(true) } func (cw *columnwriter) linecap() int { return cw.col + len(cw.sep) - 1 } func (cw *columnwriter) flushlinepartially(eof bool) { for cw.line.Len() > 0 { k := bytes.Index(cw.line.Bytes(), []byte(cw.sep)) if k != -1 && k <= cw.col - len(cw.sep) { cw.pending.Write(cw.line.Next(k + len(cw.sep))) continue } if !eof && cw.line.Len() < cw.linecap() { break } cw.pending.Write(cw.line.Next(cw.col - len(cw.sep))) if bytes.HasPrefix(cw.line.Bytes(), []byte(cw.sep)) { cw.line.Next(len(cw.sep)) } cw.pending.Write([]byte(cw.sep)) } } func (cw *columnwriter) flushpending(eof bool) error { cw.flushlinepartially(eof) // NOTE this might not terminate (depending on cw.w) for { if cw.pending.Len() == 0 { return nil } n, err := cw.w.Write(cw.pending.Bytes()) cw.pending.Next(n) if err != nil { return err } } }