Compare commits

..

3 Commits

Author SHA1 Message Date
f7fe3b0bb0 more reworking 2025-11-15 08:26:07 +00:00
0b68d803e8 cargo clippy 2025-11-15 07:54:29 +00:00
01743c3c75 cleanup 2025-11-15 07:51:32 +00:00
14 changed files with 149 additions and 791 deletions

4
Cargo.lock generated
View File

@@ -114,9 +114,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
[[package]] [[package]]
name = "indicatif" name = "indicatif"
version = "0.18.2" version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88"
dependencies = [ dependencies = [
"console", "console",
"portable-atomic", "portable-atomic",

View File

@@ -15,7 +15,7 @@ opt-level = 3
[dependencies] [dependencies]
color-eyre = "0.6.5" color-eyre = "0.6.5"
console = "0.16.1" console = "0.16.1"
indicatif = "0.18.2" indicatif = "0.18.3"
owo-colors = "4.2.3" owo-colors = "4.2.3"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.145", features = ["raw_value"] } serde_json = { version = "1.0.145", features = ["raw_value"] }

View File

@@ -15,7 +15,9 @@ use crate::action_raw::RawAction;
#[derive(Clone, Copy, Debug, Deserialize_repr)] #[derive(Clone, Copy, Debug, Deserialize_repr)]
#[repr(u8)] #[repr(u8)]
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
#[derive(Default)]
pub enum ActionType { pub enum ActionType {
#[default]
Unknown = 0, Unknown = 0,
CopyPath = 100, CopyPath = 100,
FileTransfer = 101, FileTransfer = 101,
@@ -32,11 +34,6 @@ pub enum ActionType {
FetchTree = 112, FetchTree = 112,
} }
impl Default for ActionType {
fn default() -> Self {
Self::Unknown
}
}
// --- // ---
// --- BuildStepId // --- BuildStepId

View File

@@ -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, rc::Rc};
use indicatif::HumanBytes; use indicatif::HumanBytes;
use owo_colors::Style;
use crate::multibar::MultiBar; use crate::{
multibar::MultiBar,
pub enum StyledStringInner<'a> { util::{
Cow(Cow<'a, str>), AStrExt, DOWNLOAD_STYLE, EXTRACT_STYLE, INFO_WIDTH, NAME_WIDTH_CUT, NAME_WIDTH_MIN,
Styled(Box<StyledString<'a>>), pad_string,
} },
};
impl<'a> From<Cow<'a, str>> for StyledStringInner<'a> {
fn from(value: Cow<'a, str>) -> Self {
StyledStringInner::Cow(value)
}
}
impl<'a> From<StyledString<'a>> 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::<String>()
+ "..",
)),
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<StyledStringInner<'a>>, 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;
pub fn style_bar<'s, const N: usize>( pub fn style_bar<'s, const N: usize>(
prefix: StyledString, prefix: &str,
name: StyledString, name: &str,
work_per_sec: StyledString, work_per_sec: &str,
download_done: usize, download_done: usize,
extract_done: usize, extract_done: usize,
bar: MultiBar<'s, N>, bar: MultiBar<'s, N>,
width: u16, width: u16,
) -> String { ) -> 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 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 { if remaining_width == 0 {
return status; return status;
} }
let possible_chunks = remaining_width.saturating_div(INFO_WIDTH as u16 + 3); let possible_chunks = remaining_width.saturating_div(INFO_WIDTH as u16 + 3);
let possible_chunks = possible_chunks.min(3); let possible_chunks = possible_chunks.min(3);
let leftover_width = remaining_width.saturating_sub(possible_chunks * (INFO_WIDTH as u16 + 3)); let leftover_width = remaining_width.saturating_sub(possible_chunks * (INFO_WIDTH as u16 + 3));
let download_done_human = StyledString::new( let download_done_human = pad_string(
Cow::Owned(HumanBytes(download_done as u64).to_string()), DOWNLOAD_STYLE
DOWNLOAD_STYLE, .style(HumanBytes(download_done as u64).to_string())
) .to_string(),
.pad_to_width(INFO_WIDTH); INFO_WIDTH,
let extract_done_human = StyledString::new( );
Cow::Owned(HumanBytes(extract_done as u64).to_string()),
EXTRACT_STYLE, let extract_done_human = pad_string(
) EXTRACT_STYLE
.pad_to_width(INFO_WIDTH); .style(HumanBytes(extract_done as u64).to_string())
let work_per_sec = work_per_sec.pad_to_width(INFO_WIDTH); .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(); let bar = bar.scale(leftover_width as u64).to_string();
match possible_chunks { 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();
// }

View File

@@ -1,139 +0,0 @@
use std::{cell::RefCell, rc::Rc, sync::LazyLock, time::Instant};
use console::style;
use indicatif::{HumanBytes, MultiProgress, ProgressBar};
use crate::{
estimator::Estimator,
multibar::{BarSegment, MultiBar},
pad_string,
};
static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| style("#").blue().bold().to_string());
static DONE_CHAR: LazyLock<String> = LazyLock::new(|| style("#").green().bold().to_string());
const INFO_WIDTH: usize = 12;
#[derive(Debug, Clone)]
pub struct DownloadBar {
pub download_estimator: Rc<RefCell<Estimator>>,
pub extract_estimator: Rc<RefCell<Estimator>>,
pub bar: ProgressBar,
pub name: String,
pub download_expected: Rc<RefCell<u64>>,
pub download_done: Rc<RefCell<u64>>,
pub extract_expected: Rc<RefCell<u64>>,
pub extract_done: Rc<RefCell<u64>>,
pub started_at: Instant,
}
impl DownloadBar {
pub fn new(bar: ProgressBar, name: String, width: u16) -> Self {
bar.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap());
let new_self = Self {
name,
bar,
download_estimator: Rc::new(RefCell::new(Estimator::new(Instant::now()))),
extract_estimator: Rc::new(RefCell::new(Estimator::new(Instant::now()))),
download_expected: Rc::new(RefCell::new(0)),
download_done: Rc::new(RefCell::new(0)),
extract_expected: Rc::new(RefCell::new(0)),
extract_done: Rc::new(RefCell::new(0)),
started_at: Instant::now(),
};
new_self.update(width);
new_self
}
pub fn update(&self, width: u16) {
let download_done = (*self.download_done.borrow());
let download_expected = (*self.download_expected.borrow());
let extract_done = *self.extract_done.borrow();
let extract_expected = *self.extract_expected.borrow();
self.download_estimator
.borrow_mut()
.record(download_done, Instant::now());
self.extract_estimator
.borrow_mut()
.record(extract_done, Instant::now());
let name = pad_string(&self.name, (width / 4) as usize);
let status = format!("Download {} |", style(name).magenta().bold());
let status_len = (width / 4) as usize + 10;
if download_expected == 0 || extract_expected == 0 {
let text = pad_string("Pending...", width as usize / 3);
self.bar.set_message(format!("{status}"));
return;
}
let total_expected = download_expected + extract_expected;
let total_done = download_done + extract_done;
let dl_percent = ((download_done as f64 / download_expected as f64) * 100.0) as u64;
let ex_percent = ((extract_done as f64 / extract_expected as f64) * 100.0) as u64;
let download_per_sec = HumanBytes(
self.download_estimator
.borrow()
.steps_per_second(Instant::now()) as u64,
)
.to_string();
let download_done_human = HumanBytes(download_done).to_string();
let download_done_human = style(pad_string(&download_done_human, INFO_WIDTH))
.blue()
.bold()
.to_string();
let extract_done_human = HumanBytes(extract_done).to_string();
let extract_done_human = style(pad_string(&extract_done_human, INFO_WIDTH))
.green()
.bold()
.to_string();
let download_per_sec = style(pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH))
.blue()
.bold()
.to_string();
let display = format!("{download_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 bar = MultiBar([
BarSegment::Dynamic(&DONE_CHAR, min),
BarSegment::Dynamic(&DOWNLOAD_CHAR, dl),
BarSegment::Dynamic(" ", 100 - min - dl),
])
.scale((width - total_length as u16) as u64);
let msg = match width {
0..50 => {
format!(
"{}: {}/{}",
self.name,
total_done,
if total_expected == 0 {
"-".to_string()
} else {
total_expected.to_string()
}
)
}
_ => {
format!("{status} {display} [{bar}]",)
}
};
self.bar.set_message(msg);
self.bar.tick();
}
}
fn str_len(s: &str) -> usize {
s.chars()
.filter(|x| x.is_alphanumeric() || x.is_whitespace())
.count()
}

View File

@@ -1,21 +1,18 @@
use std::{borrow::Cow, sync::LazyLock, time::Instant}; use std::{borrow::Cow, time::Instant};
use indicatif::{HumanBytes, ProgressBar}; use indicatif::{HumanBytes, ProgressBar};
use owo_colors::{OwoColorize, Style}; use owo_colors::{OwoColorize, Style};
use crate::{ use crate::{
action::{Action, BuildStepId, ResultFields, StartFields}, action::{Action, BuildStepId, ResultFields, StartFields},
download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar}, download_display::style_bar,
estimator::Estimator, estimator::Estimator,
handlers::{Handler, fetch::FetchHandler}, handlers::Handler,
multibar::{BarSegment, MultiBar}, multibar::{BarSegment, MultiBar},
nix_path, pad_string, nix_path,
util::{DONE_CHAR, DOWNLOAD_CHAR, DOWNLOAD_STYLE, EXTRACT_STYLE},
}; };
static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| "-".blue().bold().to_string());
static DONE_CHAR: LazyLock<String> = LazyLock::new(|| "#".green().bold().to_string());
const INFO_WIDTH: usize = 13;
pub struct SubstituteHandler; pub struct SubstituteHandler;
impl Handler for SubstituteHandler { impl Handler for SubstituteHandler {
@@ -26,7 +23,7 @@ impl Handler for SubstituteHandler {
) -> color_eyre::Result<bool> { ) -> color_eyre::Result<bool> {
match action { match action {
Action::Start { Action::Start {
start_type: StartFields::Substitute { source, target }, start_type: StartFields::Substitute { .. },
id, id,
.. ..
} => { } => {
@@ -109,32 +106,27 @@ impl DownloadHandler {
}; };
let name = nix_path::extract_package_name_string(&self.path) let name = nix_path::extract_package_name_string(&self.path)
.unwrap_or_else(|| "unknown".to_string()); .unwrap_or_else(|| "unknown".to_string());
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 { if self.download_expected == 0 || self.extract_expected == 0 {
bar.set_message(format!("{status}")); bar.set_message(format!("Downloading {name}"));
bar.tick(); bar.tick();
return; return;
} }
let work_per_sec = if self.download_done < self.download_expected { let work_per_sec = if self.download_done < self.download_expected {
StyledString::new( DOWNLOAD_STYLE
Cow::Owned(format!( .style(format!(
"{}", "{}",
HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64) HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64)
)), ))
DOWNLOAD_STYLE, .to_string()
)
} else { } else {
StyledString::new( EXTRACT_STYLE
Cow::Owned(format!( .style(format!(
"{}", "{}",
HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64) HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64)
)), ))
EXTRACT_STYLE, .to_string()
)
}; };
let dl_percent = let dl_percent =
@@ -148,9 +140,9 @@ impl DownloadHandler {
BarSegment::Dynamic(" ", 100 - min - dl), BarSegment::Dynamic(" ", 100 - min - dl),
]); ]);
let msg = style_bar( let msg = style_bar(
StyledString::new(Cow::Owned("Downloading".to_string()), Style::new()), "Downloading",
StyledString::new(Cow::Owned(name), Style::new().purple()), &(name.purple().to_string()),
work_per_sec, &work_per_sec,
self.download_done as usize, self.download_done as usize,
self.extract_done as usize, self.extract_done as usize,
mbar, mbar,

View File

@@ -3,7 +3,7 @@ use std::borrow::Cow;
use indicatif::ProgressBar; use indicatif::ProgressBar;
use owo_colors::OwoColorize; 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 { pub struct FetchHandler {
id: u64, id: u64,

View File

@@ -1,21 +1,17 @@
use std::{borrow::Cow, collections::HashMap, sync::LazyLock, time::Instant}; use std::{borrow::Cow, collections::HashMap, time::Instant};
use indicatif::{HumanBytes, ProgressBar}; use indicatif::{HumanBytes, ProgressBar};
use owo_colors::{OwoColorize, Style}; use owo_colors::{OwoColorize, Style};
use crate::{ use crate::{
action::{Action, ActionType, BuildStepId, ResultFields, StartFields}, action::{Action, ActionType, BuildStepId, ResultFields, StartFields},
download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar}, download_display::style_bar,
estimator::Estimator, estimator::Estimator,
handlers::{Handler, fetch::FetchHandler}, handlers::Handler,
multibar::{BarSegment, MultiBar}, multibar::{BarSegment, MultiBar},
nix_path, pad_string, util::{DONE_CHAR, DOWNLOAD_CHAR, DOWNLOAD_STYLE, EXTRACT_STYLE, INPROGRESS_CHAR},
}; };
static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| "-".blue().bold().to_string());
static DONE_CHAR: LazyLock<String> = LazyLock::new(|| "#".green().bold().to_string());
static INPROGRESS_CHAR: LazyLock<String> = LazyLock::new(|| "-".purple().bold().to_string());
pub struct CopyPathsHandler; pub struct CopyPathsHandler;
impl Handler for CopyPathsHandler { impl Handler for CopyPathsHandler {
@@ -30,7 +26,6 @@ impl Handler for CopyPathsHandler {
id, id,
.. ..
} => { } => {
state.println(format!("CopyPaths start"))?;
let progress = state.add_pb(ProgressBar::new(1)); let progress = state.add_pb(ProgressBar::new(1));
progress.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap()); progress.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap());
@@ -103,11 +98,15 @@ impl SubstitutionStatusHandler {
} }
let dl_percent = ((self.get_done() as f64 / self.max_transfer as f64) * 100.0) as u64; let dl_percent = ((self.get_done() as f64 / self.max_transfer as f64) * 100.0) as u64;
let ex_percent = ((self.get_unpacked() as f64 / self.max_copy as f64) * 100.0) as u64; let ex_percent = ((self.get_unpacked() as f64 / self.max_copy as f64) * 100.0) as u64;
let expected = (((self.get_running() + self.get_running_copy()) as f64
/ (self.max_transfer + self.max_copy) as f64) let dl_expected_percent =
* 100.0) as u64; ((self.get_running() as f64 / self.max_transfer as f64) * 100.0) as u64;
let ex_expected_percent =
((self.get_running_copy() as f64 / self.max_copy as f64) * 100.0) as u64;
let min = dl_percent.min(ex_percent); let min = dl_percent.min(ex_percent);
let dl = dl_percent.saturating_sub(min); let dl = dl_percent.saturating_sub(min);
let expected = dl_expected_percent.max(ex_expected_percent);
let exp = expected.saturating_sub(min + dl); let exp = expected.saturating_sub(min + dl);
let mbar = MultiBar([ let mbar = MultiBar([
BarSegment::Dynamic(&DONE_CHAR, min), BarSegment::Dynamic(&DONE_CHAR, min),
@@ -117,32 +116,24 @@ impl SubstitutionStatusHandler {
]); ]);
let work_per_sec = if self.get_done() < self.max_transfer { let work_per_sec = if self.get_done() < self.max_transfer {
StyledString::new( DOWNLOAD_STYLE
Cow::Owned(format!( .style(format!(
"{}", "{}",
HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64) HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64)
)), ))
DOWNLOAD_STYLE, .to_string()
)
} else { } else {
StyledString::new( EXTRACT_STYLE
Cow::Owned(format!( .style(format!(
"{}", "{}",
HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64) HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64)
)), ))
EXTRACT_STYLE, .to_string()
)
}; };
let msg = style_bar( let msg = style_bar(
StyledString::new(Cow::Owned("Downloading".to_string()), Style::new()), "Downloading",
StyledString::new( &(format!("{}/{}", self.state_self[0].purple(), self.state_self[1])),
Cow::Owned(format!( &work_per_sec,
"{}/{} {min} {dl} {expected}",
self.state_self[0], self.state_self[1]
)),
Style::new().purple(),
),
work_per_sec,
self.get_done() as usize, self.get_done() as usize,
self.get_unpacked() as usize, self.get_unpacked() as usize,
mbar, mbar,
@@ -161,7 +152,7 @@ impl Handler for SubstitutionStatusHandler {
) -> color_eyre::Result<bool> { ) -> color_eyre::Result<bool> {
match action { match action {
Action::Start { Action::Start {
start_type: StartFields::CopyPath { path, .. }, start_type: StartFields::CopyPath { .. },
id, id,
.. ..
} => { } => {
@@ -178,24 +169,24 @@ impl Handler for SubstitutionStatusHandler {
} }
Action::Result { Action::Result {
id,
fields: fields:
ResultFields::SetExpected { ResultFields::SetExpected {
action: ActionType::FileTransfer, action: ActionType::FileTransfer,
expected, expected,
}, },
..
} => { } => {
self.max_transfer = *expected; self.max_transfer = *expected;
self.draw_bar(state.term_width); self.draw_bar(state.term_width);
Ok(true) Ok(true)
} }
Action::Result { Action::Result {
id,
fields: fields:
ResultFields::SetExpected { ResultFields::SetExpected {
action: ActionType::CopyPath, action: ActionType::CopyPath,
expected, expected,
}, },
..
} => { } => {
self.max_copy = *expected; self.max_copy = *expected;
self.draw_bar(state.term_width); self.draw_bar(state.term_width);

View File

@@ -1,30 +1,21 @@
use std::sync::LazyLock;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use console::Term; use console::Term;
use owo_colors::OwoColorize;
use crate::state::State; use crate::state::State;
pub mod action; pub mod action;
pub mod action_raw; pub mod action_raw;
pub mod download_display; pub mod download_display;
pub mod download_pb;
pub mod estimator; pub mod estimator;
pub mod handlers; pub mod handlers;
pub mod multibar; pub mod multibar;
pub mod nix_path; pub mod nix_path;
pub mod state; pub mod state;
pub mod state_manager; pub mod util;
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
}
}
fn main() -> Result<(), color_eyre::Report> { fn main() -> Result<(), color_eyre::Report> {
color_eyre::install().unwrap(); color_eyre::install().unwrap();
@@ -32,168 +23,11 @@ fn main() -> Result<(), color_eyre::Report> {
let lines = std::fs::read_to_string("build.log")?; let lines = std::fs::read_to_string("build.log")?;
let mut state = State::new(Term::stderr().size().1); let mut state = State::new(Term::stderr().size().1);
// let lines = lines.lines().take(0).collect::<Vec<_>>().join("\n");
// let term = Term::stdout();
// let mut state = State {
// multi_progress: MultiProgress::new(),
// manager: StateManager::new(),
// separator: None,
// width: term.size().1,
// current_width: term.size().1,
// term,
// };
for line in lines.lines() { for line in lines.lines() {
let line = line.strip_prefix("@nix ").unwrap_or(line); let line = line.strip_prefix("@nix ").unwrap_or(line);
sleep(Duration::from_millis(1)); sleep(Duration::from_millis(1));
let action = action::Action::parse(line)?; let action = action::Action::parse(line)?;
state.handle(&action)?; state.handle(&action)?;
// match action {
// action::Action::Msg { level, msg } => {
// state.println(format!("MSG (level {level}): {msg}"))?;
// }
// action::Action::Start {
// start_type,
// id,
// level: _,
// parent,
// text: _,
// } => {
// // state.progress.println(format!("START {start_type:?}"));
// if let Some(parent) = state.manager.get(*parent) {
// let mut child = parent.clone();
// child.merge(&mut state, start_type);
// child.tick(&mut state);
// state.manager.insert_parent(*id, child);
// };
// // match start_type {
// // StartFields::Substitute { source, target } => {
// // state
// // .manager
// // .insert_parent(*id, BuildState::new(Some(source.to_string()), None));
// //
// // let build_state = state.manager.get_mut(*id).unwrap();
// // build_state.state = BuildEnumState::Substituting;
// // }
// // StartFields::CopyPath {
// // path,
// // origin,
// // destination,
// // } => {
// // state.manager.add_child(*id, *parent);
// // }
// // StartFields::QueryPathInfo { path, source } => {
// // state.progress.println(format!(
// // "START QueryPathInfo (id: {}, parent: {}): path={}",
// // id, parent, path
// // ))?;
// // }
// // StartFields::FileTransfer { target } => {
// // // state.progress.println(format!(
// // // "START FileTransfer (id: {}, parent: {}): target={}",
// // // id, parent, target
// // // ))?;
// // if let Some(parent) = state.manager.get(*parent) {
// // state
// // .manager
// // .insert_parent(*id, BuildState::new(parent.path.clone(), None));
// // let build_state = state.manager.get_mut(*id).unwrap();
// // build_state.state = BuildEnumState::Downloading;
// // };
// //
// // // state.manager.add_child(*id, *parent);
// // // Add child ID mapping to parent
// // }
// // _ => {}
// // };
// }
// action::Action::Stop { id } => {
// state.manager.remove(*id);
// // Stop will only return Some when the last reference is stopped
// // if let Some(build_state) = state.manager.stop(*id) {
// // if let Some(pb) = &build_state.progress_bar {
// // pb.finish_and_clear();
// // state.progress.remove(pb);
// // }
// // state.progress.println(format!(
// // "Completed: {}",
// // build_state.path.as_deref().unwrap_or("unknown")
// // ))?;
// // }
// }
// action::Action::Result { id, fields } => match fields {
// action::ResultFields::FetchStatus(status) => {
// state.println(format!(
// "RESULT FetchStatus (id: {}): status={}",
// id, status
// ))?;
// }
// action::ResultFields::Progress {
// done,
// expected,
// running,
// failed,
// } => {
// if expected == 0 {
// continue;
// };
// if let Some(mut child) = state.manager.take(*id) {
// child.progress(&mut state, done, expected);
// state.manager.insert_parent(*id, child);
// }
// // sleep(Duration::from_millis(1));
// // if let Some(build_state) = state.manager.get_mut(*id)
// // && expected > 0
// // {
// // let percentage = if expected == 0 {
// // 0
// // } else {
// // (done * 100 / expected) as u64
// // };
// // match &build_state.progress_bar {
// // Some(pb) => {
// // if percentage > pb.position() {
// // pb.set_position(percentage);
// // };
// // }
// // None => {
// // state.progress.println(format!(
// // "Creating progress bar for id {} (done: {}, expected: {})",
// // id, done, expected
// // ))?;
// // let n = match build_state.state {
// // BuildEnumState::Downloading => "Downloading",
// // BuildEnumState::Substituting => "Substituting",
// // _ => "Processing",
// // };
// // let name =
// // nix_path::extract_full_name(build_state.path.as_ref().unwrap());
// // let pb = state.progress.add(
// // ProgressBar::new(100)
// // .with_style(
// // ProgressStyle::default_bar()
// // .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%")
// // .unwrap(),
// // )
// // .with_message(format!(
// // "{n} {}",
// // name.as_deref().unwrap_or("unknown")
// // )),
// // );
// // pb.set_position(percentage);
// // build_state.progress_bar = Some(pb);
// // }
// // };
// // } else {
// // // state.progress.println(format!(
// // // "RESULT Progress (id: {}): done={}, expected={}, running={}, failed={}",
// // // id, done, expected, running, failed
// // // ))?;
// // };
// }
// _ => {}
// },
// }
} }
Ok(()) Ok(())

View File

@@ -16,7 +16,7 @@ impl<'s> BarSegment<'s> {
fn length(&self) -> u64 { fn length(&self) -> u64 {
match self { match self {
BarSegment::Dynamic(c, len) => *len, BarSegment::Dynamic(_c, len) => *len,
BarSegment::Static(c) => c.len() as u64, BarSegment::Static(c) => c.len() as u64,
} }
} }

View File

@@ -16,7 +16,7 @@ pub fn extract_package_name(path: &str) -> Option<Vec<&str>> {
let name_parts: Vec<&str> = parts let name_parts: Vec<&str> = parts
.iter() .iter()
.take_while(|part| !part.chars().next().map_or(false, |c| c.is_ascii_digit())) .take_while(|part| !part.chars().next().is_some_and(|c| c.is_ascii_digit()))
.copied() .copied()
.collect(); .collect();

View File

@@ -1,4 +1,4 @@
use std::{io, marker::PhantomData, rc::Rc}; use std::{io, rc::Rc};
use console::style; use console::style;
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};

View File

@@ -1,189 +0,0 @@
use console::{Term, style};
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
use std::{borrow::Cow, collections::HashMap, io, time::Instant};
use crate::{action::StartFields, download_pb::DownloadBar, nix_path};
pub struct State<'a> {
pub multi_progress: MultiProgress,
pub manager: StateManager<'a>,
pub separator: Option<ProgressBar>,
pub term: Term,
pub width: u16,
pub current_width: u16,
}
impl<'a> State<'a> {
pub fn add_pb(&mut self, pb: ProgressBar) -> ProgressBar {
let separator = self.separator.get_or_insert_with(|| {
let separator = ProgressBar::new_spinner()
.with_style(
ProgressStyle::default_spinner()
.template(&style("··{wide_msg:<}").dim().to_string())
.expect("invalid template"),
)
.with_message("·".repeat(512))
.with_finish(ProgressFinish::AndClear);
let separator = self.multi_progress.insert(0, separator);
separator.set_length(0);
separator
});
self.multi_progress.insert_after(separator, pb)
// self.progress.add(pb)
}
pub fn add_pb_before(&mut self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
self.multi_progress.insert_before(before, pb)
}
pub fn add_pb_after(&mut self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
self.multi_progress.insert_after(after, pb)
}
pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
self.multi_progress.println(msg)
}
}
#[derive(Debug, Clone)]
pub enum BuildEnumState<'a> {
Init,
Unknown,
Realise,
Builds,
CopyPaths,
Substitute {
source: Cow<'a, str>,
target: Cow<'a, str>,
},
SubstituteCopy {
path: Cow<'a, str>,
bar: DownloadBar,
},
SubstituteFetch {
path: Cow<'a, str>,
bar: DownloadBar,
},
Query {
path: Cow<'a, str>,
source: Cow<'a, str>,
},
QueryFetch {
target: Cow<'a, str>,
},
}
#[derive(Debug, Clone)]
pub struct BuildState<'a> {
pub path: Option<String>,
pub progress_bar: Option<ProgressBar>,
pub state: BuildEnumState<'a>,
}
impl<'a> BuildState<'a> {
pub fn new() -> Self {
Self {
path: None,
progress_bar: None,
state: BuildEnumState::Init,
}
}
}
impl<'a> BuildState<'a> {
pub fn merge(&mut self, state: &mut State, start_type: StartFields<'a>) {
let state = match (&self.state, start_type.clone()) {
(BuildEnumState::Init, StartFields::QueryPathInfo { path, source }) => {
BuildEnumState::Query { path, source }
}
(BuildEnumState::Query { path, source }, StartFields::FileTransfer { target }) => {
BuildEnumState::QueryFetch { target }
}
(BuildEnumState::Init, StartFields::Realise) => BuildEnumState::Realise,
(BuildEnumState::Init, StartFields::Builds) => BuildEnumState::Builds,
(BuildEnumState::Init, StartFields::CopyPaths) => BuildEnumState::CopyPaths,
(BuildEnumState::Init, StartFields::Substitute { source, target }) => {
BuildEnumState::Substitute { source, target }
}
(BuildEnumState::Substitute { .. }, StartFields::CopyPath { path, .. }) => {
let name =
nix_path::extract_package_name_string(&path).unwrap_or("unknown".to_string());
let bar = state.add_pb(ProgressBar::new(100));
let bar = DownloadBar::new(bar, name, state.width);
BuildEnumState::SubstituteCopy { path, bar }
}
(BuildEnumState::SubstituteCopy { path, bar }, StartFields::FileTransfer { .. }) => {
BuildEnumState::SubstituteFetch {
path: path.clone(),
bar: bar.clone(),
}
}
(_, StartFields::Unknown) => BuildEnumState::Unknown,
_ => unimplemented!(
"Unsupported state transition from {:?} with {:?}",
self.state,
start_type
),
};
self.state = state;
}
pub fn tick(&mut self, state: &mut State<'a>) {
match &self.state {
BuildEnumState::QueryFetch { target } => {
let pb = state.add_pb(ProgressBar::new_spinner());
pb.set_message(format!("Fetching info for {}", target));
pb.enable_steady_tick(std::time::Duration::from_millis(100));
self.progress_bar = Some(pb);
}
_ => {}
}
}
pub fn progress(&mut self, state: &mut State<'a>, done: u64, expected: u64) {
match &self.state {
BuildEnumState::SubstituteFetch { bar, .. } => {
bar.download_expected.replace(expected);
bar.download_done.replace(done);
bar.update(state.width);
}
BuildEnumState::SubstituteCopy { path, bar } => {
bar.extract_expected.replace(expected);
bar.extract_done.replace(done);
bar.update(state.width);
}
_ => {}
}
}
}
pub struct StateManager<'a> {
states: HashMap<u64, BuildState<'a>>,
}
impl<'a> StateManager<'a> {
pub fn new() -> Self {
let mut states = HashMap::new();
states.insert(0, BuildState::new());
Self { states }
}
pub fn get(&self, id: u64) -> Option<&BuildState<'a>> {
self.states.get(&id)
}
pub fn get_mut(&mut self, id: u64) -> Option<&mut BuildState<'a>> {
self.states.get_mut(&id)
}
pub fn insert_parent(&mut self, id: u64, state: BuildState<'a>) {
self.states.insert(id, state);
}
pub fn get_or_insert(&mut self, id: u64) -> &mut BuildState<'a> {
self.states.entry(id).or_insert_with(BuildState::new)
}
pub fn take(&mut self, id: u64) -> Option<BuildState<'a>> {
self.states.remove(&id)
}
pub fn remove(&mut self, id: u64) {
if let Some(state) = self.states.get(&id) {
if let Some(pb) = &state.progress_bar {
pb.finish_and_clear();
self.states.remove(&id);
}
}
self.states.remove(&id);
}
}

67
src/util.rs Normal file
View File

@@ -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<String> = LazyLock::new(|| "-".blue().bold().to_string());
pub static DONE_CHAR: LazyLock<String> = LazyLock::new(|| "=".green().bold().to_string());
pub static INPROGRESS_CHAR: LazyLock<String> = LazyLock::new(|| "-".purple().bold().to_string());
pub trait StrExt<'a>: Into<Cow<'a, str>> {
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::<String>() + ".."
} 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<Cow<'a, str>>> StrExt<'a> for T {}
pub fn pad_string<'a, S: Into<Cow<'a, str>>>(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::<String>() + ".."
} else {
let mut padded = s.to_string();
padded.push_str(&" ".repeat(width - len));
padded
}
}