multilint 0.1.3

A driver of multiple linters
Documentation
use anyhow::Result;
use std::{
    io::{stdout, Write},
    process::Output,
};

use colored::*;

use crate::parser::{Parsed, Parser};

fn parse(parser: &Parser, name: &str, output: &[u8]) -> Result<Vec<Parsed>> {
    let mut msgs = parser.parse(std::str::from_utf8(output)?);
    for msg in &mut msgs {
        msg.program.get_or_insert_with(|| name.to_string());
    }
    Ok(msgs)
}

pub trait OutputFormat {
    fn start(&self, name: &str);
    fn no_command(&self, name: &str);
    fn no_file(&self, name: &str);
    fn status(&self, name: &str, output: &Output, parser: &Parser) -> Result<()>;
}

#[derive(Default)]
pub struct NullFormat {}

impl OutputFormat for NullFormat {
    fn start(&self, _name: &str) {}
    fn no_command(&self, _name: &str) {}
    fn no_file(&self, _name: &str) {}
    fn status(&self, _name: &str, _output: &Output, _parser: &Parser) -> Result<()> {
        Ok(())
    }
}

#[derive(Default)]
pub struct TextFormat {}

impl OutputFormat for TextFormat {
    fn start(&self, name: &str) {
        print!("{} {} ... ", "Running".bold().green(), &name);
    }

    fn no_command(&self, _name: &str) {
        println!("{}", "no command".yellow());
    }

    fn no_file(&self, _name: &str) {
        println!("{}", "skipped".yellow());
    }

    fn status(&self, _name: &str, output: &Output, _parser: &Parser) -> Result<()> {
        if output.status.success() {
            println!("{}", "ok".green());
        } else {
            println!("{}", "failed".red());
        }
        if !output.stdout.is_empty() {
            stdout().write_all(&output.stdout).unwrap();
        }
        if !output.stderr.is_empty() {
            stdout().write_all(&output.stderr).unwrap();
        }
        Ok(())
    }
}

#[derive(Default)]
pub struct JSONLFormat {}

impl OutputFormat for JSONLFormat {
    fn start(&self, _name: &str) {}
    fn no_command(&self, _name: &str) {}
    fn no_file(&self, _name: &str) {}

    fn status(&self, name: &str, output: &Output, parser: &Parser) -> Result<()> {
        let msgs = parse(parser, name, &output.stdout)?;
        for msg in msgs {
            println!("{}", serde_json::to_string(&msg)?);
        }
        Ok(())
    }
}

#[derive(Default)]
pub struct GNUFormat {}

impl OutputFormat for GNUFormat {
    fn start(&self, _name: &str) {}
    fn no_command(&self, _name: &str) {}
    fn no_file(&self, _name: &str) {}

    fn status(&self, name: &str, output: &Output, parser: &Parser) -> Result<()> {
        let msgs = parse(parser, name, &output.stdout)?;
        for msg in msgs {
            if let Some(program) = msg.program {
                print!("{}:", program);
            }
            if let Some(file) = msg.file {
                print!("{}:", file);
            }
            if let Some(line) = msg.line {
                print!("{}:", line);
            }
            if let Some(column) = msg.column {
                print!("{}:", column);
            }
            if let Some(message) = msg.message {
                print!(" {}", message);
            }
            println!();
        }
        Ok(())
    }
}