feat: first bad implementation

it just reads path from argv[1] and plots commits per week for all users
and quits at first keypress or mouse event
This commit is contained in:
əlemi 2023-11-21 03:17:15 +01:00
parent 5fbd1e95cd
commit 5a35e6b04c
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 187 additions and 0 deletions

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "gitts"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.31"
crossterm = "0.27.0"
git2 = "0.18.1"
rand = "0.8.5"
ratatui = { version = "0.24.0", features = ["all-widgets"] }

39
src/main.rs Normal file
View file

@ -0,0 +1,39 @@
use std::collections::HashMap;
use git2::{Repository, Commit};
use chrono::DateTime;
mod tui;
fn main() {
let repo = Repository::open(std::env::args().nth(1).unwrap()).unwrap();
let mut rev = repo.revwalk().unwrap();
rev.set_sorting(git2::Sort::TOPOLOGICAL).unwrap();
rev.push_head().unwrap();
let mut store = HashMap::new();
for commit in rev {
match commit {
Err(e) => eprintln!("could not revwalk: {}", e),
Ok(oid) => {
let commit = repo.find_commit(oid).unwrap();
let id_txt = commit.id().to_string()[0..8].to_string();
let time = DateTime::from_timestamp(commit.time().seconds(), 0).unwrap();
println!(" * {}: {}\t -- {}", id_txt, commit.summary().unwrap_or(""), author_display(&commit, true));
store.insert(oid, (time, commit));
},
}
}
tui::display(store, false).unwrap();
}
pub fn author_display(commit: &Commit, full: bool) -> String {
if full {
format!("{} <{}>", commit.author().name().unwrap_or(""), commit.author().email().unwrap_or(""))
} else {
commit.author().name().unwrap_or("").to_string()
}
}

135
src/tui.rs Normal file
View file

@ -0,0 +1,135 @@
use std::collections::{HashMap, HashSet};
use rand::seq::SliceRandom;
use chrono::{Utc, Duration, DateTime};
use crossterm::{terminal::{enable_raw_mode, EnterAlternateScreen, disable_raw_mode, LeaveAlternateScreen}, execute, event};
use git2::{Commit, Oid};
use ratatui::{prelude::*, widgets::{Dataset, GraphType, Chart, Block, Axis}};
const COLORS : [Color; 10] = [
Color::Blue,
Color::LightBlue,
Color::Cyan,
Color::LightCyan,
Color::Green,
Color::LightGreen,
Color::Yellow,
Color::LightYellow,
Color::Magenta,
Color::LightMagenta,
];
pub fn display(
commits: HashMap<Oid, (DateTime<Utc>, Commit)>,
total: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let now = Utc::now();
let min = *commits.values().map(|(t, _c)| t).min().unwrap();
let bins = (now - min).num_weeks() as usize;
let mut data = vec![(0f64, 0f64); bins + 1];
let authors : HashSet<String> = commits.values().map(|(_t, c)| crate::author_display(c, false)).collect();
let mut authors_data : HashMap<String,Vec<(f64, f64)>> = HashMap::new();
for author in authors {
authors_data.insert(author, vec![(0f64, 0f64); bins + 1]);
}
let mut y_max = 0.;
for (t, c) in commits.values() {
let bin = (*t - min).num_weeks() as usize;
data[bin].1 += 1.;
if data[bin].1 > y_max {
y_max = data[bin].1;
}
if let Some(x) = authors_data.get_mut(&crate::author_display(c, false)) {
x[bin].1 += 1.;
}
}
let x_min = min.timestamp() as f64;
let x_max = Utc::now().timestamp() as f64;
for (_name, author_d) in authors_data.iter_mut() {
let mut min_mut = min;
for (x, _) in author_d.iter_mut() {
*x = min_mut.timestamp() as f64;
min_mut += Duration::weeks(1);
}
}
let mut min_mut = min;
for (x, _) in data.iter_mut() {
*x = min_mut.timestamp() as f64;
min_mut += Duration::weeks(1);
}
// prepare chart widget
let mut datasets = Vec::new();
for (name, author_d) in authors_data.iter() {
datasets.push(
Dataset::default()
.name(name.clone())
.marker(symbols::Marker::Braille)
.graph_type(GraphType::Line)
.style(Style::default().fg(COLORS.choose(&mut rand::thread_rng()).cloned().unwrap()))
.data(author_d)
);
}
if total {
datasets.push(
Dataset::default()
.name("[total]")
.marker(symbols::Marker::Braille)
.graph_type(GraphType::Line)
.style(Style::default().fg(Color::Red))
.data(&data)
);
}
let x_labels = [
min.format("%Y/%m/%d").to_string(),
Utc::now().date_naive().format("%Y/%m/%d").to_string(),
];
let y_labels = [
"0".into(), format!("{}", y_max)
];
let chart = Chart::new(datasets)
.block(Block::default().title("Commits"))
.x_axis(Axis::default()
.title(Span::styled("time", Style::default().fg(Color::Cyan)))
.style(Style::default().fg(Color::White))
.bounds([x_min - 1., x_max + 1.])
.labels(x_labels.iter().map(Span::from).collect())
)
.y_axis(Axis::default()
.title(Span::styled("Y Axis", Style::default().fg(Color::Cyan)))
.style(Style::default().fg(Color::White))
.bounds([-0.1, y_max + 0.1])
.labels(y_labels.iter().map(Span::from).collect())
);
// setup terminal
enable_raw_mode()?;
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.hide_cursor()?;
// draw chart and keep it for 3s
terminal.draw(|f| f.render_widget(chart, f.size()))?;
event::read().unwrap();
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
)?;
terminal.show_cursor()?;
Ok(())
}