use crate::cargo_config::{CargoConfigs, DiscoveredConfig};
use anstyle_progress::{TermProgress, supports_term_progress};
use indicatif::ProgressStyle;
use std::{
env,
io::{self, Write},
};
use tracing::debug;
pub(crate) const PROGRESS_REFRESH_RATE_HZ: u8 = 1;
pub(crate) fn progress_bar_style(progress_chars: &str, suffix: &str) -> ProgressStyle {
let template = format!("{{prefix:>12}} [{{elapsed_precise:>9}}] {{wide_bar}} {suffix}");
ProgressStyle::default_bar()
.progress_chars(progress_chars)
.template(&template)
.expect("template is known to be valid")
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShowTerminalProgress {
Yes,
No,
}
impl ShowTerminalProgress {
const ENV: &str = "CARGO_TERM_PROGRESS_TERM_INTEGRATION";
pub fn from_cargo_configs(configs: &CargoConfigs, is_terminal: bool) -> Self {
for config in configs.discovered_configs() {
match config {
DiscoveredConfig::CliOption { config, source } => {
if let Some(v) = config.term.progress.term_integration {
if v {
debug!("enabling terminal progress reporting based on {source:?}");
return Self::Yes;
} else {
debug!("disabling terminal progress reporting based on {source:?}");
return Self::No;
}
}
}
DiscoveredConfig::Env => {
if let Some(v) = env::var_os(Self::ENV) {
if v == "true" {
debug!(
"enabling terminal progress reporting based on \
CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
);
return Self::Yes;
} else if v == "false" {
debug!(
"disabling terminal progress reporting based on \
CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
);
return Self::No;
} else {
debug!(
"invalid value for CARGO_TERM_PROGRESS_TERM_INTEGRATION \
environment variable: {v:?}, ignoring"
);
}
}
}
DiscoveredConfig::File { config, source } => {
if let Some(v) = config.term.progress.term_integration {
if v {
debug!("enabling terminal progress reporting based on {source:?}");
return Self::Yes;
} else {
debug!("disabling terminal progress reporting based on {source:?}");
return Self::No;
}
}
}
}
}
let show = supports_term_progress(is_terminal);
debug!(is_terminal, show, "autodetected terminal progress support");
if show { Self::Yes } else { Self::No }
}
}
#[derive(Default)]
pub(crate) struct TerminalProgress {
last_value: TermProgress,
}
impl TerminalProgress {
pub(crate) fn new(show: ShowTerminalProgress) -> Option<Self> {
match show {
ShowTerminalProgress::Yes => Some(Self::default()),
ShowTerminalProgress::No => None,
}
}
pub(crate) fn set(&mut self, value: TermProgress) {
self.last_value = value;
}
#[cfg(test)]
pub(crate) fn last_value(&self) -> TermProgress {
self.last_value
}
pub(crate) fn emit(&self) {
let _ = write!(io::stderr(), "{}", self.last_value);
}
}
pub(crate) fn term_progress_percent(done: usize, total: usize) -> u8 {
if total == 0 {
return 100;
}
((done as f64 / total as f64) * 100.0)
.round()
.clamp(0.0, 100.0) as u8
}
#[cfg(test)]
mod tests {
use super::term_progress_percent;
#[test]
fn term_progress_percent_boundaries() {
assert_eq!(term_progress_percent(0, 0), 100);
assert_eq!(term_progress_percent(0, 10), 0);
assert_eq!(term_progress_percent(1, 2), 50);
assert_eq!(term_progress_percent(1, 4), 25);
assert_eq!(term_progress_percent(10, 10), 100);
assert_eq!(term_progress_percent(1, 8), 13);
assert_eq!(term_progress_percent(3, 8), 38);
assert_eq!(term_progress_percent(1, 40), 3);
assert_eq!(term_progress_percent(1, 3), 33);
assert_eq!(term_progress_percent(2, 3), 67);
assert_eq!(term_progress_percent(11, 10), 100);
}
}