use crate::projects::balls::Ball;
use std::collections::HashMap;
use std::path::PathBuf;
use super::{Churn, FileChurn};
const ON_CLOSE: &str = "close";
const NUMSTAT_FIELDS: usize = 3;
const NO_COUNT: &str = "-";
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct Plan {
pub(super) project: PathBuf,
pub(super) ball_id: String,
pub(super) target_ball: Option<String>,
}
pub(super) fn plans(by_project: &HashMap<PathBuf, Vec<Ball>>, name: &str) -> Vec<Plan> {
let mut projects: Vec<(&PathBuf, &Vec<Ball>)> = by_project.iter().collect();
projects.sort_by_key(|(path, _)| *path);
let mut out = Vec::new();
for (project, live) in projects {
for ball in live.iter().filter(|b| b.claimant.as_deref() == Some(name)) {
out.push(Plan {
project: project.clone(),
ball_id: ball.id.clone(),
target_ball: target_ball(ball, live),
});
}
}
out
}
fn target_ball(ball: &Ball, live: &[Ball]) -> Option<String> {
let parent = ball.parent.as_deref()?;
let gated = live
.iter()
.find(|b| b.id == parent)?
.blockers
.iter()
.any(|b| b.id == ball.id && b.on == ON_CLOSE);
gated.then(|| parent.to_owned())
}
pub(super) fn parse_numstat(stdout: &[u8]) -> Vec<FileChurn> {
String::from_utf8_lossy(stdout)
.lines()
.filter_map(parse_row)
.collect()
}
pub(super) fn parse_row(line: &str) -> Option<FileChurn> {
let mut fields = line.splitn(NUMSTAT_FIELDS, '\t');
let (added, removed, path) = (fields.next()?, fields.next()?, fields.next()?);
if path.is_empty() {
return None;
}
let churn = match (added, removed) {
(NO_COUNT, NO_COUNT) => Churn::Binary,
_ => Churn::Text {
added: added.parse().ok()?,
removed: removed.parse().ok()?,
},
};
Some(FileChurn {
path: path.to_owned(),
churn,
})
}