-
Notifications
You must be signed in to change notification settings - Fork 1.5k
pkg/lineprinter: Add a wrapper for Write -> Print #730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // Package lineprinter wraps a Print implementation to provide an io.WriteCloser. | ||
| package lineprinter | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "io" | ||
| "sync" | ||
| ) | ||
|
|
||
| // Print is a type that can hold fmt.Print and other implementations | ||
| // which match that signature. For example, you can use: | ||
| // | ||
| // trimmer := &lineprinter.Trimmer{WrappedPrint: logrus.StandardLogger().Debug} | ||
| // linePrinter := &linePrinter{Print: trimmer.Print} | ||
| // | ||
| // to connect the line printer to logrus at the debug level. | ||
| type Print func(args ...interface{}) | ||
|
|
||
| // LinePrinter is an io.WriteCloser that buffers written bytes. | ||
| // During each Write, newline-terminated lines are removed from the | ||
| // buffer and passed to Print. On Close, any content remaining in the | ||
| // buffer is also passed to Print. | ||
| // | ||
| // One use-case is connecting a subprocess's standard streams to a | ||
| // logger: | ||
| // | ||
| // linePrinter := &linePrinter{ | ||
| // Print: &Trimmer{WrappedPrint: logrus.StandardLogger().Debug}.Print, | ||
| // } | ||
| // defer linePrinter.Close() | ||
| // cmd := exec.Command(...) | ||
| // cmd.Stdout = linePrinter | ||
| // | ||
| // LinePrinter buffers the subcommand's byte stream and splits it into | ||
| // lines for the logger. Sometimes we might have a partial line | ||
| // written to the buffer. We don't want to push that partial line into | ||
| // the logs if the next read attempt will pull in the remainder of the | ||
| // line. But we do want to push that partial line into the logs if there | ||
| // will never be a next read. So LinePrinter.Write pushes any | ||
| // complete lines to the wrapped printer, and LinePrinter.Close pushes | ||
| // any remaining partial line. | ||
| type LinePrinter struct { | ||
| buf bytes.Buffer | ||
| Print Print | ||
|
|
||
| sync.Mutex | ||
| } | ||
|
|
||
| // Write writes len(p) bytes from p to an internal buffer. Then it | ||
| // retrieves any newline-terminated lines from the internal buffer and | ||
| // prints them with lp.Print. Partial lines are left in the internal | ||
| // buffer. | ||
| func (lp *LinePrinter) Write(p []byte) (int, error) { | ||
| lp.Lock() | ||
| defer lp.Unlock() | ||
| n, err := lp.buf.Write(p) | ||
| if err != nil { | ||
| return n, err | ||
| } | ||
|
|
||
| for { | ||
| line, err := lp.buf.ReadString(byte('\n')) | ||
| if err == io.EOF { | ||
| _, err = lp.buf.Write([]byte(line)) | ||
| return n, err | ||
| } else if err != nil { | ||
| return n, err | ||
| } | ||
|
|
||
| lp.Print(line) | ||
| } | ||
| } | ||
|
|
||
| // Close prints anything that remains in the buffer. | ||
| func (lp *LinePrinter) Close() error { | ||
| lp.Lock() | ||
| defer lp.Unlock() | ||
| line := lp.buf.String() | ||
| if len(line) > 0 { | ||
| lp.Print(line) | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package lineprinter | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| type printer struct { | ||
| data [][]interface{} | ||
| } | ||
|
|
||
| func (p *printer) print(args ...interface{}) { | ||
| p.data = append(p.data, args) | ||
| } | ||
|
|
||
| func TestLinePrinter(t *testing.T) { | ||
| print := &printer{} | ||
| lp := &LinePrinter{Print: print.print} | ||
| data := []byte("Hello\nWorld\nAnd more") | ||
| n, err := lp.Write(data) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| assert.Equal(t, len(data), n) | ||
| assert.Equal( | ||
| t, | ||
| [][]interface{}{ | ||
| {"Hello\n"}, | ||
| {"World\n"}, | ||
| }, | ||
| print.data, | ||
| ) | ||
| print.data = [][]interface{}{} | ||
|
|
||
| err = lp.Close() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| assert.Equal( | ||
| t, | ||
| [][]interface{}{ | ||
| {"And more"}, | ||
| }, | ||
| print.data, | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package lineprinter | ||
|
||
|
|
||
| import ( | ||
| "strings" | ||
| ) | ||
|
|
||
| // Trimmer is a Print wrapper that removes trailing newlines from the | ||
| // final argument (if it is a string argument). This is useful for | ||
| // connecting a LinePrinter to a logger whose Print-analog does not | ||
| // expect trailing newlines. | ||
| type Trimmer struct { | ||
| WrappedPrint Print | ||
| } | ||
|
|
||
| // Print removes trailing newlines from the final argument (if it is a | ||
| // string argument) and then passes the arguments through to | ||
| // WrappedPrint. | ||
| func (t *Trimmer) Print(args ...interface{}) { | ||
| if len(args) > 0 { | ||
| i := len(args) - 1 | ||
| arg, ok := args[i].(string) | ||
| if ok { | ||
| args[i] = strings.TrimRight(arg, "\n") | ||
| } | ||
| } | ||
| t.WrappedPrint(args...) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package lineprinter | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestTrimmer(t *testing.T) { | ||
| print := &printer{} | ||
| trimmer := &Trimmer{WrappedPrint: print.print} | ||
| trimmer.Print("Hello\n", "World\n") | ||
| trimmer.Print(123) | ||
| assert.Equal( | ||
| t, | ||
| [][]interface{}{ | ||
| {"Hello\n", "World"}, | ||
| {123}, | ||
| }, | ||
| print.data, | ||
| ) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.