injm 0.5.0

A CLI tool that injects content into marked regions in source files.
use crate::core::types::{MarkerBlock, ParsedFile, Result};
use std::collections::HashSet;
use std::fs;

pub(crate) fn check_file(path: &str) -> Result<()> {
    if !fs::exists(path)? {
        return Err("file does not exist".into());
    }

    if is_binary_file(path)? {
        return Err("binary file".into());
    }

    Ok(())
}

pub fn check_missing_ids(output_files: &[ParsedFile], input_files: &[ParsedFile]) -> Result<()> {
    let provided: HashSet<&String> = input_files
        .iter()
        .flat_map(|file| file.blocks.iter())
        .flat_map(|block| block.input_ids.iter())
        .collect();

    if let Some(id) = output_files
        .iter()
        .flat_map(|file| file.blocks.iter())
        .filter_map(|block| block.output_id.as_ref())
        .find(|id| !provided.contains(*id))
    {
        return Err(format!("missing input id `{id}`").into());
    }

    Ok(())
}

pub fn check_duplicated_ids(blocks: &[MarkerBlock]) -> Result<()> {
    let mut seen = HashSet::new();

    for id in blocks.iter().flat_map(|block| block.input_ids.iter()) {
        if !seen.insert(id) {
            return Err(format!("duplicated input id `{id}`").into());
        }
    }

    Ok(())
}

fn is_binary_file(path: &str) -> Result<bool> {
    use std::io::Read;
    let mut f = fs::File::open(path)?;
    let mut buffer = [0u8; 8192];
    let n = f.read(&mut buffer)?;
    Ok(buffer[..n].contains(&0))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    #[test]
    fn test_file_not_exist() {
        assert!(check_file("not_exist.rs").is_err());
    }

    #[test]
    fn test_text_file() {
        let mut f = tempfile::NamedTempFile::new().unwrap();
        writeln!(f, "fn main() {{}}").unwrap();
        assert!(check_file(&f.path().to_string_lossy()).is_ok());
    }

    #[test]
    fn test_binary_file() {
        let mut f = tempfile::NamedTempFile::new().unwrap();
        f.write_all(&[0x00, 0x01, 0x02, 0x03]).unwrap();
        assert!(check_file(&f.path().to_string_lossy()).is_err());
    }

    #[test]
    fn test_empty_file() {
        let f = tempfile::NamedTempFile::new().unwrap();
        assert!(check_file(&f.path().to_string_lossy()).is_ok());
    }
}