#![doc = include_str!(concat!(env!("OUT_DIR"), "/README-rustdocified.md"))]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
use std::{
borrow::Cow,
error::Error as StdError,
fmt,
sync::Arc,
thread::{self, JoinHandle},
time::Duration,
};
use parking_lot::Mutex;
pub use crate::state::State;
use crate::internal::Item;
#[allow(missing_docs)]
pub mod internal;
mod macros;
mod state;
const DEFAULT_DRAW_RATE: usize = 20;
const DEFAULT_DRAW_INTERVAL: Duration =
Duration::from_nanos(1_000_000_000 / DEFAULT_DRAW_RATE as u64);
const DEFAULT_DRAW_DELAY: Duration = Duration::from_millis(5);
const MIN_ETA_ELAPSED: Duration = Duration::from_millis(100);
const MIN_SPEED_ELAPSED: Duration = Duration::from_millis(100);
const BINARY_PREFIXES: &[&str] = &["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"];
const DECIMAL_PREFIXES: &[&str] = &["", "k", "M", "G", "T", "P", "E", "Z", "Y"];
#[derive(Debug, PartialEq)]
pub enum Error {
MultipleFillItems,
TotalIsOutOfRange,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::MultipleFillItems => {
write!(f, "got multiple fill items, at most one is allowed")
}
Error::TotalIsOutOfRange => {
write!(f, "total is out-of-range of `u64`")
}
}
}
}
impl StdError for Error {}
#[derive(Clone)]
pub struct Progress {
drawer: Option<Arc<JoinHandle<()>>>,
state: Arc<Mutex<State>>,
}
impl Progress {
pub fn finish(&self) {
self.state.lock().finish(self.drawer.as_ref().unwrap());
}
pub fn finish_and_clear(&self) {
self.state
.lock()
.finish_and_clear(self.drawer.as_ref().unwrap());
}
pub fn finish_at_current_pos(&self) {
self.state
.lock()
.finish_at_current_pos(self.drawer.as_ref().unwrap());
}
pub fn inc(&self, steps: u64) {
self.state.lock().inc(steps, self.drawer.as_ref().unwrap());
}
pub fn message(&self, message: impl Into<Cow<'static, str>>) {
self.state
.lock()
.message(message, self.drawer.as_ref().unwrap());
}
pub fn state(&self) -> &Arc<Mutex<State>> {
&self.state
}
}
impl Drop for Progress {
fn drop(&mut self) {
if let Ok(drawer) = Arc::try_unwrap(self.drawer.take().unwrap()) {
let mut state = self.state.lock();
if !state.is_finished() {
state.finish_quietly(&drawer);
}
drop(state);
let _ = drawer.join();
}
}
}
impl Progress {
pub(crate) fn new(state: State) -> Self {
let state = Arc::new(Mutex::new(state));
let drawer = thread::spawn({
let state = state.clone();
move || loop {
let mut state = state.lock();
if state.is_finished() {
break;
}
let timeout = match state.try_draw() {
Ok(()) => None,
Err(timeout) => timeout,
};
drop(state);
if let Some(timeout) = timeout {
thread::park_timeout(timeout);
} else {
thread::park();
}
}
});
Self {
drawer: Some(Arc::new(drawer)),
state,
}
}
}
pub struct ProgressBuilder {
total: Result<Option<u64>, Error>,
pre_inc: bool,
thousands_separator: String,
items: Vec<Item>,
}
impl ProgressBuilder {
pub fn build(self) -> Result<Progress, Error> {
let state = State::new(
self.total?,
self.pre_inc,
self.thousands_separator,
self.items,
)?;
Ok(Progress::new(state))
}
pub fn new(items: Vec<Item>) -> Self {
let items = if items.is_empty() {
items!(bar_fill " " pos "/" total " (" eta ")")
} else {
items
};
Self {
total: Ok(None),
pre_inc: false,
thousands_separator: " ".to_owned(),
items,
}
}
pub fn pre_inc(self) -> Self {
Self {
pre_inc: true,
..self
}
}
pub fn thousands_separator(self, separator: &str) -> Self {
Self {
thousands_separator: separator.to_owned(),
..self
}
}
pub fn total<T: TryInto<u64>>(self, total: Option<T>) -> Self {
let total = if let Some(total) = total {
match total.try_into() {
Ok(total) => Ok(Some(total)),
Err(_) => Err(Error::TotalIsOutOfRange),
}
} else {
Ok(None)
};
Self { total, ..self }
}
}
pub fn binary_prefix(mut value: f64) -> (f64, &'static str) {
let mut scale = 0;
while value.abs() >= 1024.0 && scale < BINARY_PREFIXES.len() - 1 {
value /= 1024.0;
scale += 1;
}
(value, BINARY_PREFIXES[scale])
}
pub fn decimal_prefix(mut value: f64) -> (f64, &'static str) {
let mut scale = 0;
while value.abs() >= 1000.0 && scale < DECIMAL_PREFIXES.len() - 1 {
value /= 1000.0;
scale += 1;
}
(value, DECIMAL_PREFIXES[scale])
}
pub fn duration_approx(duration: Duration) -> (u64, &'static str) {
let secs = duration.as_secs();
if secs < 60 {
(secs, "s")
} else if secs < 3600 {
(secs / 60, "m")
} else {
(secs / 3600, "h")
}
}
pub fn duration_hms(duration: Duration) -> (u64, u64, u64) {
let secs = duration.as_secs();
let h = secs / 3600;
let m = (secs % 3600) / 60;
let s = secs % 60;
(h, m, s)
}
pub fn group_digits(mut value: u64, separator: &str) -> String {
let mut groups = [0; 7];
let mut pos = 0;
while pos == 0 || value > 0 {
groups[pos] = value % 1000;
value /= 1000;
pos += 1;
}
let mut result = String::with_capacity(pos * 3 + (pos - 1) * separator.len());
pos -= 1;
result.push_str(&format!("{}", groups[pos]));
while pos > 0 {
pos -= 1;
result.push_str(separator);
result.push_str(&format!("{:03}", groups[pos]));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binary_prefix_misc() {
assert_eq!(binary_prefix(0.0), (0.0, ""));
assert_eq!(binary_prefix(2560.0), (2.5, "Ki"));
assert_eq!(binary_prefix(2621440.0), (2.5, "Mi"));
assert_eq!(binary_prefix(-2560.0), (-2.5, "Ki"));
assert_eq!(binary_prefix(-2621440.0), (-2.5, "Mi"));
}
#[test]
fn binary_prefix_overflow() {
assert_eq!(binary_prefix(91.0f64.exp2()), (2048.0, "Yi"));
}
#[test]
fn decimal_prefix_misc() {
assert_eq!(decimal_prefix(0.0), (0.0, ""));
assert_eq!(decimal_prefix(2500.0), (2.5, "k"));
assert_eq!(decimal_prefix(2500000.0), (2.5, "M"));
assert_eq!(decimal_prefix(-2500.0), (-2.5, "k"));
assert_eq!(decimal_prefix(-2500000.0), (-2.5, "M"));
}
#[test]
fn decimal_prefix_overflow() {
assert_eq!(decimal_prefix(2.0e27), (2000.0, "Y"));
}
#[test]
fn duration_approx_no_rounding() {
assert_eq!(duration_approx(Duration::from_millis(1800)), (1, "s"));
}
#[test]
fn duration_hms_no_rounding() {
assert_eq!(duration_hms(Duration::from_millis(1800)), (0, 0, 1));
}
#[test]
fn group_digits_0() {
assert_eq!(group_digits(0, " "), "0");
}
#[test]
fn group_digits_max() {
assert_eq!(group_digits(u64::MAX, " "), "18 446 744 073 709 551 615");
}
#[test]
fn group_digits_long_separator() {
assert_eq!(group_digits(12_345, "abc"), "12abc345");
}
#[test]
fn group_digits_has_zero_padding() {
assert_eq!(group_digits(1_002_034, " "), "1 002 034");
}
#[test]
fn group_digits_no_zero_padding() {
assert_eq!(group_digits(1_234_567, " "), "1 234 567");
}
}