1use std::io::Result;
2use std::process::Command;
3
4use colored::Colorize;
5use spinoff::{spinners, Color, Spinner};
6
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Serialize, Deserialize)]
10pub struct Package {
11 name: String,
12 version: String,
13 latest_version: String,
14}
15
16impl Package {
17 fn to_formatted(&self) -> Self {
18 Self {
19 name: self.name.clone(),
20 version: format!("v{}", self.version).bright_red().to_string(),
21 latest_version: format!("v{}", self.latest_version)
22 .bright_green()
23 .to_string(),
24 }
25 }
26}
27
28fn get_outdated() -> Result<Vec<Package>> {
30 let mut spinner = Spinner::new(
31 spinners::Dots,
32 "Scanning for outdated packages...",
33 Color::Cyan,
34 );
35 let output = Command::new("pip")
37 .args(["list", "--outdated", "--not-required", "--format=json"])
38 .output()?;
39
40 let packages: Vec<Package> = match serde_json::from_slice(&output.stdout) {
42 Ok(packages) => packages,
43 Err(error) => {
44 spinner.fail(&format!("Failed to parse json:\n {error}"));
45 return Ok(vec![]);
46 }
47 };
48
49 spinner.clear();
50
51 Ok(packages)
52}
53
54pub fn show_outdated() -> Result<()> {
60 let packages = get_outdated()?;
61 if packages.is_empty() {
62 return Ok(());
63 }
64 for package in packages {
65 let formatted = package.to_formatted();
66 println!(
67 "🐍 {}: {} -> {}",
68 formatted.name, formatted.version, formatted.latest_version
69 );
70 }
71
72 Ok(())
73}
74
75pub fn update_all() -> Result<()> {
81 for package in get_outdated()? {
83 let formatted = package.to_formatted();
84
85 let message = format!(
86 "Upgrading {} from {} to {}",
87 formatted.name, formatted.version, formatted.latest_version
88 );
89
90 let mut spinner = Spinner::new(spinners::Dots, message, Color::Cyan);
91
92 let output = std::process::Command::new("pip")
93 .args(["install", "--upgrade", &formatted.name])
94 .output()?;
95
96 let stdout = String::from_utf8(output.stdout).unwrap_or_default();
97 let stdout_output = stdout.lines().last().unwrap_or_default().trim();
98
99 match output.status.code().unwrap_or(1) {
100 0 => spinner.success(stdout_output),
101 1 => spinner.fail(stdout_output),
102 _ => spinner.warn(stdout_output),
103 }
104 }
105
106 Ok(())
107}