| // Copyright 2020 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| use std::error; | |
| use std::fmt; | |
| use std::io::{self, prelude::*}; | |
| /// LineOutput tells process_output what to do when a line is processed. | |
| /// If a Message is returned, it will be written to write_end, if | |
| /// Skip is returned nothing will be printed and execution continues, | |
| /// if Terminate is returned, process_output returns immediately. | |
| /// Terminate is used to stop processing when we see an emit metadata | |
| /// message. | |
| #[derive(Debug)] | |
| pub(crate) enum LineOutput { | |
| Message(String), | |
| Skip, | |
| Terminate, | |
| } | |
| #[derive(Debug)] | |
| pub(crate) enum ProcessError { | |
| IO(io::Error), | |
| Process(String), | |
| } | |
| impl fmt::Display for ProcessError { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| Self::IO(e) => write!(f, "{}", e), | |
| Self::Process(p) => write!(f, "{}", p), | |
| } | |
| } | |
| } | |
| impl error::Error for ProcessError {} | |
| impl From<io::Error> for ProcessError { | |
| fn from(err: io::Error) -> Self { | |
| Self::IO(err) | |
| } | |
| } | |
| impl From<String> for ProcessError { | |
| fn from(s: String) -> Self { | |
| Self::Process(s) | |
| } | |
| } | |
| pub(crate) type ProcessResult = Result<(), ProcessError>; | |
| /// If this is Err we assume there were issues processing the line. | |
| /// We will print the error returned and all following lines without | |
| /// any more processing. | |
| pub(crate) type LineResult = Result<LineOutput, String>; | |
| /// process_output reads lines from read_end and invokes process_line on each. | |
| /// Depending on the result of process_line, the modified message may be written | |
| /// to write_end. | |
| pub(crate) fn process_output<F>( | |
| read_end: &mut dyn Read, | |
| output_write_end: &mut dyn Write, | |
| opt_file_write_end: Option<&mut std::fs::File>, | |
| mut process_line: F, | |
| ) -> ProcessResult | |
| where | |
| F: FnMut(String) -> LineResult, | |
| { | |
| let mut reader = io::BufReader::new(read_end); | |
| let mut output_writer = io::LineWriter::new(output_write_end); | |
| let mut file_writer = opt_file_write_end.map(io::LineWriter::new); | |
| // If there was an error parsing a line failed_on contains the offending line | |
| // and the error message. | |
| let mut failed_on: Option<(String, String)> = None; | |
| loop { | |
| let mut line = String::new(); | |
| let read_bytes = reader.read_line(&mut line)?; | |
| if read_bytes == 0 { | |
| break; | |
| } | |
| if let Some(ref mut file) = file_writer { | |
| file.write_all(line.as_bytes())? | |
| } | |
| match process_line(line.clone()) { | |
| Ok(LineOutput::Message(to_write)) => output_writer.write_all(to_write.as_bytes())?, | |
| Ok(LineOutput::Skip) => {} | |
| Ok(LineOutput::Terminate) => return Ok(()), | |
| Err(msg) => { | |
| failed_on = Some((line, msg)); | |
| break; | |
| } | |
| }; | |
| } | |
| // If we encountered an error processing a line we want to flush the rest of | |
| // reader into writer and return the error. | |
| if let Some((line, msg)) = failed_on { | |
| output_writer.write_all(line.as_bytes())?; | |
| io::copy(&mut reader, &mut output_writer)?; | |
| return Err(ProcessError::Process(msg)); | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use super::*; | |
| #[test] | |
| fn test_json_parsing_error() { | |
| let mut input = io::Cursor::new(b"ok text\nsome more\nerror text"); | |
| let mut output: Vec<u8> = vec![]; | |
| let result = process_output(&mut input, &mut output, None, move |line| { | |
| if line == "ok text\n" { | |
| Ok(LineOutput::Skip) | |
| } else { | |
| Err("error parsing output".to_owned()) | |
| } | |
| }); | |
| assert!(result.is_err()); | |
| assert_eq!(&output, b"some more\nerror text"); | |
| } | |
| } |