Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
term: Catch EPIPE and swallow
Fintan Halpenny committed 1 day ago
commit e1f16bee26696128ea1c8bb38088a5217c80c92a
parent 8f4b90d
3 files changed +79 -25
modified crates/radicle-cli/tests/commands/sigpipe.rs
@@ -40,7 +40,6 @@ use radicle::profile;

use crate::util::environment::Environment;

-
#[ignore = "test fails"]
#[test]
fn config() {
    let mut environment = Environment::new();
@@ -121,7 +120,6 @@ fn help() {
}

/// `rad self` uses `Element::print()` for table output.
-
#[ignore = "test fails"]
#[test]
fn rad_self() {
    let mut environment = Environment::new();
modified crates/radicle-term/src/element.rs
@@ -84,8 +84,12 @@ pub trait Element: fmt::Debug + Send + Sync {

    /// Print this element to stdout.
    fn print(&self) {
+
        use std::io::Write;
+

+
        let mut stdout = io::stdout().lock();
        for line in self.render(Constraint::from_env().unwrap_or_default()) {
-
            println!("{}", line.to_string().trim_end());
+
            let _ = writeln!(stdout, "{}", line.to_string().trim_end())
+
                .or_else(crate::io::swallow_broken_pipe_stdout);
        }
    }

modified crates/radicle-term/src/io.rs
@@ -74,7 +74,7 @@ macro_rules! info {
        writeln!($writer, $($arg)*).ok();
    });
    ($($arg:tt)*) => ({
-
        println!("{}", format_args!($($arg)*));
+
        $crate::io::print(format_args!($($arg)*));
    })
}

@@ -118,11 +118,11 @@ pub fn success_args<W: io::Write>(w: &mut W, args: fmt::Arguments) {
}

pub fn tip_args(args: fmt::Arguments) {
-
    println!(
+
    print(format_args!(
        "{} {}",
        format::yellow("*"),
        style(format!("{args}")).italic()
-
    );
+
    ));
}

pub fn notice_args<W: io::Write>(w: &mut W, args: fmt::Arguments) {
@@ -148,27 +148,76 @@ pub fn viewport() -> Option<Size> {
}

pub fn headline(headline: impl fmt::Display) {
-
    println!();
-
    println!("{}", style(headline).bold());
-
    println!();
+
    print("");
+
    print(style(headline).bold());
+
    print("");
}

pub fn header(header: &str) {
-
    println!();
-
    println!("{}", style(format::yellow(header)).bold().underline());
-
    println!();
+
    print("");
+
    print(style(format::yellow(header)).bold().underline());
+
    print("");
}

pub fn blob(text: impl fmt::Display) {
-
    println!("{}", style(text.to_string().trim()).dim());
+
    print(style(text.to_string().trim()).dim());
}

pub fn blank() {
-
    println!()
+
    print("");
}

+
/// Print a line to stdout, silently ignoring broken pipe errors.
+
///
+
/// Use this function instead of [`println!`] when you want to print to standard
+
/// output, but silently ignore broken pipe errors.
+
///
+
/// See also [`self::print`].
+
///
+
/// # Panics
+
///
+
/// If writing to standard output fails with an error not of kind [`io::ErrorKind::BrokenPipe`].
pub fn print(msg: impl fmt::Display) {
-
    println!("{msg}");
+
    use io::Write;
+

+
    let mut stdout = io::stdout().lock();
+
    let _ = writeln!(stdout, "{msg}").or_else(swallow_broken_pipe_stdout);
+
}
+

+
/// Print to stdout without a trailing newline, silently ignoring broken pipe
+
/// errors.
+
///
+
/// Use this function instead of [`print!`] when you want to print to standard
+
/// output, but silently ignore broken pipe errors.
+
///
+
/// See also [`self::print`].
+
///
+
/// # Panics
+
///
+
/// If writing to standard output fails with an error not of kind [`io::ErrorKind::BrokenPipe`].
+
pub fn print_inline(msg: impl fmt::Display) {
+
    use io::Write;
+

+
    let mut stdout = io::stdout().lock();
+
    let _ = write!(stdout, "{msg}").or_else(swallow_broken_pipe_stdout);
+
}
+

+
/// If the given `err` is of kind [`io::ErrorKind::BrokenPipe`], return `Ok(())`
+
/// to silently ignore it. Otherwise, panic saying "failed printing to stdout",
+
/// followed by the error message.
+
///
+
/// This may be used with [`Result::or_else`] to ignore broken pipes when
+
/// writing to standard output.
+
///
+
/// # Panics
+
///
+
/// If `err` is not of kind [`io::ErrorKind::BrokenPipe`].
+
pub(crate) fn swallow_broken_pipe_stdout(err: io::Error) -> io::Result<()> {
+
    if err.kind() == io::ErrorKind::BrokenPipe {
+
        Ok(())
+
    } else {
+
        panic!("failed printing to stdout: {err}")
+
    }
}

pub fn prefixed(prefix: &str, text: &str) -> String {
@@ -179,7 +228,7 @@ pub fn prefixed(prefix: &str, text: &str) -> String {
}

pub fn help(name: &str, version: &str, description: &str, usage: &str) {
-
    println!("rad-{name} {version}\n{description}\n{usage}");
+
    print(format_args!("rad-{name} {version}\n{description}\n{usage}"));
}

pub fn manual(name: &str) -> io::Result<process::ExitStatus> {
@@ -192,40 +241,43 @@ pub fn manual(name: &str) -> io::Result<process::ExitStatus> {
}

pub fn usage(name: &str, usage: &str) {
-
    println!(
+
    print(format_args!(
        "{} {}\n{}",
        PREFIX_ERROR,
        Paint::red(format!("Error: rad-{name}: invalid usage")),
        Paint::red(prefixed(TAB, usage)).dim()
-
    );
+
    ));
}

pub fn println(prefix: impl fmt::Display, msg: impl fmt::Display) {
-
    println!("{prefix} {msg}");
+
    print(format_args!("{prefix} {msg}"));
}

pub fn indented(msg: impl fmt::Display) {
-
    println!("{TAB}{msg}");
+
    print(format_args!("{TAB}{msg}"));
}

pub fn subcommand(msg: impl fmt::Display) {
-
    println!("{}", style(format!("Running `{msg}`...")).dim());
+
    print(style(format!("Running `{msg}`...")).dim());
}

pub fn warning(warning: impl fmt::Display) {
-
    println!(
+
    print(format_args!(
        "{} {} {warning}",
        PREFIX_WARNING,
        Paint::yellow("Warning:").bold(),
-
    );
+
    ));
}

pub fn error(error: impl fmt::Display) {
-
    println!("{PREFIX_ERROR} {} {error}", Paint::red("Error:"));
+
    print(format_args!(
+
        "{PREFIX_ERROR} {} {error}",
+
        Paint::red("Error:")
+
    ));
}

pub fn hint(hint: impl fmt::Display) {
-
    println!("{}", format::hint(format!("{SYMBOL_ERROR} Hint: {hint}")));
+
    print(format::hint(format!("{SYMBOL_ERROR} Hint: {hint}")));
}

pub fn ask<D: fmt::Display>(prompt: D, default: bool) -> bool {