textutil: Add PrefixLineWriter

PrefixLineWriter returns a io.Writer wrapper that adds a prefix
to each EOL it encounters.  It buffers, and Flush must be called.

Also added the WriteFlusher and WriteFlushCloser interfaces.

Change-Id: I6a3335c682b505f386c890c50e89d39ba174e526
diff --git a/textutil/.api b/textutil/.api
index 278023d..fbf83f1 100644
--- a/textutil/.api
+++ b/textutil/.api
@@ -5,6 +5,7 @@
 pkg textutil, func FlushRuneChunk(RuneChunkDecoder, func(rune) error) error
 pkg textutil, func NewLineWriter(io.Writer, int, RuneChunkDecoder, RuneEncoder) *LineWriter
 pkg textutil, func NewUTF8LineWriter(io.Writer, int) *LineWriter
+pkg textutil, func PrefixLineWriter(io.Writer, string) WriteFlushCloser
 pkg textutil, func PrefixWriter(io.Writer, string) io.Writer
 pkg textutil, func TerminalSize() (int, int, error)
 pkg textutil, func WriteRuneChunk(RuneChunkDecoder, func(rune) error, []byte) (int, error)
@@ -26,3 +27,10 @@
 pkg textutil, type RuneEncoder interface, Encode(rune, *bytes.Buffer)
 pkg textutil, type UTF8ChunkDecoder struct
 pkg textutil, type UTF8Encoder struct
+pkg textutil, type WriteFlushCloser interface { Close, Flush, Write }
+pkg textutil, type WriteFlushCloser interface, Close() error
+pkg textutil, type WriteFlushCloser interface, Flush() error
+pkg textutil, type WriteFlushCloser interface, Write([]byte) (int, error)
+pkg textutil, type WriteFlusher interface { Flush, Write }
+pkg textutil, type WriteFlusher interface, Flush() error
+pkg textutil, type WriteFlusher interface, Write([]byte) (int, error)
diff --git a/textutil/writer.go b/textutil/writer.go
index 9bbec00..8b7e930 100644
--- a/textutil/writer.go
+++ b/textutil/writer.go
@@ -7,8 +7,29 @@
 import (
 	"bytes"
 	"io"
+	"unicode/utf8"
 )
 
+// WriteFlusher is the interface that groups the basic Write and Flush methods.
+//
+// Flush is typically provided when Write calls perform buffering; Flush
+// immediately outputs the buffered data.  Flush must be called after the last
+// call to Write, and may be called an arbitrary number of times before the last
+// Write.
+//
+// If the type is also a Closer, Close implies a Flush call.
+type WriteFlusher interface {
+	io.Writer
+	Flush() error
+}
+
+// WriteFlushCloser is the interface that groups the basic Write, Flush and
+// Close methods.
+type WriteFlushCloser interface {
+	WriteFlusher
+	io.Closer
+}
+
 // PrefixWriter returns an io.Writer that wraps w, where the prefix is written
 // out immediately before the first non-empty Write call.
 func PrefixWriter(w io.Writer, prefix string) io.Writer {
@@ -28,6 +49,77 @@
 	return w.w.Write(data)
 }
 
+// PrefixLineWriter returns a WriteFlushCloser that wraps w.  Any occurrence of
+// EOL (\f, \n, \r, \v, LineSeparator or ParagraphSeparator) causes the
+// preceeding line to be written to w, with the given prefix.  Data without EOL
+// is buffered until the next EOL, or Flush or Close call.  A single Write call
+// may result in zero or more Write calls on the underlying writer.
+func PrefixLineWriter(w io.Writer, prefix string) WriteFlushCloser {
+	return &prefixLineWriter{w, []byte(prefix), nil}
+}
+
+type prefixLineWriter struct {
+	w      io.Writer
+	prefix []byte
+	buf    []byte
+}
+
+const eolRunesAsString = "\f\n\r\v" + string(LineSeparator) + string(ParagraphSeparator)
+
+func (w *prefixLineWriter) Write(data []byte) (int, error) {
+	totalLen := len(data)
+	for len(data) > 0 {
+		index := bytes.IndexAny(data, eolRunesAsString)
+		if index == -1 {
+			// No EOL: buffer remaining data.
+			// TODO(toddw): Flush at a max size, to avoid unbounded growth?
+			w.buf = append(w.buf, data...)
+			return totalLen, nil
+		}
+		// Saw EOL: write prefix, buffer, and data including EOL.
+		if _, err := w.w.Write(w.prefix); err != nil {
+			return totalLen - len(data), err
+		}
+		if _, err := w.w.Write(w.buf); err != nil {
+			return totalLen - len(data), err
+		}
+		w.buf = w.buf[:0]
+		_, eolSize := utf8.DecodeRune(data[index:])
+		n, err := w.w.Write(data[:index+eolSize])
+		data = data[n:]
+		if err != nil {
+			return totalLen - len(data), err
+		}
+	}
+	return totalLen, nil
+}
+
+func (w *prefixLineWriter) Flush() error {
+	if len(w.buf) > 0 {
+		if _, err := w.w.Write(w.prefix); err != nil {
+			return err
+		}
+		if _, err := w.w.Write(w.buf); err != nil {
+			return err
+		}
+		w.buf = w.buf[:0]
+	}
+	if f, ok := w.w.(WriteFlusher); ok {
+		return f.Flush()
+	}
+	return nil
+}
+
+func (w *prefixLineWriter) Close() error {
+	firstErr := w.Flush()
+	if c, ok := w.w.(io.Closer); ok {
+		if err := c.Close(); firstErr == nil {
+			firstErr = err
+		}
+	}
+	return firstErr
+}
+
 // ByteReplaceWriter returns an io.Writer that wraps w, where all occurrences of
 // the old byte are replaced with the new string on Write calls.
 func ByteReplaceWriter(w io.Writer, old byte, new string) io.Writer {
diff --git a/textutil/writer_test.go b/textutil/writer_test.go
index 3db70e9..cb8f705 100644
--- a/textutil/writer_test.go
+++ b/textutil/writer_test.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"fmt"
+	"strings"
 	"testing"
 )
 
@@ -22,18 +23,39 @@
 		{"", []string{"a", ""}, "a"},
 		{"", []string{"", "a"}, "a"},
 		{"", []string{"a", "b"}, "ab"},
+		{"", []string{"ab"}, "ab"},
+		{"", []string{"\n"}, "\n"},
+		{"", []string{"\n", ""}, "\n"},
+		{"", []string{"", "\n"}, "\n"},
+		{"", []string{"a", "\n"}, "a\n"},
+		{"", []string{"a\n"}, "a\n"},
+		{"", []string{"\n", "a"}, "\na"},
+		{"", []string{"\na"}, "\na"},
+		{"", []string{"a\nb\nc"}, "a\nb\nc"},
 		{"PRE", nil, ""},
 		{"PRE", []string{""}, ""},
 		{"PRE", []string{"a"}, "PREa"},
 		{"PRE", []string{"a", ""}, "PREa"},
 		{"PRE", []string{"", "a"}, "PREa"},
 		{"PRE", []string{"a", "b"}, "PREab"},
+		{"PRE", []string{"ab"}, "PREab"},
+		{"PRE", []string{"\n"}, "PRE\n"},
+		{"PRE", []string{"\n", ""}, "PRE\n"},
+		{"PRE", []string{"", "\n"}, "PRE\n"},
+		{"PRE", []string{"a", "\n"}, "PREa\n"},
+		{"PRE", []string{"a\n"}, "PREa\n"},
+		{"PRE", []string{"\n", "a"}, "PRE\na"},
+		{"PRE", []string{"\na"}, "PRE\na"},
+		{"PRE", []string{"a", "\n", "b", "\n", "c"}, "PREa\nb\nc"},
+		{"PRE", []string{"a\nb\nc"}, "PREa\nb\nc"},
+		{"PRE", []string{"a\nb\nc\n"}, "PREa\nb\nc\n"},
 	}
 	for _, test := range tests {
 		var buf bytes.Buffer
 		w := PrefixWriter(&buf, test.Prefix)
+		name := fmt.Sprintf("(%q, %q)", test.Want, test.Writes)
 		for _, write := range test.Writes {
-			name := fmt.Sprintf("(%v, %v)", test.Want, write)
+			name := name + fmt.Sprintf("(%q)", write)
 			n, err := w.Write([]byte(write))
 			if got, want := n, len(write); got != want {
 				t.Errorf("%s got len %d, want %d", name, got, want)
@@ -43,11 +65,99 @@
 			}
 		}
 		if got, want := buf.String(), test.Want; got != want {
-			t.Errorf("got %v, want %v", got, want)
+			t.Errorf("%s got %q, want %q", name, got, want)
 		}
 	}
 }
 
+func TestPrefixLineWriter(t *testing.T) {
+	tests := []struct {
+		Prefix string
+		Writes []string
+		Want   string
+	}{
+		{"", nil, ""},
+		{"", []string{""}, ""},
+		{"", []string{"a"}, "a"},
+		{"", []string{"a", ""}, "a"},
+		{"", []string{"", "a"}, "a"},
+		{"", []string{"a", "b"}, "ab"},
+		{"", []string{"ab"}, "ab"},
+		{"", []string{"\n"}, "\n"},
+		{"", []string{"\n", ""}, "\n"},
+		{"", []string{"", "\n"}, "\n"},
+		{"", []string{"a", "\n"}, "a\n"},
+		{"", []string{"a\n"}, "a\n"},
+		{"", []string{"\n", "a"}, "\na"},
+		{"", []string{"\na"}, "\na"},
+		{"", []string{"a\nb\nc"}, "a\nb\nc"},
+		{"PRE", nil, ""},
+		{"PRE", []string{""}, ""},
+		{"PRE", []string{"a"}, "PREa"},
+		{"PRE", []string{"a", ""}, "PREa"},
+		{"PRE", []string{"", "a"}, "PREa"},
+		{"PRE", []string{"a", "b"}, "PREab"},
+		{"PRE", []string{"ab"}, "PREab"},
+		{"PRE", []string{"\n"}, "PRE\n"},
+		{"PRE", []string{"\n", ""}, "PRE\n"},
+		{"PRE", []string{"", "\n"}, "PRE\n"},
+		{"PRE", []string{"a", "\n"}, "PREa\n"},
+		{"PRE", []string{"a\n"}, "PREa\n"},
+		{"PRE", []string{"\n", "a"}, "PRE\nPREa"},
+		{"PRE", []string{"\na"}, "PRE\nPREa"},
+		{"PRE", []string{"a", "\n", "b", "\n", "c"}, "PREa\nPREb\nPREc"},
+		{"PRE", []string{"a\nb\nc"}, "PREa\nPREb\nPREc"},
+		{"PRE", []string{"a\nb\nc\n"}, "PREa\nPREb\nPREc\n"},
+	}
+	for _, test := range tests {
+		for _, eol := range eolRunesAsString {
+			// Replace \n in Want and Writes with the test eol rune.
+			want := strings.Replace(test.Want, "\n", string(eol), -1)
+			var writes []string
+			for _, write := range test.Writes {
+				writes = append(writes, strings.Replace(write, "\n", string(eol), -1))
+			}
+			// Run the actual tests.
+			var buf bytes.Buffer
+			w := PrefixLineWriter(&buf, test.Prefix)
+			name := fmt.Sprintf("(%q, %q)", want, writes)
+			for _, write := range writes {
+				name := name + fmt.Sprintf("(%q)", write)
+				n, err := w.Write([]byte(write))
+				if got, want := n, len(write); got != want {
+					t.Errorf("%s got len %d, want %d", name, got, want)
+				}
+				if err != nil {
+					t.Errorf("%s got error: %v", name, err)
+				}
+			}
+			if err := w.Flush(); err != nil {
+				t.Errorf("%s Flush got error: %v", name, err)
+			}
+			if got, want := buf.String(), want; got != want {
+				t.Errorf("%s got %q, want %q", name, got, want)
+			}
+		}
+	}
+}
+
+type fakeWriteFlushCloser struct{ flushed, closed bool }
+
+func (f *fakeWriteFlushCloser) Write(p []byte) (int, error) { return len(p), nil }
+func (f *fakeWriteFlushCloser) Flush() error                { f.flushed = true; return nil }
+func (f *fakeWriteFlushCloser) Close() error                { f.closed = true; return nil }
+
+func TestPrefixLineWriterCloseFlush(t *testing.T) {
+	var fake fakeWriteFlushCloser
+	w := PrefixLineWriter(&fake, "")
+	if w.Flush(); !fake.flushed {
+		t.Errorf("Flush not propagated")
+	}
+	if w.Close(); !fake.closed {
+		t.Errorf("Close not propagated")
+	}
+}
+
 func TestByteReplaceWriter(t *testing.T) {
 	tests := []struct {
 		Old    byte
@@ -77,8 +187,9 @@
 	for _, test := range tests {
 		var buf bytes.Buffer
 		w := ByteReplaceWriter(&buf, test.Old, test.New)
+		name := fmt.Sprintf("(%q, %q, %q, %q)", test.Old, test.New, test.Want, test.Writes)
 		for _, write := range test.Writes {
-			name := fmt.Sprintf("(%v, %v, %v, %v)", test.Old, test.New, test.Want, write)
+			name := name + fmt.Sprintf("(%q)", write)
 			n, err := w.Write([]byte(write))
 			if got, want := n, len(write); got != want {
 				t.Errorf("%s got len %d, want %d", name, got, want)
@@ -88,7 +199,7 @@
 			}
 		}
 		if got, want := buf.String(), test.Want; got != want {
-			t.Errorf("got %v, want %v", got, want)
+			t.Errorf("%s got %q, want %q", name, got, want)
 		}
 	}
 }