diff-coverage 0.6.1

Diff-coverage, supercharged in Rust. Fast, memory-efficient coverage on changed lines for CI.
mod error;
mod files;
mod output;
mod reporting;
mod validation;

use crate::{cli, coverage};
use error::AppError;
use files::{collect_coverage_files, load_changed_files, load_coverage_files};
use output::build_output_plan;
use reporting::write_reports;
use validation::{validate_fail_under, validate_output_requirements};

pub fn run(options: cli::CliOptions) -> Result<(), AppError> {
    let diff_file = options.diff_file;
    let coverage_paths = options.coverage_paths;
    let fail_under = options.fail_under;
    let output_targets = options.outputs;
    let missing_coverage = options.missing_coverage;
    let skip_coverage_paths = options.skip_coverage_paths;

    let coverage_files = collect_coverage_files(coverage_paths).map_err(AppError::usage)?;

    validate_fail_under(fail_under, diff_file.as_ref(), &coverage_files)?;

    let output_plan = build_output_plan(output_targets)?;
    validate_output_requirements(&output_plan, diff_file.as_ref(), &coverage_files)?;

    match (diff_file, coverage_files.is_empty()) {
        (Some(diff_file), false) => {
            let changed = load_changed_files(&diff_file).map_err(|err| {
                AppError::usage(format!(
                    "Failed to parse diff file {}: {err}",
                    diff_file.display()
                ))
            })?;

            let coverage = load_coverage_files(&coverage_files)
                .map_err(|err| AppError::usage(format!("Failed to parse coverage files: {err}")))?;

            let treat_missing_as_uncovered =
                matches!(missing_coverage, cli::MissingCoverageMode::Uncovered);
            let report = coverage::analyze_changed_coverage(
                &changed,
                &coverage,
                treat_missing_as_uncovered,
                &skip_coverage_paths,
            )
            .map_err(|err| AppError::usage(err.to_string()))?;
            write_reports(&report, &output_plan)?;

            if let Some(threshold) = fail_under {
                let percent = report.coverage_percent();
                if percent < threshold {
                    return Err(AppError::fail_under(percent, threshold));
                }
            }
        }
        (Some(diff_file), true) => {
            let changed = load_changed_files(&diff_file).map_err(|err| {
                AppError::usage(format!(
                    "Failed to parse diff file {}: {err}",
                    diff_file.display()
                ))
            })?;

            for file in changed {
                println!("{}: {:?}", file.path, file.changed_lines);
            }
        }
        (None, false) => {
            return Err(AppError::usage("coverage path(s) require --diff-file"));
        }
        (None, true) => {
            cli::print_help();
        }
    }

    Ok(())
}