#![feature(byte_slice_trim_ascii)] #![feature(let_chains)] #![allow(dead_code)] use std::{cmp::Reverse, sync::OnceLock}; use anyhow::{Context, Result}; use chrono::{Date, DateTime, Datelike, Duration, Local, NaiveDate, Offset, TimeZone, Utc}; use clap::Parser; use gix::{bstr::ByteSlice, ObjectId, Repository}; use heatmap::HeatmapColors; use rgb::Rgb; use crate::{cli::CliArgs, heatmap::Heatmap}; mod cli; mod heatmap; mod rgb; pub const ESCAPE: &str = "\x1B"; pub const RESET: &str = "\x1B[0m"; pub const DAYS: [&str; 7] = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; pub static CHAR: OnceLock = OnceLock::new(); pub static COLOR_MAP: OnceLock> = OnceLock::new(); const GREEN_COLOR_MAP: [Rgb; 5] = [ Rgb(0, 0, 0), Rgb(14, 68, 41), Rgb(0, 109, 50), Rgb(38, 166, 65), Rgb(57, 211, 83), ]; const RED_COLOR_MAP: [Rgb; 5] = [ Rgb(0, 0, 0), Rgb(208, 169, 35), Rgb(208, 128, 35), Rgb(208, 78, 35), Rgb(208, 35, 64), ]; struct Commit { id: ObjectId, title: String, author: String, time: DateTime, } fn main() -> Result<()> { clear_screen(); let args = CliArgs::parse(); CHAR.set(args.char).unwrap(); let color_map = match args.color_scheme { HeatmapColors::Green => GREEN_COLOR_MAP, HeatmapColors::Red => RED_COLOR_MAP, }; let color_map = color_map .into_iter() .map(|c| c.to_ansi()) .collect::>(); COLOR_MAP.set(color_map).unwrap(); let repo = gix::open(&args.input).unwrap(); let end_date = Local::now(); let start_date = end_date - Duration::days(365); let commits = get_commits(repo, args, start_date).with_context(|| "Could not fetch commit list")?; let heatmap = Heatmap::new(start_date, end_date, commits); heatmap.print(); Ok(()) } fn get_color(val: u32) -> usize { match val { 0 => 0, x if x < 2 => 1, x if x < 4 => 2, x if x < 6 => 3, x if x > 8 => 4, _ => 0, } } fn clear_screen() { print!("\x1b[2J\x1b[1;1H"); } fn get_char() -> char { *CHAR.get_or_init(|| '▩') } fn get_color_map() -> Vec { COLOR_MAP .get_or_init(|| { GREEN_COLOR_MAP .into_iter() .map(|c| c.to_ansi()) .collect::>() }) .to_vec() } fn get_commits( repo: Repository, args: CliArgs, start_date: DateTime, ) -> Result> { let mut commits: Vec = repo .head()? .into_peeled_id()? .ancestors() .all()? .filter_map(|c| c.ok()) .filter_map(|c| c.object().ok()) .filter_map(|c| { let title = c .message() .ok()? .title .trim_ascii() .to_str() .ok()? .to_string(); let author = c.author().ok()?.name.to_string(); if author != args.author { return None; } let time = c.time().ok()?; // let offset = Local.timestamp_opt(0, 0).unwrap().offset().fix(); // let time = Local::now() - Duration::seconds(time.seconds); let time = DateTime::from_timestamp_millis(time.seconds * 1000)?.with_timezone(&Local); if time <= start_date + Duration::days(1) { return None; } Some(Commit { id: c.id, title, author, time, }) }) .collect(); commits.sort_by_cached_key(|a| Reverse(a.time)); Ok(commits) } // fn print_streak(commits: Vec) { // let mut commits_pushed = 0; // let mut days_streak = 0; // let mut last_date: Option> = None; // let mut start_date: Option> = None; // let mut end_date: Option> = None; // // for commit in commits { // match last_date { // Some(date) => { // let day = date.ordinal0(); // let commit_day = commit.time.ordinal0(); // let time_diff = date - commit.time; // // if commit_day != day { // days_streak += 1; // } // // if time_diff.num_hours() >= 24 { // // println!( // // "Failing Commit\n{} - {} {} hours difference", // // commit.id, // // commit.time.to_rfc3339(), // // time_diff.num_hours() // // ); // break; // } // // commits_pushed += 1; // last_date = Some(commit.time); // end_date = Some(commit.time); // // // println!( // // "{} - {} {} hours difference", // // commit.id, // // commit.time.to_rfc3339(), // // time_diff.num_hours() // // ); // } // None => { // last_date = Some(commit.time); // start_date = Some(commit.time); // // println!("First Commit\n{} - {}", commit.id, commit.time.to_rfc3339()); // continue; // } // } // } // // println!("{commits_pushed} commits pushed during a {days_streak} days streak"); // if let Some(start_date) = start_date // && let Some(end_date) = end_date // { // let start_date = start_date.format("%d-%b-%Y").to_string(); // let end_date = end_date.format("%d-%b-%Y").to_string(); // // println!("Between: {start_date} {end_date}"); // } // }