use std::path::Path;
use std::time::SystemTime;
#[inline]
fn calculate_freshness_from_lock_age(lock_file_path: &Path) -> f32 {
let metadata = match std::fs::metadata(lock_file_path) {
Ok(m) => m,
Err(_) => return 0.5, };
let modified = match metadata.modified() {
Ok(time) => time,
Err(_) => return 0.5, };
let now = SystemTime::now();
let age = match now.duration_since(modified) {
Ok(duration) => duration,
Err(_) => return 0.5, };
let days = age.as_secs() / 86400;
match days {
0..=30 => 1.0, 31..=90 => 0.9, 91..=180 => 0.8, 181..=365 => 0.6, 366..=730 => 0.4, 731..=1095 => 0.2, _ => 0.1, }
}
#[inline]
fn calculate_freshness_from_timestamp(modified: SystemTime) -> f32 {
let now = SystemTime::now();
let age = match now.duration_since(modified) {
Ok(duration) => duration,
Err(_) => return 0.5,
};
let days = age.as_secs() / 86400;
match days {
0..=30 => 1.0,
31..=90 => 0.9,
91..=180 => 0.8,
181..=365 => 0.6,
366..=730 => 0.4,
731..=1095 => 0.2,
_ => 0.1,
}
}
#[inline]
fn get_most_recent_lock_file(repo_path: &Path, lock_files: &[&str]) -> Option<SystemTime> {
lock_files
.iter()
.filter_map(|&filename| {
let path = repo_path.join(filename);
std::fs::metadata(&path).ok()?.modified().ok()
})
.max() }
pub(crate) fn calculate_dependency_freshness(
repo_path: &Path,
package_managers: &[String],
) -> f32 {
if package_managers.is_empty() {
return 0.0; }
let mut first_score: Option<f32> = None;
let mut additional_scores: Vec<f32> = Vec::new();
for pm in package_managers {
let score = match pm.as_str() {
"Cargo" => {
let lock_path = repo_path.join("Cargo.lock");
if lock_path.exists() {
calculate_freshness_from_lock_age(&lock_path)
} else {
continue;
}
}
"npm" => {
let lock_files = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"];
if let Some(most_recent) = get_most_recent_lock_file(repo_path, &lock_files) {
calculate_freshness_from_timestamp(most_recent)
} else {
0.4 }
}
"pip" => {
let lock_files = ["Pipfile.lock", "poetry.lock"];
if let Some(most_recent) = get_most_recent_lock_file(repo_path, &lock_files) {
calculate_freshness_from_timestamp(most_recent)
} else {
let req_path = repo_path.join("requirements.txt");
if req_path.exists() {
calculate_freshness_from_lock_age(&req_path)
} else {
0.4
}
}
}
"Go modules" => {
let lock_path = repo_path.join("go.sum");
if lock_path.exists() {
calculate_freshness_from_lock_age(&lock_path)
} else {
0.4
}
}
_ => continue,
};
match first_score {
None => first_score = Some(score),
Some(_) => additional_scores.push(score),
}
}
match (first_score, additional_scores.is_empty()) {
(None, _) => 0.4, (Some(score), true) => score, (Some(first), false) => {
let sum = first + additional_scores.iter().sum::<f32>();
sum / (1 + additional_scores.len()) as f32
}
}
}