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
// If `type` is "version":
"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.
},

View File

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

View File

@@ -36,6 +36,10 @@ pub enum UpdateInfo {
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct VersionUpdateInfo {
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,
}
@@ -54,7 +58,7 @@ impl Serialize for VersionUpdateInfo {
let mut state = serializer.serialize_struct("VersionUpdateInfo", 3)?;
let _ = state.serialize_field("type", "version");
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()
}
}

View File

@@ -36,7 +36,7 @@ export default function Image({ data }: { data: Image }) {
};
const new_reference =
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;
let url: string | null = null;
if (clickable_registries.includes(data.parts.registry)) {

View File

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