gmap 0.3.3

Git repository analysis tool for churn and heatmap visualization
Documentation
use crate::cli::CommonArgs;
use crate::git::GitRepo;
use crate::model::{HeatBucket, HeatOutput, SCHEMA_VERSION};
use chrono::Utc;
use console::style;
use anyhow::Result;

fn intensity_char<'a>(value: f64, max: f64, symbols: &'a [&str]) -> &'a str {
    if max <= 0.0 {
        return symbols[0];
    }
    let levels = (symbols.len() - 1) as f64;
    let mut level = ((value / max) * levels).round() as usize;
    if level > symbols.len() - 1 {
        level = symbols.len() - 1;
    }
    symbols[level]
}

pub fn output_json(
    heat_data: &[HeatBucket],
    repo: &GitRepo,
    common: &CommonArgs,
    path_prefix: Option<&str>,
) -> Result<()> {
    let output = HeatOutput {
        version: SCHEMA_VERSION,
        generated_at: Utc::now(),
        repository_path: repo.path().display().to_string(),
        path_prefix: path_prefix.unwrap_or_default().to_string(),
        since: common.since.clone(),
        until: common.until.clone(),
        buckets: heat_data.to_vec(),
    };

    println!("{}", serde_json::to_string_pretty(&output)?);
    Ok(())
}

pub fn output_ndjson(heat_data: &[HeatBucket]) -> Result<()> {
    for bucket in heat_data {
        println!("{}", serde_json::to_string(bucket)?);
    }
    Ok(())
}

pub fn output_heatmap(heat_data: &[HeatBucket], common: &CommonArgs) -> Result<()> {
    if heat_data.is_empty() {
        println!("No data to display");
        return Ok(());
    }

    match (&common.since, &common.until) {
        (Some(since), Some(until)) => {
            println!("Filtering commits from {} to {}", since, until);
        }
        (Some(since), None) => {
            println!("Filtering commits since {}", since);
        }
        (None, Some(until)) => {
            println!("Filtering commits until {}", until);
        }
        _ => {}
    }

    let max_commits = heat_data.iter().map(|b| b.commit_count).max().unwrap_or(1) as f64;
    let max_lines = heat_data.iter().map(|b| b.lines_changed).max().unwrap_or(1) as f64;

    println!("{}", style("Commit Activity Heatmap").bold());
    println!("{}", "".repeat(50));

    for bucket in heat_data {
        let commit_char = intensity_char(bucket.commit_count as f64, max_commits, &[" ", "", "", "", "", ""]);
        let lines_char = intensity_char(bucket.lines_changed as f64, max_lines, &[" ", "", "", "", "", ""]);

        println!(
            "{} {} {} commits: {:>3}, lines: {:>6}",
            bucket.week,
            style(commit_char).green(),
            style(lines_char).blue(),
            bucket.commit_count,
            bucket.lines_changed
        );
    }

    println!("\n{}", style("Legend").bold());
    println!("  {} commits intensity", style("▁▃▅▇█").green());
    println!("  {} lines intensity", style("░▒▓█").blue());

    Ok(())
}