diff --git a/Cargo.lock b/Cargo.lock index 95ed244..de6fb9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,9 +114,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indicatif" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console", "portable-atomic", diff --git a/Cargo.toml b/Cargo.toml index 9f2c3fb..ef77776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ opt-level = 3 [dependencies] color-eyre = "0.6.5" console = "0.16.1" -indicatif = "0.18.2" +indicatif = "0.18.3" owo-colors = "4.2.3" serde = { version = "1.0.228", features = ["derive"] } serde_json = { version = "1.0.145", features = ["raw_value"] } diff --git a/src/download_display.rs b/src/download_display.rs index 65924e6..a9565f9 100644 --- a/src/download_display.rs +++ b/src/download_display.rs @@ -1,140 +1,46 @@ -pub const DOWNLOAD_STYLE: owo_colors::Style = owo_colors::Style::new().blue().bold(); -pub const EXTRACT_STYLE: owo_colors::Style = owo_colors::Style::new().green().bold(); -use std::{borrow::Cow, fmt::Display}; - use indicatif::HumanBytes; -use owo_colors::Style; -use crate::multibar::MultiBar; - -pub enum StyledStringInner<'a> { - Cow(Cow<'a, str>), - Styled(Box>), -} - -impl<'a> From> for StyledStringInner<'a> { - fn from(value: Cow<'a, str>) -> Self { - StyledStringInner::Cow(value) - } -} -impl<'a> From> for StyledStringInner<'a> { - fn from(value: StyledString<'a>) -> Self { - StyledStringInner::Styled(Box::new(value)) - } -} -impl<'a> StyledStringInner<'a> { - pub fn len(&self) -> usize { - match self { - StyledStringInner::Cow(cow) => cow.len(), - StyledStringInner::Styled(styled) => styled.len(), - } - } - pub fn pad_to_width(self, width: usize) -> StyledStringInner<'a> { - let len = self.len(); - if len == width { - self - } else if len > width { - match self { - StyledStringInner::Cow(cow) => StyledStringInner::Cow(Cow::Owned( - cow.chars() - .take(width.saturating_sub(2)) - .collect::() - + "..", - )), - StyledStringInner::Styled(styled) => styled.pad_to_width(width).into(), - } - } else { - match self { - StyledStringInner::Cow(cow) => StyledStringInner::Cow(Cow::Owned(format!( - "{}{}", - cow, - " ".repeat(width - len) - ))), - StyledStringInner::Styled(styled) => styled.pad_to_width(width).into(), - } - } - } -} - -pub struct StyledString<'a> { - inner: StyledStringInner<'a>, - style: Style, -} - -impl<'a> StyledString<'a> { - pub fn new(inner: impl Into>, style: Style) -> Self { - Self { - inner: inner.into(), - style, - } - } - pub fn len(&self) -> usize { - self.inner.len() - } - pub fn pad_to_width(self, width: usize) -> StyledString<'a> { - if self.inner.len() == width { - self - } else { - StyledString { - inner: self.inner.pad_to_width(width), - style: self.style, - } - } - } -} - -impl<'a> Display for StyledStringInner<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - StyledStringInner::Cow(cow) => write!(f, "{}", cow), - StyledStringInner::Styled(styled) => write!(f, "{}", styled), - } - } -} - -impl<'a> Display for StyledString<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.style.style(self.inner.to_string())) - } -} - -// {prefix} {name} | {work_per_sec} | {download_done_human} | {extract_done_human} [bar] -// -// -const NAME_WIDTH_CUT: usize = 4; -const NAME_WIDTH_MIN: usize = 5; -const INFO_WIDTH: usize = 12; +use crate::{ + multibar::MultiBar, + util::{ + AStrExt, DOWNLOAD_STYLE, EXTRACT_STYLE, INFO_WIDTH, NAME_WIDTH_CUT, NAME_WIDTH_MIN, + pad_string, + }, +}; pub fn style_bar<'s, const N: usize>( - prefix: StyledString, - name: StyledString, - work_per_sec: StyledString, + prefix: &str, + name: &str, + work_per_sec: &str, download_done: usize, extract_done: usize, bar: MultiBar<'s, N>, - width: u16, ) -> String { - let name = name.pad_to_width((width as usize / NAME_WIDTH_CUT).max(NAME_WIDTH_MIN)); + let name = pad_string(name, (width as usize / NAME_WIDTH_CUT).max(NAME_WIDTH_MIN)); let status = format!("{} {}", prefix, name); - let remaining_width = width.saturating_sub((name.len() + prefix.len()) as u16 + 6); + let remaining_width = + width.saturating_sub((name.no_ansi_len() + prefix.no_ansi_len()) as u16 + 6); if remaining_width == 0 { return status; } let possible_chunks = remaining_width.saturating_div(INFO_WIDTH as u16 + 3); let possible_chunks = possible_chunks.min(3); let leftover_width = remaining_width.saturating_sub(possible_chunks * (INFO_WIDTH as u16 + 3)); - let download_done_human = StyledString::new( - Cow::Owned(HumanBytes(download_done as u64).to_string()), - DOWNLOAD_STYLE, - ) - .pad_to_width(INFO_WIDTH); - let extract_done_human = StyledString::new( - Cow::Owned(HumanBytes(extract_done as u64).to_string()), - EXTRACT_STYLE, - ) - .pad_to_width(INFO_WIDTH); - let work_per_sec = work_per_sec.pad_to_width(INFO_WIDTH); + let download_done_human = pad_string( + DOWNLOAD_STYLE + .style(HumanBytes(download_done as u64).to_string()) + .to_string(), + INFO_WIDTH, + ); + + let extract_done_human = pad_string( + EXTRACT_STYLE + .style(HumanBytes(extract_done as u64).to_string()) + .to_string(), + INFO_WIDTH, + ); + let work_per_sec = pad_string(work_per_sec, INFO_WIDTH); let bar = bar.scale(leftover_width as u64).to_string(); match possible_chunks { @@ -148,104 +54,3 @@ pub fn style_bar<'s, const N: usize>( } } } - -// pub fn format(width: u16) { -// let name = pad_string(&name, (width / 4) as usize); -// let status = format!("Download {} |", name.purple().bold()); -// let status_len = (width / 4) as usize + 10; -// -// if self.download_expected == 0 || self.extract_expected == 0 { -// self.bar.set_message(format!("{status}")); -// self.bar.tick(); -// return; -// } -// -// let total_expected = self.download_expected + self.extract_expected; -// let total_done = self.download_done + self.extract_done; -// -// let dl_percent = ((self.download_done as f64 / self.download_expected as f64) * 100.0) as u64; -// let ex_percent = ((self.extract_done as f64 / self.extract_expected as f64) * 100.0) as u64; -// -// let download_per_sec = -// HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64).to_string(); -// let extract_per_sec = -// HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64).to_string(); -// let download_done_human = HumanBytes(self.download_done).to_string(); -// let download_done_human = pad_string(&download_done_human, INFO_WIDTH) -// .blue() -// .bold() -// .to_string(); -// let extract_done_human = HumanBytes(self.extract_done).to_string(); -// let extract_done_human = pad_string(&extract_done_human, INFO_WIDTH) -// .green() -// .bold() -// .to_string(); -// let download_per_sec = pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH) -// .blue() -// .bold() -// .to_string(); -// let extract_per_sec = pad_string(&format!("{extract_per_sec}/s"), INFO_WIDTH) -// .green() -// .bold() -// .to_string(); -// let work_per_sec = if self.download_done < self.download_expected { -// download_per_sec -// } else { -// extract_per_sec -// }; -// -// let display = format!("{work_per_sec} | {download_done_human} | {extract_done_human} "); -// let display_length = (INFO_WIDTH * 3) + 9; -// -// // + 6 to account for final format -// let total_length = status_len + display_length + 4; -// -// let min = dl_percent.min(ex_percent); -// let dl = dl_percent.saturating_sub(min); -// let len = total_length as u16; -// // if width <= len { -// // self.bar.set_message(format!( -// // "{}: {}/{}", -// // name, -// // total_done, -// // if total_expected == 0 { -// // "-".to_string() -// // } else { -// // total_expected.to_string() -// // } -// // )); -// // self.bar.tick(); -// // return; -// // } -// let msg = match width { -// 0..60 => { -// format!( -// "{}: {}/{}", -// name, -// total_done, -// if total_expected == 0 { -// "-".to_string() -// } else { -// total_expected.to_string() -// } -// ) -// } -// _ => { -// let bar = MultiBar([ -// BarSegment::Dynamic(&DONE_CHAR, min), -// BarSegment::Dynamic(&DOWNLOAD_CHAR, dl), -// BarSegment::Dynamic(" ", 100 - min - dl), -// ]) -// .scale((width.saturating_sub(total_length as u16)) as u64); -// let bar = if width > display_length as u16 + status_len as u16 { -// format!("[{}]", bar) -// } else { -// String::new() -// }; -// -// format!("{status} {display} {bar}",) -// } -// }; -// self.bar.set_message(msg); -// self.bar.tick(); -// } diff --git a/src/handlers/download.rs b/src/handlers/download.rs index eb8e590..e43d0fd 100644 --- a/src/handlers/download.rs +++ b/src/handlers/download.rs @@ -1,16 +1,16 @@ use std::{borrow::Cow, time::Instant}; use indicatif::{HumanBytes, ProgressBar}; -use owo_colors::Style; +use owo_colors::{OwoColorize, Style}; use crate::{ - DONE_CHAR, DOWNLOAD_CHAR, action::{Action, BuildStepId, ResultFields, StartFields}, - download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar}, + download_display::style_bar, estimator::Estimator, handlers::Handler, multibar::{BarSegment, MultiBar}, nix_path, + util::{DONE_CHAR, DOWNLOAD_CHAR, DOWNLOAD_STYLE, EXTRACT_STYLE}, }; pub struct SubstituteHandler; @@ -114,21 +114,19 @@ impl DownloadHandler { } let work_per_sec = if self.download_done < self.download_expected { - StyledString::new( - Cow::Owned(format!( + DOWNLOAD_STYLE + .style(format!( "{}", HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64) - )), - DOWNLOAD_STYLE, - ) + )) + .to_string() } else { - StyledString::new( - Cow::Owned(format!( + EXTRACT_STYLE + .style(format!( "{}", HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64) - )), - EXTRACT_STYLE, - ) + )) + .to_string() }; let dl_percent = @@ -142,9 +140,9 @@ impl DownloadHandler { BarSegment::Dynamic(" ", 100 - min - dl), ]); let msg = style_bar( - StyledString::new(Cow::Owned("Downloading".to_string()), Style::new()), - StyledString::new(Cow::Owned(name), Style::new().purple()), - work_per_sec, + "Downloading", + &(name.purple().to_string()), + &work_per_sec, self.download_done as usize, self.extract_done as usize, mbar, diff --git a/src/handlers/fetch.rs b/src/handlers/fetch.rs index 23d1845..5032748 100644 --- a/src/handlers/fetch.rs +++ b/src/handlers/fetch.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use indicatif::ProgressBar; use owo_colors::OwoColorize; -use crate::{action::Action, handlers::Handler, nix_path, pad_string}; +use crate::{action::Action, handlers::Handler, nix_path, util::pad_string}; pub struct FetchHandler { id: u64, diff --git a/src/handlers/substitute_status.rs b/src/handlers/substitute_status.rs index bfe08b4..d68fdf2 100644 --- a/src/handlers/substitute_status.rs +++ b/src/handlers/substitute_status.rs @@ -1,15 +1,15 @@ use std::{borrow::Cow, collections::HashMap, time::Instant}; use indicatif::{HumanBytes, ProgressBar}; -use owo_colors::Style; +use owo_colors::{OwoColorize, Style}; use crate::{ - DONE_CHAR, DOWNLOAD_CHAR, INPROGRESS_CHAR, action::{Action, ActionType, BuildStepId, ResultFields, StartFields}, - download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar}, + download_display::style_bar, estimator::Estimator, handlers::Handler, multibar::{BarSegment, MultiBar}, + util::{DONE_CHAR, DOWNLOAD_CHAR, DOWNLOAD_STYLE, EXTRACT_STYLE, INPROGRESS_CHAR}, }; pub struct CopyPathsHandler; @@ -116,32 +116,24 @@ impl SubstitutionStatusHandler { ]); let work_per_sec = if self.get_done() < self.max_transfer { - StyledString::new( - Cow::Owned(format!( + DOWNLOAD_STYLE + .style(format!( "{}", HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64) - )), - DOWNLOAD_STYLE, - ) + )) + .to_string() } else { - StyledString::new( - Cow::Owned(format!( + EXTRACT_STYLE + .style(format!( "{}", HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64) - )), - EXTRACT_STYLE, - ) + )) + .to_string() }; let msg = style_bar( - StyledString::new(Cow::Owned("Downloading".to_string()), Style::new()), - StyledString::new( - Cow::Owned(format!( - "{}/{} {min} {dl} {expected}", - self.state_self[0], self.state_self[1] - )), - Style::new().purple(), - ), - work_per_sec, + "Downloading", + &(format!("{}/{}", self.state_self[0].purple(), self.state_self[1])), + &work_per_sec, self.get_done() as usize, self.get_unpacked() as usize, mbar, diff --git a/src/main.rs b/src/main.rs index 043efdb..6247e33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,20 +15,7 @@ pub mod handlers; pub mod multibar; pub mod nix_path; pub mod state; - -pub static DOWNLOAD_CHAR: LazyLock = LazyLock::new(|| "-".blue().bold().to_string()); -pub static DONE_CHAR: LazyLock = LazyLock::new(|| "=".green().bold().to_string()); -pub static INPROGRESS_CHAR: LazyLock = LazyLock::new(|| "-".purple().bold().to_string()); - -pub fn pad_string(s: &str, width: usize) -> String { - if s.len() >= width { - s.to_string() - } else { - let mut padded = s.to_string(); - padded.push_str(&" ".repeat(width - s.len())); - padded - } -} +pub mod util; fn main() -> Result<(), color_eyre::Report> { color_eyre::install().unwrap(); diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..3028d03 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,67 @@ +use std::{borrow::Cow, sync::LazyLock}; + +use console::strip_ansi_codes; +use owo_colors::{OwoColorize, Style}; + +pub const DOWNLOAD_STYLE: Style = Style::new().blue().bold(); +pub const EXTRACT_STYLE: Style = Style::new().green().bold(); +pub const NAME_WIDTH_CUT: usize = 4; +pub const NAME_WIDTH_MIN: usize = 5; +pub const INFO_WIDTH: usize = 12; +pub static DOWNLOAD_CHAR: LazyLock = LazyLock::new(|| "-".blue().bold().to_string()); +pub static DONE_CHAR: LazyLock = LazyLock::new(|| "=".green().bold().to_string()); +pub static INPROGRESS_CHAR: LazyLock = LazyLock::new(|| "-".purple().bold().to_string()); + +pub trait StrExt<'a>: Into> { + fn pad_string(self, width: usize) -> String { + let s = self.into(); + let len = s.no_ansi_len(); + if len == width { + s.to_string() + } else if len > width { + s.chars().take(width.saturating_sub(2)).collect::() + ".." + } else { + let mut padded = s.to_string(); + padded.push_str(&" ".repeat(width - len)); + padded + } + } +} + +pub trait AStrExt { + fn no_ansi_len(&self) -> usize; +} +impl AStrExt for String { + fn no_ansi_len(&self) -> usize { + let s = strip_ansi_codes(self); + s.len() + } +} +impl AStrExt for &str { + fn no_ansi_len(&self) -> usize { + let s = strip_ansi_codes(self); + s.len() + } +} +impl AStrExt for str { + fn no_ansi_len(&self) -> usize { + let s = strip_ansi_codes(self); + s.len() + } +} +impl<'a, T: Into>> StrExt<'a> for T {} + +pub fn pad_string<'a, S: Into>>(s: S, width: usize) -> String { + let s = s.into(); + let trimmed = strip_ansi_codes(&s); + let len = trimmed.len(); + if len == width { + s.to_string() + } else if len > width { + s.chars().take(width.saturating_sub(2)).collect::() + ".." + } else { + let mut padded = s.to_string(); + padded.push_str(&" ".repeat(width - len)); + padded + } +}