diff --git a/jaq-fmts/src/write/mod.rs b/jaq-fmts/src/write/mod.rs index d2e38614b..36f20eaa5 100644 --- a/jaq-fmts/src/write/mod.rs +++ b/jaq-fmts/src/write/mod.rs @@ -33,8 +33,7 @@ pub struct Writer { } /// Buffer writes if stdout is terminal, else just lock stdout. -pub fn with_stdout(f: impl FnOnce(&mut dyn Write) -> T) -> T { - let stdout = io::stdout(); +pub fn with_stdout(stdout: &mut io::Stdout, f: impl FnOnce(&mut dyn Write) -> T) -> T { if stdout.is_terminal() { f(&mut stdout.lock()) } else { diff --git a/jaq/src/funs.rs b/jaq/src/funs.rs index d094c770d..77803a7c6 100644 --- a/jaq/src/funs.rs +++ b/jaq/src/funs.rs @@ -36,7 +36,9 @@ fn eval(runner: &Runner, code: String, input: Val) -> Result<(), Error> { let ctx = Vars::new(ctx); let inputs = core::iter::once(Ok(input)); let writer = &runner.writer; - with_stdout(|out| run(runner, &filter, ctx, inputs, |v| write(out, writer, &v)))?; + with_stdout(&mut std::io::stdout(), |out| { + run(runner, &filter, ctx, inputs, |v| write(out, writer, &v)) + })?; Ok(()) } diff --git a/jaq/src/main.rs b/jaq/src/main.rs index 8f3694374..5d85e15f9 100644 --- a/jaq/src/main.rs +++ b/jaq/src/main.rs @@ -25,7 +25,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; extern crate alloc; -fn main() -> io::Result { +fn main() -> ExitCode { use env_logger::Env; env_logger::Builder::from_env(Env::default().filter_or("LOG", "jaq=debug")) .format(|buf, record| match record.level() { @@ -36,9 +36,17 @@ fn main() -> io::Result { }) .init(); - let mut out = io::stdout(); - let mut err = io::stderr(); + // last line of defense against I/O errors that happen during printing, + // e.g. catching broken pipes during error printing + main_io(&mut io::stdin(), &mut io::stdout(), &mut io::stderr()) + .unwrap_or_else(|e: io::Error| Error::from(e).report()) +} +fn main_io( + inp: &mut io::Stdin, + out: &mut io::Stdout, + err: &mut io::Stderr, +) -> io::Result { let cli = match Cli::parse() { Ok(cli) => cli, Err(e) => { @@ -58,10 +66,10 @@ fn main() -> io::Result { } else if let Some(test_files) = &cli.run_tests { match test_files.last() { Some(file) => tests::run(io::BufReader::new(std::fs::File::open(file)?)), - None => tests::run(io::stdin().lock()), + None => tests::run(inp.lock()), } } else { - real_main(&cli).or_else(|e| { + real_main(&cli, inp, out).or_else(|e| { write!(err, "{}", ErrorColor::new(&e, cli.color_errors()))?; Ok(e.report()) }) @@ -105,7 +113,7 @@ impl Cli { } } -fn real_main(cli: &Cli) -> Result { +fn real_main(cli: &Cli, inp: &mut io::Stdin, out: &mut io::Stdout) -> Result { let mut var_val = binds(cli)?; let input_filename_idx = var_val.len(); var_val.push(("!input_filename".to_string(), Val::Null)); @@ -134,9 +142,11 @@ fn real_main(cli: &Cli) -> Result { let vars = Vars::new(vars); let format = unwrap_or_json(cli.from); - let s = read::read_string(format, io::stdin().lock())?; - let inputs = read::read(format, io::stdin().lock(), &s, cli.slurp); - with_stdout(|out| run(runner, &filter, vars, inputs, |v| write(out, writer, &v)))? + let s = read::read_string(format, inp.lock())?; + let inputs = read::read(format, inp.lock(), &s, cli.slurp); + with_stdout(out, |out| { + run(runner, &filter, vars, inputs, |v| write(out, writer, &v)) + })? } else { let mut last = None; for file in &cli.files { @@ -168,7 +178,7 @@ fn real_main(cli: &Cli) -> Result { tmp.persist(path).map_err(|e| Error::Io(None, e.into()))?; std::fs::set_permissions(path, perms)?; } else { - last = with_stdout(|out| { + last = with_stdout(out, |out| { run(runner, &filter, vars.clone(), inputs, |v| { write(out, writer, &v) }) @@ -261,6 +271,7 @@ impl fmt::Display for ErrorColor<'_> { let Self(error, color) = self; match error { Error::FalseOrNull | Error::NoOutput | Error::Halt(_) => Ok(()), + Error::Io(_, e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(()), Error::Io(prefix, e) => { write!(f, "Error: ")?; if let Some(p) = prefix { @@ -284,6 +295,7 @@ impl Termination for Error { fn report(self) -> ExitCode { ExitCode::from(match self { Self::FalseOrNull => 1, + Self::Io(_, e) if e.kind() == io::ErrorKind::BrokenPipe => 141, Self::Io(_, _) => 2, Self::Report(_) => 3, Self::NoOutput => 4,