use crate::global::{GlobalCheck, GlobalSource};
use crate::uv_python::UvPythonCheck;
use check_updates_core::UpdateSeverity;
use colored::Colorize;
use std::collections::BTreeMap;
pub use check_updates_core::TableRenderer;
pub struct GlobalTableRenderer {
show_colors: bool,
}
impl GlobalTableRenderer {
pub fn new(show_colors: bool) -> Self {
Self { show_colors }
}
pub fn render(&self, checks: &[GlobalCheck]) {
if checks.is_empty() {
return;
}
let mut uv_checks: Vec<&GlobalCheck> = Vec::new();
let mut pipx_checks: Vec<&GlobalCheck> = Vec::new();
let mut pip_by_python: BTreeMap<String, Vec<&GlobalCheck>> = BTreeMap::new();
for check in checks {
match &check.package.source {
GlobalSource::Uv => uv_checks.push(check),
GlobalSource::Pipx => pipx_checks.push(check),
GlobalSource::PipUser => {
let py_version = check
.package
.python_version
.clone()
.unwrap_or_else(|| "unknown".to_string());
pip_by_python
.entry(py_version)
.or_default()
.push(check);
}
}
}
let mut first_group = true;
if !uv_checks.is_empty() {
if !first_group {
println!();
}
first_group = false;
self.render_group_or_uptodate("uv tools:", &uv_checks);
}
if !pipx_checks.is_empty() {
if !first_group {
println!();
}
first_group = false;
self.render_group_or_uptodate("pipx:", &pipx_checks);
}
for (py_version, pip_checks) in &pip_by_python {
if !first_group {
println!();
}
first_group = false;
let header = format!("pip --user (Python {py_version}):");
self.render_group_or_uptodate(&header, pip_checks);
}
}
fn render_group_or_uptodate(&self, header: &str, checks: &[&GlobalCheck]) {
let updates: Vec<&GlobalCheck> = checks.iter().filter(|c| c.has_update).copied().collect();
println!("{header}");
if updates.is_empty() {
println!(" All packages up to date.");
} else {
self.render_group_rows(&updates);
}
}
fn render_group_rows(&self, checks: &[&GlobalCheck]) {
let max_name = checks.iter().map(|c| c.package.name.len()).max().unwrap_or(0);
let max_installed = checks
.iter()
.map(|c| c.package.installed_version.to_string().len())
.max()
.unwrap_or(0);
let max_latest = checks
.iter()
.map(|c| c.latest.to_string().len())
.max()
.unwrap_or(0);
let mut sorted_checks = checks.to_vec();
sorted_checks.sort_by_key(|a| a.package.name.to_lowercase());
for check in sorted_checks {
let severity_str = match check.update_severity() {
Some(UpdateSeverity::Major) => {
if self.show_colors {
"MAJOR".red().to_string()
} else {
"MAJOR".to_string()
}
}
Some(UpdateSeverity::Minor) => {
if self.show_colors {
"minor".yellow().to_string()
} else {
"minor".to_string()
}
}
Some(UpdateSeverity::Patch) => {
if self.show_colors {
"patch".green().to_string()
} else {
"patch".to_string()
}
}
None => String::new(),
};
println!(
" {:<name_w$} {:>inst_w$} → {:<to_w$} {}",
check.package.name,
check.package.installed_version.to_string(),
check.latest.to_string(),
severity_str,
name_w = max_name,
inst_w = max_installed,
to_w = max_latest,
);
}
}
}
pub struct UvPythonTableRenderer {
show_colors: bool,
}
impl UvPythonTableRenderer {
pub fn new(show_colors: bool) -> Self {
Self { show_colors }
}
pub fn render(&self, checks: &[UvPythonCheck]) {
if checks.is_empty() {
return;
}
let updates: Vec<&UvPythonCheck> = checks.iter().filter(|c| c.has_update).collect();
println!("uv-managed Python installations:");
if updates.is_empty() {
println!(" All Python versions up to date.");
return;
}
let max_series = updates.iter().map(|c| c.series.len()).max().unwrap_or(0);
let max_installed = updates
.iter()
.map(|c| c.installed_version.to_string().len())
.max()
.unwrap_or(0);
let mut sorted = updates.clone();
sorted.sort_by(|a, b| a.series.cmp(&b.series));
for check in sorted {
let severity_str = if self.show_colors {
if check.is_patch_update() {
"patch".green().to_string()
} else {
"minor".yellow().to_string()
}
} else if check.is_patch_update() {
"patch".to_string()
} else {
"minor".to_string()
};
println!(
" {:<series_w$} {:>inst_w$} → {} {}",
check.series,
check.installed_version.to_string(),
check.latest_version,
severity_str,
series_w = max_series,
inst_w = max_installed,
);
}
}
}