|  | // Copyright 2019 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | package common | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "context" | 
|  | "fmt" | 
|  | "io" | 
|  | "os/exec" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | // Formatter formats a writer stream. | 
|  | type Formatter struct { | 
|  | path string | 
|  | args []string | 
|  | } | 
|  |  | 
|  | const timeout = 2 * time.Minute | 
|  |  | 
|  | // NewFormatter creates a new formatter. | 
|  | // | 
|  | // The `path` needs to either | 
|  | // * Point to an executable which formats stdin and outputs it to stdout; | 
|  | // * An empty string, in which case no formatting will occur. | 
|  | func NewFormatter(path string, args ...string) Formatter { | 
|  | return Formatter{ | 
|  | path: path, | 
|  | args: args, | 
|  | } | 
|  | } | 
|  |  | 
|  | // FormatPipe formats an output stream. | 
|  | // | 
|  | // When the returned WriteCloser is closed, 'out' will also be closed. | 
|  | // This allows the caller to close the writer in a single location and received all relevant errors. | 
|  | func (f Formatter) FormatPipe(out io.WriteCloser) (io.WriteCloser, error) { | 
|  | if f.path == "" { | 
|  | return unformattedStream{normalOut: out}, nil | 
|  | } | 
|  | ctx, cancel := context.WithTimeout(context.Background(), timeout) | 
|  | cmd := exec.CommandContext(ctx, f.path, f.args...) | 
|  | cmd.Stdout = out | 
|  | errBuf := new(bytes.Buffer) | 
|  | cmd.Stderr = errBuf | 
|  | in, err := cmd.StdinPipe() | 
|  | if err != nil { | 
|  | cancel() | 
|  | return nil, err | 
|  | } | 
|  | if err := cmd.Start(); err != nil { | 
|  | cancel() | 
|  | return nil, err | 
|  | } | 
|  | return formattedStream{ | 
|  | cmd:    cmd, | 
|  | cancel: cancel, | 
|  | in:     in, | 
|  | out:    out, | 
|  | errBuf: errBuf, | 
|  | }, nil | 
|  | } | 
|  |  | 
|  | type unformattedStream struct { | 
|  | normalOut io.Writer | 
|  | } | 
|  |  | 
|  | type formattedStream struct { | 
|  | cmd    *exec.Cmd | 
|  | cancel context.CancelFunc | 
|  | in     io.WriteCloser | 
|  | out    io.WriteCloser | 
|  | errBuf *bytes.Buffer | 
|  | } | 
|  |  | 
|  | var _ = []io.WriteCloser{ | 
|  | unformattedStream{}, | 
|  | formattedStream{}, | 
|  | } | 
|  |  | 
|  | func (s unformattedStream) Write(p []byte) (int, error) { | 
|  | return s.normalOut.Write(p) | 
|  | } | 
|  |  | 
|  | func (s formattedStream) Write(p []byte) (int, error) { | 
|  | return s.in.Write(p) | 
|  | } | 
|  |  | 
|  | func (s unformattedStream) Close() error { | 
|  | // Not the responsibility of unformattedStream to close underlying stream | 
|  | // which may (or may not) be an io.WriteCloser. | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (s formattedStream) Close() error { | 
|  | defer s.cancel() | 
|  | if err := s.in.Close(); err != nil { | 
|  | return err | 
|  | } | 
|  | if err := s.cmd.Wait(); err != nil { | 
|  | if errContent := s.errBuf.Bytes(); len(errContent) != 0 { | 
|  | return fmt.Errorf("%s: %s", err, string(errContent)) | 
|  | } | 
|  | return err | 
|  | } | 
|  | return s.out.Close() | 
|  | } |