2024-08-15 22:09:51 +03:00
|
|
|
#![feature(byte_slice_trim_ascii)]
|
|
|
|
#![feature(let_chains)]
|
|
|
|
#![allow(dead_code)]
|
|
|
|
|
|
|
|
use std::{cmp::Reverse, sync::OnceLock};
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
2024-08-16 12:50:24 +03:00
|
|
|
use chrono::{DateTime, Duration, Local};
|
2024-08-15 22:09:51 +03:00
|
|
|
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<char> = OnceLock::new();
|
|
|
|
pub static COLOR_MAP: OnceLock<Vec<String>> = OnceLock::new();
|
|
|
|
|
|
|
|
const GREEN_COLOR_MAP: [Rgb; 5] = [
|
|
|
|
Rgb(0, 0, 0),
|
|
|
|
Rgb(14, 68, 41),
|
|
|
|
Rgb(0, 109, 50),
|
|
|
|
Rgb(38, 166, 65),
|
2024-08-16 12:50:24 +03:00
|
|
|
Rgb(25, 255, 64),
|
2024-08-15 22:09:51 +03:00
|
|
|
];
|
|
|
|
|
|
|
|
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,
|
2024-08-16 02:27:50 +03:00
|
|
|
time: DateTime<Local>,
|
2024-08-15 22:09:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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::<Vec<_>>();
|
|
|
|
COLOR_MAP.set(color_map).unwrap();
|
|
|
|
|
|
|
|
let repo = gix::open(&args.input).unwrap();
|
|
|
|
|
2024-08-16 02:27:50 +03:00
|
|
|
let end_date = Local::now();
|
2024-08-15 22:09:51 +03:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2024-08-16 12:50:24 +03:00
|
|
|
fn get_color(val: i32, high: i32) -> usize {
|
|
|
|
let color = val as f32 / high as f32;
|
|
|
|
match color {
|
|
|
|
0.0 => 0,
|
|
|
|
x if x <= 0.2 => 1,
|
|
|
|
x if x <= 0.4 => 2,
|
|
|
|
x if x <= 0.8 => 3,
|
|
|
|
x if x > 0.8 => 4,
|
2024-08-15 22:09:51 +03:00
|
|
|
_ => 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_screen() {
|
|
|
|
print!("\x1b[2J\x1b[1;1H");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_char() -> char {
|
|
|
|
*CHAR.get_or_init(|| '▩')
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_color_map() -> Vec<String> {
|
|
|
|
COLOR_MAP
|
|
|
|
.get_or_init(|| {
|
|
|
|
GREEN_COLOR_MAP
|
|
|
|
.into_iter()
|
|
|
|
.map(|c| c.to_ansi())
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
})
|
|
|
|
.to_vec()
|
|
|
|
}
|
|
|
|
|
2024-08-16 02:27:50 +03:00
|
|
|
fn get_commits(
|
|
|
|
repo: Repository,
|
|
|
|
args: CliArgs,
|
|
|
|
start_date: DateTime<Local>,
|
|
|
|
) -> Result<Vec<Commit>> {
|
2024-08-15 22:09:51 +03:00
|
|
|
let mut commits: Vec<Commit> = 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()?;
|
2024-08-16 02:27:50 +03:00
|
|
|
// 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);
|
2024-08-15 22:09:51 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-08-16 02:27:50 +03:00
|
|
|
// fn print_streak(commits: Vec<Commit>) {
|
|
|
|
// let mut commits_pushed = 0;
|
|
|
|
// let mut days_streak = 0;
|
|
|
|
// let mut last_date: Option<DateTime<Utc>> = None;
|
|
|
|
// let mut start_date: Option<DateTime<Utc>> = None;
|
|
|
|
// let mut end_date: Option<DateTime<Utc>> = 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}");
|
|
|
|
// }
|
|
|
|
// }
|