clipped 0.1.0

Clipped your cargo clippy
Documentation
use std::{
    env, ffi,
    fmt::{self, Display, Formatter},
    io::{BufReader, Read},
    process::{Child, Command, ExitStatus, Stdio},
    sync::mpsc::{self, Receiver},
    thread::{self, JoinHandle},
};

use crate::error::{Error, Result};

use cargo_metadata::Message;

#[derive(Debug)]
pub struct ClippyRunner {
    child: Child,
    stderr_handle: JoinHandle<Result<String>>,
    receiver: Receiver<std::result::Result<Message, std::io::Error>>,
}

impl ClippyRunner {
    pub fn wait(mut self) -> Result<(ExitStatus, String)> {
        let status = self.child.wait().map_err(Error::ClippyWait)?;
        let stderr = self.stderr_handle.join().expect("failed to join stderr thread")?;
        Ok((status, stderr))
    }
}

impl Iterator for ClippyRunner {
    type Item = std::result::Result<Message, std::io::Error>;

    fn next(&mut self) -> Option<Self::Item> {
        self.receiver.recv().ok()
    }
}

#[derive(Debug)]
pub struct ClippyCommand {
    cmd: Command,
}

impl ClippyCommand {
    pub fn new() -> Self {
        let mut command = Command::new(env::var("CARGO").unwrap_or("cargo".into()));
        command.arg("clippy").arg("--message-format=json-diagnostic-rendered-ansi");
        ClippyCommand { cmd: command }
    }

    pub fn arg<S: AsRef<ffi::OsStr>>(mut self, arg: S) -> Self {
        self.cmd.arg(arg);
        self
    }

    pub fn args<I, S>(mut self, args: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: AsRef<ffi::OsStr>,
    {
        self.cmd.args(args);
        self
    }

    pub fn spawn(&mut self) -> Result<ClippyRunner> {
        let mut child = self
            .cmd
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(Error::ClippySpawn)?;

        let stdout = child.stdout.take().ok_or(Error::ClippyCapture { stream: "stdout" })?;
        let mut stderr = child.stderr.take().ok_or(Error::ClippyCapture { stream: "stderr" })?;

        let (tx, rx) = mpsc::sync_channel::<std::result::Result<Message, std::io::Error>>(100);

        thread::spawn(move || {
            let reader = BufReader::new(stdout);
            for message in Message::parse_stream(reader) {
                if tx.send(message).is_err() {
                    break;
                }
            }
        });

        let stderr_handle = thread::spawn(move || {
            let mut output = String::new();
            stderr.read_to_string(&mut output).map(|_| output).map_err(Error::ClippyStderrRead)
        });

        Ok(ClippyRunner { child, stderr_handle, receiver: rx })
    }
}

impl Default for ClippyCommand {
    fn default() -> Self {
        ClippyCommand::new()
    }
}

impl Display for ClippyCommand {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.cmd.get_program().to_string_lossy())?;
        for arg in self.cmd.get_args() {
            write!(f, " {}", arg.to_string_lossy())?;
        }
        Ok(())
    }
}