internal/validation/reader.go (87 lines of code) (raw):

package validation import ( "bufio" "os" ) // LineReader is an interface that allows to read lines from some source. type LineReader interface { // Line reads a line from the source. It returns false for the second argument // if there is no more lines to read. Line() (string, bool) // Name is name of the lines source. Name() string } // ChannelReader implements LineReader for a channel of strings source. type ChannelReader struct { in <-chan string name string } var _ LineReader = ChannelReader{} // NewChannelReader returns a new ChannelReader. func NewChannelReader(in <-chan string, name string) ChannelReader { return ChannelReader{in: in, name: name} } // Line reads a line from the channel. // Returns false if there is no more lines to read (in buffered channels) // or the channel is closed. func (c ChannelReader) Line() (string, bool) { select { case line, ok := <-c.in: if !ok { // Channel is closed return "", false } return line, true default: // Nothing more in the channel to read return "", false } } func (c ChannelReader) Name() string { return c.name } // FileCapture forwards the content of a file to a channel, // reading line by line. type FileCapture struct { *os.File done chan struct{} scanner *bufio.Scanner out chan<- string } // NewFileCapture returns a new FileCapture. func NewFileCapture(out chan<- string) *FileCapture { r := &FileCapture{ out: out, } return r } // Init initializes the FileCapture. // The caller is responsible for calling Close // after Init succeeds once they are done with the FileCapture. // If not, FileCapture might leak resources. func (r *FileCapture) Init() error { readPipe, writePipe, err := os.Pipe() if err != nil { return err } r.File = writePipe r.done = make(chan struct{}) r.scanner = bufio.NewScanner(readPipe) go func() { for r.scanner.Scan() { r.out <- r.scanner.Text() } close(r.out) close(r.done) }() return nil } // Close closes all internal resources and stops routines. func (r *FileCapture) Close() error { // Close the write pipe to signal end of input r.File.Close() // Wait for the reading goroutine to finish <-r.done if err := r.scanner.Err(); err != nil { return err } return nil } // PrinterWithStdCapture is a convenience struct that combines a Printer // and a FileCapture, allowing to capture a os.File. Useful for // capturing stderr or stdout and displaying it through the Printer. type PrinterWithStdCapture struct { Printer FileCapture } // NewPrinterWithStdCapture returns a new PrinterWithStdCapture. func NewPrinterWithStdCapture(stdName string, noColor bool) *PrinterWithStdCapture { out := make(chan string, 100) opts := []PrinterOpt{ WithExternalLogs(NewChannelReader(out, stdName)), } if noColor { opts = append(opts, WithNoColor()) } printer := NewPrinter(opts...) newStderr := NewFileCapture(out) return &PrinterWithStdCapture{ Printer: printer, FileCapture: *newStderr, } }