use super::{
BarExt,
styles::{Animation, Colour},
};
use crate::{
format,
term::{self, Colorizer, InitializedOutput, Writer},
};
use std::{
io::{Result, Write, stdin},
num::NonZeroU16,
time::Instant,
};
#[cfg(feature = "notebook")]
use super::notebook;
#[cfg(feature = "notebook")]
use pyo3::{
prelude::*,
types::{PyDict, PyTuple},
};
#[cfg(feature = "spinner")]
use crate::spinner::Spinner;
#[cfg(feature = "template")]
use formatx::Template;
#[derive(Clone, Debug)]
pub struct Bar {
pub animation: Animation,
#[cfg(feature = "template")]
pub bar_format: Option<Template>,
pub colour: Option<Colour>,
pub desc: String,
pub delay: f32,
pub disable: bool,
pub dynamic_miniters: bool,
pub dynamic_ncols: bool,
pub force_refresh: bool,
pub inverse_unit: bool,
pub leave: bool,
pub mininterval: f32,
pub miniters: usize,
pub ncols: Option<u16>,
pub position: u16,
pub postfix: String,
pub total: usize,
#[cfg(feature = "spinner")]
pub spinner: Option<Spinner>,
pub unit: String,
pub unit_divisor: usize,
pub unit_scale: bool,
pub writer: InitializedOutput,
pub bar_length: u16,
#[cfg(feature = "notebook")]
container: Option<notebook::PyContainer>,
pub counter: usize,
current_ncols: u16,
elapsed_time: f32,
timer: Instant,
}
impl Default for Bar {
fn default() -> Self {
let mut ncols = None;
if let Ok(Ok(x)) = std::env::var("KDAM_NCOLS").map(|x| x.parse::<u16>()) {
ncols = Some(x);
}
Self {
animation: Animation::Tqdm,
#[cfg(feature = "template")]
bar_format: None,
colour: None,
delay: 0.0,
desc: "".to_owned(),
disable: false,
dynamic_miniters: false,
dynamic_ncols: false,
force_refresh: false,
inverse_unit: false,
leave: true,
mininterval: 0.1,
miniters: 1,
ncols,
total: 0,
position: 0,
postfix: "".to_string(),
#[cfg(feature = "spinner")]
spinner: None,
unit: "it".to_owned(),
unit_divisor: 1000,
unit_scale: false,
writer: InitializedOutput::Stderr,
bar_length: 0,
counter: 0,
current_ncols: 0,
elapsed_time: 0.0,
#[cfg(feature = "notebook")]
container: None,
timer: Instant::now(),
}
}
}
impl Bar {
pub fn new(total: usize) -> Self {
Self {
total,
..Default::default()
}
}
pub fn builder() -> BarBuilder {
BarBuilder::default()
}
#[cfg(feature = "template")]
#[cfg_attr(docsrs, doc(cfg(feature = "template")))]
pub fn set_bar_format<T: Into<String>>(
&mut self,
bar_format: T,
) -> ::std::result::Result<(), String> {
let bar_format = bar_format
.into()
.parse::<Template>()
.map_err(|x| x.message())?;
let mut bar_format_check = bar_format.clone();
bar_format_check.replace("desc", "");
bar_format_check.replace("percentage", 0.0);
bar_format_check.replace("count", 0);
bar_format_check.replace("total", 0);
bar_format_check.replace("elapsed", 0);
bar_format_check.replace("remaining", 0);
bar_format_check.replace("rate", 0.0);
bar_format_check.replace("unit", "");
bar_format_check.replace("postfix", "");
#[cfg(feature = "spinner")]
bar_format_check.replace("spinner", "");
bar_format_check.replace("animation", "");
bar_format_check.text().map_err(|x| x.message())?;
self.bar_format = Some(bar_format);
Ok(())
}
pub fn set_description<T: Into<String>>(&mut self, description: T) {
self.desc = description.into();
}
pub fn set_postfix<T: Into<String>>(&mut self, postfix: T) {
self.postfix = ", ".to_owned() + &postfix.into();
}
pub fn completed(&self) -> bool {
if self.indefinite() {
false
} else {
self.counter >= self.total
}
}
pub fn elapsed_time(&mut self) -> f32 {
self.elapsed_time = self.timer.elapsed().as_secs_f32();
self.elapsed_time
}
pub fn fmt_counter(&self) -> String {
if self.unit_scale {
format::size_of(self.counter as f64, self.unit_divisor as f64)
} else {
format!("{:1$}", self.counter, self.fmt_total().len())
}
}
pub fn fmt_elapsed_time(&self) -> String {
format::interval(self.elapsed_time as usize, false)
}
pub fn fmt_percentage(&self, precision: usize) -> String {
format!(
"{:1$.2$}%",
self.percentage() * 100.0,
if precision == 0 { 3 } else { precision + 4 },
precision
)
}
pub fn fmt_rate(&self) -> String {
if !self.started() {
format!("?{}/s", self.unit)
} else {
let rate = self.rate();
if rate < 1. && self.inverse_unit {
format!(
"{}/{}",
if self.unit_scale {
format::time(1. / (rate as f64))
} else {
format!("{:.2}s", 1. / rate)
},
self.unit
)
} else {
format!(
"{}{}/s",
if self.unit_scale {
format::size_of(rate as f64, self.unit_divisor as f64)
} else {
format!("{:.2}", rate)
},
self.unit
)
}
}
}
pub fn fmt_remaining_time(&self) -> String {
if self.counter == 0 || self.indefinite() {
"inf".to_owned()
} else {
format::interval(self.remaining_time() as usize, false)
}
}
pub fn fmt_total(&self) -> String {
if self.unit_scale {
format::size_of(self.total as f64, self.unit_divisor as f64)
} else {
self.total.to_string()
}
}
pub fn indefinite(&self) -> bool {
self.total == 0
}
pub fn ncols_for_animation(&mut self, padding: u16) -> u16 {
if self.dynamic_ncols || ((padding + self.current_ncols) != self.bar_length) {
if let Some(ncols) = self.ncols {
self.current_ncols = ncols;
} else if let Some(width) = term::width() {
if width >= padding {
self.current_ncols = width - padding;
}
} else {
self.current_ncols = 10;
}
}
self.current_ncols
}
pub fn percentage(&self) -> f32 {
if self.indefinite() {
1.0
} else {
(self.counter as f64 / self.total as f64) as f32
}
}
pub fn rate(&self) -> f32 {
self.counter as f32 / self.elapsed_time
}
pub fn remaining_time(&self) -> f32 {
if self.indefinite() {
f32::INFINITY
} else {
(self.total - self.counter) as f32 / self.rate()
}
}
pub fn should_refresh(&mut self) -> bool {
if !self.disable {
if self.force_refresh {
return true;
}
let elapsed_time_now = self.timer.elapsed().as_secs_f32();
let completion_constraint = self.counter == self.total;
let delay_constraint = self.delay <= elapsed_time_now;
let mininterval_constraint = self.mininterval <= (elapsed_time_now - self.elapsed_time);
if self.dynamic_miniters && !mininterval_constraint {
self.miniters += self.counter;
}
let miniters_constraint = if self.miniters <= 1 {
true
} else {
self.counter.is_multiple_of(self.miniters)
};
if (mininterval_constraint && miniters_constraint && delay_constraint)
|| completion_constraint
{
if self.dynamic_miniters {
self.miniters = 0;
}
return true;
}
}
false
}
pub fn started(&self) -> bool {
self.counter > 0
}
}
impl BarExt for Bar {
fn clear(&mut self) -> Result<()> {
self.writer.print_at(
self.position,
" ".repeat(term::width().unwrap_or(self.bar_length) as usize)
.as_bytes(),
)
}
fn input<T: Into<String>>(&mut self, text: T) -> Result<String> {
self.clear()?;
self.writer.print(text.into().as_bytes())?;
let mut buf = String::new();
stdin().read_line(&mut buf)?;
if self.leave {
self.refresh()?;
}
Ok(buf)
}
fn refresh(&mut self) -> Result<()> {
self.elapsed_time();
#[cfg(feature = "notebook")]
if notebook::running() {
if self.completed() {
self.total = self.counter;
}
let _ = self.render();
return Ok(());
}
if self.completed() {
if !self.leave && self.position > 0 {
return self.clear();
}
self.total = self.counter;
}
let text = self.render();
let bar_length = text.len_ansi() as u16;
if bar_length > self.bar_length {
self.clear()?;
self.bar_length = bar_length;
}
self.writer.print_at(self.position, text.as_bytes())?;
Ok(())
}
fn render(&mut self) -> String {
#[cfg(feature = "notebook")]
if let Some(container) = &self.container {
Python::attach(|py| -> PyResult<()> {
let pb = container
.bind(py)
.getattr("children")?
.cast::<PyTuple>()?
.get_item(1)?;
pb.setattr("value", self.counter)?;
if self.completed() {
pb.setattr("bar_style", "success")?;
if !self.leave {
pb.call_method0("close")?;
}
}
Ok(())
})
.unwrap();
}
#[cfg(feature = "template")]
if let Some(bar_format) = &self.bar_format {
let mut bar_format = bar_format.clone();
bar_format.replace_with_callback("desc", &self.desc, |fmtval, placeholder| {
if self.desc.is_empty() {
fmtval
} else {
fmtval
+ &placeholder
.attr("suffix")
.unwrap_or_else(|| ": ".to_owned())
}
});
bar_format.replace_from_callback("percentage", |placeholder| {
placeholder.format_spec.format(self.percentage() * 100.)
});
bar_format.replace_from_callback("count", |placeholder| {
if self.unit_scale {
placeholder.format_spec.format(format::size_of(
self.counter as f64,
self.unit_divisor as f64,
))
} else {
placeholder.format_spec.format(self.counter)
}
});
bar_format.replace_from_callback("total", |placeholder| {
if self.unit_scale {
placeholder
.format_spec
.format(format::size_of(self.total as f64, self.unit_divisor as f64))
} else {
placeholder.format_spec.format(self.total)
}
});
bar_format.replace_from_callback("elapsed", |placeholder| {
let human = placeholder
.attr("human")
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
placeholder
.format_spec
.format(format::interval(self.elapsed_time as usize, human))
});
bar_format.replace_from_callback("remaining", |placeholder| {
if self.indefinite() {
placeholder.format_spec.format("inf")
} else {
let human = placeholder
.attr("human")
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
placeholder
.format_spec
.format(format::interval(self.remaining_time() as usize, human))
}
});
bar_format.replace_from_callback("rate", |placeholder| {
if self.unit_scale {
placeholder.format_spec.format(format::size_of(
self.rate() as f64,
self.unit_divisor as f64,
))
} else {
placeholder.format_spec.format(self.rate())
}
});
bar_format.replace("unit", &self.unit);
bar_format.replace("postfix", &self.postfix);
#[cfg(feature = "spinner")]
bar_format.replace_from_callback("spinner", |_| {
if let Some(spinner) = &self.spinner {
spinner.render_frame(self.elapsed_time)
} else {
"".to_owned()
}
});
#[cfg(feature = "notebook")]
if let Some(container) = &self.container {
let text = bar_format.unchecked_text();
Python::attach(|py| -> PyResult<()> {
let container = container.bind(py).getattr("children")?;
let container = container.cast::<PyTuple>()?;
let (lbar, rbar) = (container.get_item(0)?, container.get_item(2)?);
if let Some(index) = text.find("{animation}") {
lbar.setattr("value", text.get(0..index).unwrap_or("").trim_end())?;
rbar.setattr("value", text.get((index + 11)..).unwrap_or("").trim_end())?;
} else {
lbar.setattr("value", "")?;
rbar.setattr("value", text)?;
}
Ok(())
})
.unwrap();
return "".to_owned();
}
let length = bar_format.unchecked_text().len_ansi() as u16;
if bar_format.contains("animation") && length > 11 {
let ncols = self.ncols_for_animation(length - 11);
if ncols > 0 {
bar_format.replace_from_callback("animation", |_| {
let render = self
.animation
.render(NonZeroU16::new(ncols).unwrap(), self.percentage());
if let Some(colour) = &self.colour {
colour.apply(&render)
} else {
render
}
});
}
}
bar_format.replace("animation", "");
return bar_format.text().unwrap(); }
let desc = if self.desc.is_empty() {
"".to_owned()
} else {
self.desc.clone() + ": "
};
#[cfg(feature = "notebook")]
if let Some(container) = &self.container {
Python::attach(|py| -> PyResult<()> {
let container = container.bind(py).getattr("children")?;
let container = container.cast::<PyTuple>()?;
let (lbar, rbar) = (container.get_item(0)?, container.get_item(2)?);
if self.indefinite() {
lbar.setattr(
"value",
format!("{}{}{}", desc, self.fmt_counter(), self.unit),
)?;
rbar.setattr(
"value",
format!(
"[{}, {}{}]",
self.fmt_elapsed_time(),
self.fmt_rate(),
self.postfix
),
)?;
} else {
lbar.setattr("value", desc + &self.fmt_percentage(0))?;
rbar.setattr(
"value",
format!(
"{}/{} [{}<{}, {}{}]",
self.fmt_counter(),
self.fmt_total(),
self.fmt_elapsed_time(),
self.fmt_remaining_time(),
self.fmt_rate(),
self.postfix,
),
)?;
}
Ok(())
})
.unwrap();
return "".to_owned();
}
if self.indefinite() {
format!(
"{}{}{} [{}, {}{}]",
desc,
self.fmt_counter(),
self.unit,
self.fmt_elapsed_time(),
self.fmt_rate(),
self.postfix
)
} else {
let lbar = desc + &self.fmt_percentage(0);
let rbar = format!(
" {}/{} [{}<{}, {}{}]",
self.fmt_counter(),
self.fmt_total(),
self.fmt_elapsed_time(),
self.fmt_remaining_time(),
self.fmt_rate(),
self.postfix,
);
let ncols = self.ncols_for_animation(
(lbar.len_ansi() + rbar.len_ansi() + self.animation.spaces() as usize) as u16,
);
if ncols > 0 {
lbar + &self.animation.fmt_render(
NonZeroU16::new(ncols).unwrap(),
self.percentage(),
&self.colour,
) + &rbar
} else {
lbar + &rbar
}
}
}
fn reset(&mut self, total: Option<usize>) {
if let Some(x) = total {
self.total = x;
}
self.counter = 0;
self.timer = Instant::now();
}
fn update(&mut self, n: usize) -> Result<bool> {
self.counter += n;
let should_refresh = self.should_refresh();
if should_refresh {
self.refresh()?;
}
Ok(should_refresh)
}
fn update_to(&mut self, n: usize) -> Result<bool> {
self.counter = n;
self.update(0)
}
fn write<T: Into<String>>(&mut self, text: T) -> Result<()> {
self.clear()?;
self.writer
.print(format!("\r{}\n", text.into()).as_bytes())?;
if self.leave {
self.refresh()?;
}
Ok(())
}
fn write_to<T: Write>(&mut self, writer: &mut T, n: Option<usize>) -> Result<bool> {
let text;
if let Some(n) = n {
self.counter += n;
if self.should_refresh() {
text = self.render().trim_ansi();
} else {
return Ok(false);
}
} else {
text = self.render().trim_ansi();
}
self.bar_length = text.len_ansi() as u16;
writer.write_all((text + "\n").as_bytes())?;
writer.flush()?;
Ok(true)
}
}
#[derive(Default)]
pub struct BarBuilder {
pb: Bar,
#[cfg(feature = "template")]
bar_format: Option<String>,
}
impl BarBuilder {
pub fn desc<T: Into<String>>(mut self, desc: T) -> Self {
self.pb.desc = desc.into();
self
}
pub fn total(mut self, total: usize) -> Self {
self.pb.total = total;
self
}
pub fn leave(mut self, leave: bool) -> Self {
self.pb.leave = leave;
self
}
pub fn ncols(mut self, ncols: u16) -> Self {
self.pb.ncols = Some(ncols);
self
}
pub fn mininterval(mut self, mininterval: f32) -> Self {
self.pb.mininterval = mininterval;
self
}
pub fn miniters(mut self, miniters: usize) -> Self {
self.pb.miniters = miniters;
self
}
pub fn dynamic_miniters(mut self, dynamic_miniters: bool) -> Self {
self.pb.dynamic_miniters = dynamic_miniters;
self
}
pub fn disable(mut self, disable: bool) -> Self {
self.pb.disable = disable;
self
}
pub fn unit<T: Into<String>>(mut self, unit: T) -> Self {
self.pb.unit = unit.into();
self
}
pub fn unit_scale(mut self, unit_scale: bool) -> Self {
self.pb.unit_scale = unit_scale;
self
}
pub fn inverse_unit(mut self, inverse_unit: bool) -> Self {
self.pb.inverse_unit = inverse_unit;
self
}
pub fn dynamic_ncols(mut self, dynamic_ncols: bool) -> Self {
self.pb.dynamic_ncols = dynamic_ncols;
self
}
pub fn initial(mut self, initial: usize) -> Self {
self.pb.counter = initial;
self
}
#[cfg(feature = "template")]
#[cfg_attr(docsrs, doc(cfg(feature = "template")))]
pub fn bar_format<T: Into<String>>(mut self, bar_format: T) -> Self {
self.bar_format = Some(bar_format.into());
self
}
pub fn position(mut self, position: u16) -> Self {
self.pb.position = position;
self
}
pub fn postfix<T: Into<String>>(mut self, postfix: T) -> Self {
self.pb.set_postfix(postfix);
self
}
pub fn unit_divisor(mut self, unit_divisor: usize) -> Self {
self.pb.unit_divisor = unit_divisor;
self
}
pub fn colour<T: Into<Colour>>(mut self, colour: T) -> Self {
self.pb.colour = Some(colour.into());
self
}
pub fn delay<T: Into<f32>>(mut self, delay: T) -> Self {
self.pb.delay = delay.into();
self
}
pub fn animation<T: Into<Animation>>(mut self, animation: T) -> Self {
self.pb.animation = animation.into();
self
}
#[cfg(feature = "spinner")]
#[cfg_attr(docsrs, doc(cfg(feature = "spinner")))]
pub fn spinner(mut self, spinner: Spinner) -> Self {
self.pb.spinner = Some(spinner);
self
}
pub fn writer(mut self, writer: Writer) -> Self {
self.pb.writer = writer.init();
self
}
pub fn force_refresh(mut self, force_refresh: bool) -> Self {
self.pb.force_refresh = force_refresh;
self
}
#[allow(unused_mut)]
pub fn build(mut self) -> ::std::result::Result<Bar, String> {
#[cfg(feature = "template")]
if let Some(bar_format) = self.bar_format {
self.pb.set_bar_format(bar_format)?;
}
#[cfg(feature = "notebook")]
if notebook::running() {
Python::attach(|py| -> PyResult<()> {
let ipywidgets = PyModule::import(py, "ipywidgets")?;
let ipython_display = PyModule::import(py, "IPython.display")?;
let int_progress = ipywidgets.getattr("IntProgress")?;
let hbox = ipywidgets.getattr("HBox")?;
let html = ipywidgets.getattr("HTML")?;
let display = ipython_display.getattr("display")?;
let kwargs = PyDict::new(py);
kwargs.set_item("min", 0)?;
if self.pb.total == 0 {
kwargs.set_item("max", 1)?;
kwargs.set_item("value", 1)?;
} else {
kwargs.set_item("max", self.pb.total)?;
}
if let Some(Colour::Solid(colour)) = &self.pb.colour {
let style = PyDict::new(py);
style.set_item("bar_color", colour)?;
kwargs.set_item("style", style)?;
}
let pb = int_progress.call((), Some(&kwargs))?;
if self.pb.ncols.is_some() {
let layout = pb.getattr("layout")?;
layout.setattr("flex", "2")?;
}
let kwargs = PyDict::new(py);
kwargs.set_item("children", [html.call0()?, pb, html.call0()?])?;
let container = hbox.call((), Some(&kwargs))?;
if let Some(ncols) = self.pb.ncols {
let layout = container.getattr("layout")?;
layout.setattr("width", format!("{}px", ncols))?;
layout.setattr("display", "inline-flex")?;
layout.setattr("flex_flow", "row wrap")?;
}
display.call1((&container,))?;
self.pb.container = Some(container.into());
Ok(())
})
.unwrap();
}
Ok(self.pb)
}
}
#[macro_export]
macro_rules! tqdm {
($($setter_method: ident = $value: expr),*) => {
$crate::BarBuilder::default()$(.$setter_method($value))*.build().unwrap()
};
($iterable: expr) => {
$crate::TqdmIterator::tqdm($iterable)
};
($iterable: expr, $($setter_method: ident = $value: expr),*) => {
$crate::TqdmIterator::tqdm_with_bar($iterable, $crate::BarBuilder::default()$(.$setter_method($value))*.build().unwrap())
};
}
#[cfg(feature = "rayon")]
#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
#[macro_export]
macro_rules! par_tqdm {
($($setter_method: ident = $value: expr),*) => {
$crate::BarBuilder::default()$(.$setter_method($value))*.build().unwrap()
};
($iterable: expr) => {
$crate::TqdmParallelIterator::tqdm($iterable)
};
($iterable: expr, $($setter_method: ident = $value: expr),*) => {
$crate::TqdmParallelIterator::tqdm_with_bar($iterable, $crate::BarBuilder::default()$(.$setter_method($value))*.build().unwrap())
};
}