From da279cf3ffa12b60e05d33de1212fc85ab5f4d1a Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Sat, 8 Jul 2017 18:12:29 +0100 Subject: [PATCH] Implement PrefixWriter PrefixWriter is a passthrough io.Writer that prepends a prefix to the beginning of each line. --- src/gopheros/kernel/hal/hal.go | 10 +- src/gopheros/kernel/kfmt/prefix_writer.go | 57 ++++++++++++ .../kernel/kfmt/prefix_writer_test.go | 92 +++++++++++++++++++ 3 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 src/gopheros/kernel/kfmt/prefix_writer.go create mode 100644 src/gopheros/kernel/kfmt/prefix_writer_test.go diff --git a/src/gopheros/kernel/hal/hal.go b/src/gopheros/kernel/hal/hal.go index 3f5d06f..377df18 100644 --- a/src/gopheros/kernel/hal/hal.go +++ b/src/gopheros/kernel/hal/hal.go @@ -51,16 +51,18 @@ func probe(hwProbeFns []device.ProbeFn) []device.Driver { continue } + strBuf.Reset() major, minor, patch := drv.DriverVersion() + kfmt.Fprintf(&strBuf, "[hal] %s(%d.%d.%d): ", drv.DriverName(), major, minor, patch) + w.prefix = strBuf.Bytes() - kfmt.Printf("[hal] %s(%d.%d.%d): ", drv.DriverName(), major, minor, patch) - if err := drv.DriverInit(); err != nil { - kfmt.Printf("init failed: %s\n", err.Message) + if err := drv.DriverInit(&w); err != nil { + kfmt.Fprintf(&w, "init failed: %s\n", err.Message) continue } + kfmt.Fprintf(&w, "initialized\n") drivers = append(drivers, drv) - kfmt.Printf("initialized\n") } return drivers diff --git a/src/gopheros/kernel/kfmt/prefix_writer.go b/src/gopheros/kernel/kfmt/prefix_writer.go new file mode 100644 index 0000000..b326dbd --- /dev/null +++ b/src/gopheros/kernel/kfmt/prefix_writer.go @@ -0,0 +1,57 @@ +package kfmt + +import "io" + +// PrefixWriter is an io.Writer that wraps another io.Writer and injects a +// prefix at the beginning of each line. +type PrefixWriter struct { + // A writer where all writes get sent to. + Sink io.Writer + + // The prefix injected at the beginning of each line. + Prefix []byte + + bytesAfterPrefix int +} + +// Write writes len(p) bytes from p to the underlying data stream and returns +// back the number of bytes written. The PrefixWriter keeps track of the +// beginning of new lines and injects the configured prefix at each new line. +// The injected prefix is not included in the number of written bytes returned +// by this method. +func (w *PrefixWriter) Write(p []byte) (int, error) { + var ( + written int + startIndex, curIndex int + ) + + if w.bytesAfterPrefix == 0 && len(p) != 0 { + w.Sink.Write(w.Prefix) + } + + for ; curIndex < len(p); curIndex++ { + if p[curIndex] == '\n' { + n, err := w.Sink.Write(p[startIndex : curIndex+1]) + if curIndex+1 != len(p) { + w.Sink.Write(w.Prefix) + } + written += n + if err != nil { + return written, err + } + w.bytesAfterPrefix = 0 + startIndex = curIndex + 1 + } + } + + if startIndex < curIndex { + n, err := w.Sink.Write(p[startIndex:curIndex]) + written += n + w.bytesAfterPrefix = n + if err != nil { + return written, err + } + } + + return written, nil +} diff --git a/src/gopheros/kernel/kfmt/prefix_writer_test.go b/src/gopheros/kernel/kfmt/prefix_writer_test.go new file mode 100644 index 0000000..0a005ab --- /dev/null +++ b/src/gopheros/kernel/kfmt/prefix_writer_test.go @@ -0,0 +1,92 @@ +package kfmt + +import ( + "bytes" + "errors" + "testing" +) + +func TestPrefixWriter(t *testing.T) { + specs := []struct { + input string + exp string + }{ + { + "", + "", + }, + { + "\n", + "prefix: \n", + }, + { + "no line break anywhere", + "prefix: no line break anywhere", + }, + { + "line feed at the end\n", + "prefix: line feed at the end\n", + }, + { + "\nthe big brown\nfog jumped\nover the lazy\ndog", + "prefix: \nprefix: the big brown\nprefix: fog jumped\nprefix: over the lazy\nprefix: dog", + }, + } + + var ( + buf bytes.Buffer + w = PrefixWriter{ + Sink: &buf, + Prefix: []byte("prefix: "), + } + ) + + for specIndex, spec := range specs { + buf.Reset() + w.bytesAfterPrefix = 0 + + wrote, err := w.Write([]byte(spec.input)) + if err != nil { + t.Errorf("[spec %d] unexpected error: %v", specIndex, err) + } + + if expLen := len(spec.input); expLen != wrote { + t.Errorf("[spec %d] expected writer to write %d bytes; wrote %d", specIndex, expLen, wrote) + } + + if got := buf.String(); got != spec.exp { + t.Errorf("[spec %d] expected output:\n%q\ngot:\n%q", specIndex, spec.exp, got) + } + } +} + +func TestPrefixWriterErrors(t *testing.T) { + specs := []string{ + "no line break anywhere", + "\nthe big brown\nfog jumped\nover the lazy\ndog", + } + + var ( + expErr = errors.New("write failed") + w = PrefixWriter{ + Sink: writerThatAlwaysErrors{expErr}, + Prefix: []byte("prefix: "), + } + ) + + for specIndex, spec := range specs { + w.bytesAfterPrefix = 0 + _, err := w.Write([]byte(spec)) + if err != expErr { + t.Errorf("[spec %d] expected error: %v; got %v", specIndex, expErr, err) + } + } +} + +type writerThatAlwaysErrors struct { + err error +} + +func (w writerThatAlwaysErrors) Write(_ []byte) (int, error) { + return 0, w.err +}