cargo-buildw 0.2.0

Silence cargo build warnings
Documentation
//! # Hide warnings
//!
//! `cargo-buildw` is a `cargo` command to disable some/all warnings to make it easier
//! to read build output.
//!
//! - `-W<warning>`/`-W <warning>`/`--silence-warning <warning>`: disable warnings `<warnings>`
//! - `-Wall`/`--silence-warnings`: disable all warnings
//!
//! All the other parameters are passed to `cargo` as usual.
//!
//! (Also adding this crate to your project is completely useless. There is no API.)

use std::{
    borrow::Cow,
    io::{self, BufRead, BufReader, Read, Write},
    process::{Command, Stdio, exit},
};

use serde::Deserialize;

#[derive(Deserialize)]
struct CompilerMessageCode<'a> {
    code: Cow<'a, str>,
}

#[derive(Deserialize)]
struct CompilerMessage<'a> {
    code: CompilerMessageCode<'a>,
    rendered: Cow<'a, str>,
    level: Cow<'a, str>,
}

#[derive(Deserialize)]
#[serde(tag = "reason", rename_all = "kebab-case")]
enum RustBuildMessage<'a> {
    /// Stop analyzing rustc output.
    BuildFinished,
    CompilerMessage {
        message: CompilerMessage<'a>,
    },
}

struct Args {
    hidden_warnings: Vec<String>,
    hide_all: bool,
    disable_this_thing_entirely: bool,
    args: Vec<String>,
}

fn parse_args(name: &str) -> Args {
    let mut iter = std::env::args().peekable();
    iter.next();
    let mut args = vec![];
    if iter.peek() == Some(&format!("{name}w")) {
        iter.next();
    }
    let mut hide_all = false;
    let mut hidden_warnings = vec![];
    let mut disable_this_thing_entirely = false;
    while let Some(x) = iter.next() {
        if x == "--silence-warnings" || x == "-Wall" {
            hide_all = true;
            hidden_warnings.clear();
        } else if x == "-W" || x == "--silence-warning" {
            let Some(x) = iter.next() else {
                eprintln!("usage: {x} <warning>");
                exit(1);
            };
            if hide_all {
                continue;
            }
            hidden_warnings.push(x);
        } else if let Some(x) = x.strip_prefix("-W") {
            if x.is_empty() {
                eprintln!("usage: {x} <warning>");
                exit(1);
            };
            if hide_all {
                continue;
            }
            hidden_warnings.push(x.to_string());
        } else if x == "--" {
            args.push(x);
            break;
        } else if x == "--message-format" || x.starts_with("--message-format=") {
            disable_this_thing_entirely = true;
            args.push(x);
            break;
        } else {
            args.push(x);
        }
    }
    for x in iter {
        args.push(x);
    }
    Args {
        args,
        hide_all,
        hidden_warnings,
        disable_this_thing_entirely,
    }
}

#[doc(hidden)]
pub fn run(name: &str) {
    let args = parse_args(name);

    if args.disable_this_thing_entirely {
        let mut command = Command::new("cargo");
        command.arg(name).args(args.args);
        #[cfg(unix)]
        {
            use std::os::unix::process::CommandExt;
            panic!("{:#}", command.exec());
        }
        #[allow(unreachable_code)]
        if let Some(code) = command
            .spawn()
            .ok()
            .and_then(|mut x| x.wait().ok())
            .and_then(|x| x.code())
        {
            exit(code);
        } else {
            exit(-1);
        }
    }

    let mut proc = Command::new("cargo")
        .arg(name)
        .arg("--message-format=json-diagnostic-rendered-ansi")
        .args(args.args)
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let mut stdout = BufReader::new(proc.stdout.take().unwrap());

    let mut line = String::new();
    loop {
        line.clear();
        if stdout.read_line(&mut line).unwrap() == 0 {
            break;
        }
        if line.starts_with('{') && line.trim_end().ends_with('}') {
            if let Ok(x) = serde_json::from_str::<RustBuildMessage<'_>>(line.trim()) {
                match x {
                    RustBuildMessage::BuildFinished => break,
                    RustBuildMessage::CompilerMessage { message } => 'a: {
                        'b: {
                            if message.level != "warning" {
                                break 'b;
                            }
                            if args.hide_all {
                                break 'a;
                            }
                            if args
                                .hidden_warnings
                                .iter()
                                .any(|x| x.as_str() == message.code.code.as_ref())
                            {
                                break 'a;
                            }
                        }
                        io::stdout().write_all(message.rendered.as_bytes()).ok();
                    }
                }
            }
        } else {
            io::stdout().write_all(line.as_bytes()).ok();
        }
    }

    let mut buf = vec![0; 4096];
    let mut cursor = 0;
    let mut stdout = Some(stdout);
    while stdout.is_some() || cursor > 0 {
        if let Some(x) = &mut stdout
            && cursor != buf.len()
        {
            let Ok(v) = x.read(&mut buf[cursor..]) else {
                stdout.take();
                continue;
            };
            if v == 0 {
                stdout.take();
                continue;
            }
            cursor += v;
        }

        if cursor > 0 {
            let Ok(v) = io::stdout().write(&buf[..cursor]) else {
                break;
            };
            if v == 0 {
                break;
            }
            cursor -= v;
        }
    }

    match proc.wait().map(|x| x.code()) {
        Ok(Some(x)) => exit(x),
        _ => exit(-1),
    }
}