m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-16 09:03:46 -05:00

Update API and improve CLI output

This commit is contained in:
Sergio
2025-02-14 18:28:18 +02:00
parent 2262df0355
commit 6ae95bf83b
6 changed files with 163 additions and 31 deletions

View File

@@ -47,7 +47,7 @@ The data returned from the API or from the CLI is in JSON and looks like this:
"remote_digest": "sha256:170f1974d8fc8ca245bcfae5590bc326de347b19719972bf122400fb13dfa42c", // Latest digest available in the registry "remote_digest": "sha256:170f1974d8fc8ca245bcfae5590bc326de347b19719972bf122400fb13dfa42c", // Latest digest available in the registry
// If `type` is "version": // If `type` is "version":
"version_update_type": "major", // Loosely corresponds to SemVer versioning. Can also be `minor` or `patch`. "version_update_type": "major", // Loosely corresponds to SemVer versioning. Can also be `minor` or `patch`.
"new_version": "v3.3.3", // The tag of the latest image. "new_tag": "v3.3.3", // The tag of the latest image.
}, },
"error": null, // If checking for the image fails, will be a string with an error message. "error": null, // If checking for the image fails, will be a string with an error message.
}, },

View File

@@ -1,39 +1,164 @@
pub mod spinner; pub mod spinner;
use rustc_hash::FxHashMap;
use crate::{ use crate::{
structs::{status::Status, update::Update}, structs::{
status::Status,
update::{Update, UpdateInfo},
},
utils::{json::to_simple_json, sort_update_vec::sort_update_vec}, utils::{json::to_simple_json, sort_update_vec::sort_update_vec},
}; };
pub fn print_updates(updates: &[Update], icons: &bool) { pub fn print_updates(updates: &[Update], icons: &bool) {
let sorted_images = sort_update_vec(updates); let sorted_updates = sort_update_vec(updates);
let term_width: usize = termsize::get() let updates_by_server = {
.unwrap_or(termsize::Size { rows: 24, cols: 80 }) let mut servers: FxHashMap<&str, Vec<&Update>> = FxHashMap::default();
.cols as usize; sorted_updates.iter().for_each(|update| {
for image in sorted_images { let key = update.server.as_deref().unwrap_or("");
let has_update = image.get_status(); match servers.get_mut(&key) {
let description = has_update.to_string(); Some(server) => server.push(update),
let icon = if *icons { None => {
match has_update { let _ = servers.insert(key, vec![update]);
Status::UpToDate => "\u{f058} ", }
Status::Unknown(_) => "\u{f059} ",
_ => "\u{f0aa} ",
} }
});
servers
};
for (server, updates) in updates_by_server {
if server.is_empty() {
println!("\x1b[90;1m~ Local images\x1b[0m")
} else { } else {
"" println!("\x1b[90;1m~ {}\x1b[0m", server)
}; }
let color = match has_update { let (reference_width, status_width, time_width) =
Status::UpdateAvailable | Status::UpdatePatch => "\u{001b}[38;5;12m", updates.iter().fold((9, 6, 9), |acc, update| {
Status::UpdateMinor => "\u{001b}[38;5;3m", let reference_length = update.reference.len();
Status::UpdateMajor => "\u{001b}[38;5;1m", let status_length = update.get_status().to_string().len()
Status::UpToDate => "\u{001b}[38;5;2m", + match &update.result.info {
Status::Unknown(_) => "\u{001b}[38;5;8m", UpdateInfo::Version(info) => {
}; info.current_version.len() + info.new_version.len() + 6
let dynamic_space = }
" ".repeat(term_width - description.len() - icon.len() - image.reference.len()); _ => 0,
};
let time_length = update.time.to_string().len();
return (
if reference_length > acc.0 {
reference_length
} else {
acc.0
},
if status_length > acc.1 {
status_length
} else {
acc.1
},
if time_length > acc.2 {
time_length
} else {
acc.2
},
);
});
println!( println!(
"{}{}{}{}{}\u{001b}[0m", " \x1b[90;1m╭{:─<rw$}{:─<sw$}{:─<tw$}\x1b[0m",
color, icon, image.reference, dynamic_space, description "",
"",
"",
rw = reference_width,
sw = status_width + {
if *icons {
2
} else {
0
}
},
tw = time_width
);
println!(
" \x1b[90;1m│\x1b[36;1m{:<rw$}\x1b[90;1m│\x1b[36;1m{:<sw$}\x1b[90;1m│\x1b[36;1m{:<tw$}\x1b[90;1m│\x1b[0m",
"Reference",
"Status",
"Time (ms)",
rw = reference_width,
sw = status_width + {
if *icons {
2
} else {
0
}
},
tw = time_width
);
println!(
" \x1b[90;1m├{:─<rw$}{:─<sw$}{:─<tw$}\x1b[0m",
"",
"",
"",
rw = reference_width,
sw = status_width + {
if *icons {
2
} else {
0
}
},
tw = time_width
);
for update in updates {
let status = update.get_status();
let icon = if *icons {
match status {
Status::UpToDate => "\u{f058} ",
Status::Unknown(_) => "\u{f059} ",
_ => "\u{f0aa} ",
}
} else {
""
};
let color = match status {
Status::UpdateAvailable | Status::UpdatePatch => "\x1b[34m",
Status::UpdateMinor => "\x1b[33m",
Status::UpdateMajor => "\x1b[31m",
Status::UpToDate => "\x1b[32m",
Status::Unknown(_) => "\x1b[90m",
};
let description = format!(
"{} {}",
status.to_string(),
match &update.result.info {
UpdateInfo::Version(info) => {
format!("({}{})", info.current_version, info.new_version)
}
_ => String::new(),
}
);
println!(
" \x1b[90;1m│\x1b[0m{:<rw$}\x1b[90;1m│\x1b[0m{}{}{:<sw$}\x1b[0m\x1b[90;1m│\x1b[0m{:<tw$}\x1b[90;1m│\x1b[0m",
update.reference,
color,
icon,
description,
update.time,
rw = reference_width,
sw = status_width,
tw = time_width
);
}
println!(
" \x1b[90;1m╰{:─<rw$}{:─<sw$}{:─<tw$}\x1b[0m",
"",
"",
"",
rw = reference_width,
sw = status_width + {
if *icons {
2
} else {
0
}
},
tw = time_width
); );
} }
} }

View File

@@ -163,10 +163,13 @@ impl Image {
_ => unreachable!(), _ => unreachable!(),
} }
.to_string(), .to_string(),
new_version: format_str new_tag: format_str
.replacen("{}", &new_tag.major.to_string(), 1) .replacen("{}", &new_tag.major.to_string(), 1)
.replacen("{}", &new_tag.minor.unwrap_or(0).to_string(), 1) .replacen("{}", &new_tag.minor.unwrap_or(0).to_string(), 1)
.replacen("{}", &new_tag.patch.unwrap_or(0).to_string(), 1), .replacen("{}", &new_tag.patch.unwrap_or(0).to_string(), 1),
// Throwing these in, because they're useful for the CLI output, however we won't (de)serialize them
current_version: self.version_info.as_ref().unwrap().current_tag.to_string(),
new_version: self.version_info.as_ref().unwrap().latest_remote_tag.as_ref().unwrap().to_string()
}) })
} }
"digest" => { "digest" => {

View File

@@ -36,6 +36,10 @@ pub enum UpdateInfo {
#[cfg_attr(test, derive(PartialEq, Debug))] #[cfg_attr(test, derive(PartialEq, Debug))]
pub struct VersionUpdateInfo { pub struct VersionUpdateInfo {
pub version_update_type: String, pub version_update_type: String,
pub new_tag: String,
#[serde(skip_serializing, skip_deserializing)]
pub current_version: String,
#[serde(skip_serializing, skip_deserializing)]
pub new_version: String, pub new_version: String,
} }
@@ -54,7 +58,7 @@ impl Serialize for VersionUpdateInfo {
let mut state = serializer.serialize_struct("VersionUpdateInfo", 3)?; let mut state = serializer.serialize_struct("VersionUpdateInfo", 3)?;
let _ = state.serialize_field("type", "version"); let _ = state.serialize_field("type", "version");
let _ = state.serialize_field("version_update_type", &self.version_update_type); let _ = state.serialize_field("version_update_type", &self.version_update_type);
let _ = state.serialize_field("new_version", &self.new_version); let _ = state.serialize_field("new_version", &self.new_tag);
state.end() state.end()
} }
} }

View File

@@ -36,7 +36,7 @@ export default function Image({ data }: { data: Image }) {
}; };
const new_reference = const new_reference =
data.result.info?.type == "version" data.result.info?.type == "version"
? data.reference.split(":")[0] + ":" + data.result.info.new_version ? data.reference.split(":")[0] + ":" + data.result.info.new_tag
: data.reference; : data.reference;
let url: string | null = null; let url: string | null = null;
if (clickable_registries.includes(data.parts.registry)) { if (clickable_registries.includes(data.parts.registry)) {

View File

@@ -32,7 +32,7 @@ export interface Image {
interface VersionInfo { interface VersionInfo {
type: "version"; type: "version";
version_update_type: "major" | "minor" | "patch"; version_update_type: "major" | "minor" | "patch";
new_version: string; new_tag: string;
} }
interface DigestInfo { interface DigestInfo {