injm 0.5.1

A CLI tool that injects content into marked regions in source files.
use anyhow::Result;
use std::fs;
use std::io::{self, Read};

use crate::checker::{check_duplicated_input_ids, check_missing_ids};
use crate::cli::InjectArgs;
use crate::injector::inject;
use crate::parser::parse_patterns;
use crate::types::{BlockRole, MarkerBlock, SourceSpan};

pub fn run(args: InjectArgs) -> Result<()> {
    let output_files = parse_patterns(&args.output)?;

    let input_blocks: Vec<MarkerBlock> = if args.input.is_empty() {
        stdin_blocks(args.id)?
    } else {
        let input_files = parse_patterns(&args.input)?;
        check_missing_ids(&output_files, &input_files)?;
        input_files
            .into_iter()
            .flat_map(|file| file.blocks)
            .collect()
    };

    check_duplicated_input_ids(&input_blocks)?;

    for output_file in output_files {
        let replaced = inject(&output_file.content, &output_file.blocks, &input_blocks)?;
        if args.dry_run {
            println!("{replaced}");
        } else {
            fs::write(output_file.path, replaced)?;
        }
    }

    Ok(())
}

fn read_stdin() -> Result<String> {
    let mut input = String::new();
    io::stdin().read_to_string(&mut input)?;
    Ok(input)
}

fn stdin_blocks(ids: Vec<Option<String>>) -> Result<Vec<MarkerBlock>> {
    let stdin = read_stdin()?;
    let input_ids = ids.into_iter().flatten().collect();
    Ok(vec![MarkerBlock {
        span: SourceSpan::new(0, 0),
        role: BlockRole::Input {
            ids: input_ids,
            content: stdin,
        },
    }])
}