use crate::bar::render::Renderer;
use crate::bar::state::BarState;
use crate::bar::template::{TemplateError, render_template_with_arc_keys};
use crate::iter::wrap::ProgressBarIter;
use crate::style::style::ProgressStyle;
use crate::terminal::writer::DrawTarget;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::time::{Duration, Instant};
pub(crate) struct BarInner {
pub(crate) state: BarState,
pub(crate) renderer: Renderer,
pub(crate) tab_width: usize,
}
#[derive(Clone, Debug)]
pub struct ProgressBarBuilder {
len: Option<u64>,
style: Option<ProgressStyle>,
target: Option<DrawTarget>,
draw_delta: u64,
draw_rate: u64,
message: String,
prefix: String,
}
impl ProgressBarBuilder {
pub fn new() -> Self {
Self {
len: None,
style: None,
target: None,
draw_delta: 1,
draw_rate: 15,
message: String::new(),
prefix: String::new(),
}
}
pub fn length(mut self, len: u64) -> Self {
self.len = Some(len);
self
}
pub fn style(mut self, style: ProgressStyle) -> Self {
self.style = Some(style);
self
}
pub fn template(mut self, template: &str) -> Result<Self, TemplateError> {
self.style = Some(ProgressStyle::with_template(template)?);
Ok(self)
}
pub fn target(mut self, target: DrawTarget) -> Self {
self.target = Some(target);
self
}
pub fn draw_delta(mut self, delta: u64) -> Self {
self.draw_delta = delta;
self
}
pub fn draw_rate(mut self, rate: u64) -> Self {
self.draw_rate = rate;
self
}
pub fn message(mut self, msg: impl Into<String>) -> Self {
self.message = msg.into();
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = prefix.into();
self
}
pub fn build(self) -> ProgressBar {
let style = match self.style {
Some(style) => style,
None if self.len.is_some() => ProgressStyle::default_bar(),
None => ProgressStyle::default_spinner(),
};
let mut state = BarState::new(self.len, style);
state.draw_delta = self.draw_delta;
state.draw_rate = self.draw_rate;
state.message = self.message;
state.prefix = self.prefix;
let target = match self.target {
Some(target) => target,
None => DrawTarget::stderr(),
};
if target.is_hidden() {
state.hidden = true;
}
ProgressBar::from_parts(state, target)
}
}
impl Default for ProgressBarBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct ProgressBar {
pub(crate) inner: Arc<Mutex<BarInner>>,
steady_stop: Arc<AtomicBool>,
}
impl ProgressBar {
fn from_parts(state: BarState, target: DrawTarget) -> Self {
Self {
inner: Arc::new(Mutex::new(BarInner {
state,
renderer: Renderer::new(target),
tab_width: 4,
})),
steady_stop: Arc::new(AtomicBool::new(false)),
}
}
pub fn new(len: u64) -> Self {
Self::builder().length(len).build()
}
pub fn new_spinner() -> Self {
Self::builder()
.style(ProgressStyle::default_spinner())
.build()
}
pub fn hidden() -> Self {
Self::builder().target(DrawTarget::hidden()).build()
}
pub fn with_draw_target(len: u64, target: DrawTarget) -> Self {
Self::builder().length(len).target(target).build()
}
pub fn with_style(len: u64, style: ProgressStyle) -> Self {
Self::builder().length(len).style(style).build()
}
pub fn with_template(len: u64, template: &str) -> Result<Self, TemplateError> {
Ok(Self::with_style(
len,
ProgressStyle::with_template(template)?,
))
}
pub fn builder() -> ProgressBarBuilder {
ProgressBarBuilder::new()
}
pub fn inc(&self, delta: u64) {
let mut inner = self.lock_inner();
inner.state.pos = inner.state.pos.saturating_add(delta);
Self::draw_locked(&mut inner, false);
}
pub fn inc_by(&self, delta: u64) {
self.inc(delta);
}
pub fn set_position(&self, pos: u64) {
let mut inner = self.lock_inner();
inner.state.pos = pos;
Self::draw_locked(&mut inner, true);
}
pub fn set_length(&self, len: u64) {
let mut inner = self.lock_inner();
inner.state.len = Some(len);
Self::draw_locked(&mut inner, true);
}
pub fn set_length_unknown(&self) {
let mut inner = self.lock_inner();
inner.state.len = None;
Self::draw_locked(&mut inner, true);
}
pub fn set_message(&self, msg: impl Into<String>) {
let mut inner = self.lock_inner();
inner.state.message = msg.into();
Self::draw_locked(&mut inner, true);
}
pub fn set_prefix(&self, prefix: impl Into<String>) {
let mut inner = self.lock_inner();
inner.state.prefix = prefix.into();
Self::draw_locked(&mut inner, true);
}
pub fn set_postfix(&self, postfix: impl Into<String>) {
let mut inner = self.lock_inner();
inner.state.postfix = postfix.into();
Self::draw_locked(&mut inner, true);
}
pub fn tick(&self) {
let mut inner = self.lock_inner();
inner.state.spinner_frame_index = inner.state.spinner_frame_index.wrapping_add(1);
Self::draw_locked(&mut inner, true);
}
pub fn finish(&self) {
let mut inner = self.lock_inner();
if let Some(len) = inner.state.len {
inner.state.pos = len;
}
inner.state.finished = true;
let line = Self::line_locked(&inner);
inner.renderer.finish(&line);
inner.state.update_draw_time();
}
pub fn finish_with_message(&self, msg: impl Into<String>) {
let mut inner = self.lock_inner();
inner.state.message = msg.into();
if let Some(len) = inner.state.len {
inner.state.pos = len;
}
inner.state.finished = true;
let line = Self::line_locked(&inner);
inner.renderer.finish(&line);
inner.state.update_draw_time();
}
pub fn finish_with_symbol(&self, symbol: &str, msg: impl Into<String>) {
let mut inner = self.lock_inner();
inner.state.prefix = symbol.to_string();
inner.state.message = msg.into();
if let Some(len) = inner.state.len {
inner.state.pos = len;
}
inner.state.finished = true;
let line = Self::line_locked(&inner);
inner.renderer.finish(&line);
inner.state.update_draw_time();
}
pub fn finish_and_clear(&self) {
let mut inner = self.lock_inner();
inner.state.finished = true;
inner.renderer.clear();
inner.state.update_draw_time();
}
pub fn abandon(&self) {
let mut inner = self.lock_inner();
inner.state.abandoned = true;
let line = Self::line_locked(&inner);
inner.renderer.finish(&line);
}
pub fn abandon_with_message(&self, msg: impl Into<String>) {
let mut inner = self.lock_inner();
inner.state.message = msg.into();
inner.state.abandoned = true;
let line = Self::line_locked(&inner);
inner.renderer.finish(&line);
}
pub fn reset(&self) {
let mut inner = self.lock_inner();
inner.state.pos = 0;
inner.state.started_at = Instant::now();
inner.state.last_draw_pos = 0;
inner.state.last_draw_time = Instant::now();
inner.state.finished = false;
inner.state.abandoned = false;
inner.state.spinner_frame_index = 0;
Self::draw_locked(&mut inner, true);
}
pub fn set_style(&self, style: ProgressStyle) {
let mut inner = self.lock_inner();
inner.state.style = style;
Self::draw_locked(&mut inner, true);
}
pub fn set_template(&self, template: &str) -> Result<(), TemplateError> {
self.set_style(ProgressStyle::with_template(template)?);
Ok(())
}
pub fn set_draw_delta(&self, delta: u64) {
let mut inner = self.lock_inner();
inner.state.draw_delta = delta;
}
pub fn set_draw_rate(&self, rate: u64) {
let mut inner = self.lock_inner();
inner.state.draw_rate = rate;
}
pub fn set_tab_width(&self, n: usize) {
let mut inner = self.lock_inner();
inner.tab_width = n.max(1);
}
pub fn enable_steady_tick(&self, interval: Duration) {
{
let mut inner = self.lock_inner();
inner.state.steady_tick_interval = Some(interval);
}
self.steady_stop.store(false, Ordering::SeqCst);
let bar = self.clone();
let stop = Arc::clone(&self.steady_stop);
thread::spawn(move || {
while !stop.load(Ordering::SeqCst) && !bar.is_finished() {
bar.tick();
thread::sleep(interval);
}
});
}
pub fn disable_steady_tick(&self) {
self.steady_stop.store(true, Ordering::SeqCst);
let mut inner = self.lock_inner();
inner.state.steady_tick_interval = None;
}
pub fn println(&self, msg: &str) {
let mut inner = self.lock_inner();
inner.renderer.println(msg);
}
pub fn position(&self) -> u64 {
self.lock_inner().state.pos
}
pub fn length(&self) -> Option<u64> {
self.lock_inner().state.len
}
pub fn remaining(&self) -> Option<u64> {
let inner = self.lock_inner();
inner
.state
.len
.map(|len| len.saturating_sub(inner.state.pos))
}
pub fn fraction(&self) -> f64 {
self.lock_inner().state.fraction()
}
pub fn percent(&self) -> f64 {
self.fraction() * 100.0
}
pub fn message(&self) -> String {
self.lock_inner().state.message.clone()
}
pub fn prefix(&self) -> String {
self.lock_inner().state.prefix.clone()
}
pub fn elapsed(&self) -> Duration {
self.lock_inner().state.started_at.elapsed()
}
pub fn eta(&self) -> Duration {
self.lock_inner().state.eta()
}
pub fn per_sec(&self) -> f64 {
self.lock_inner().state.per_sec()
}
pub fn is_finished(&self) -> bool {
self.lock_inner().state.finished
}
pub fn is_abandoned(&self) -> bool {
self.lock_inner().state.abandoned
}
pub fn is_hidden(&self) -> bool {
let inner = self.lock_inner();
inner.state.hidden || inner.renderer.target.is_hidden()
}
pub fn wrap_iter<I: Iterator>(&self, iter: I) -> ProgressBarIter<I> {
ProgressBarIter::new(iter, self.clone())
}
pub(crate) fn is_complete_for_join(&self) -> bool {
let inner = self.lock_inner();
inner.state.finished || inner.state.abandoned
}
pub(crate) fn same_bar(&self, other: &ProgressBar) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
pub(crate) fn redirect_target(&self, target: DrawTarget) {
let mut inner = self.lock_inner();
if target.is_hidden() {
inner.state.hidden = true;
}
inner.renderer.set_target(target);
}
pub(crate) fn render_line_for_width(&self, width: usize) -> String {
let inner = self.lock_inner();
Self::line_for_width_locked(&inner, width)
}
fn draw_locked(inner: &mut BarInner, force: bool) {
if inner.state.hidden || inner.renderer.target.is_hidden() {
return;
}
if force || inner.state.should_redraw() {
let line = Self::line_locked(inner);
inner.renderer.draw(&line);
inner.state.update_draw_time();
}
}
fn line_locked(inner: &BarInner) -> String {
let width = inner.renderer.target.width();
Self::line_for_width_locked(inner, width)
}
fn line_for_width_locked(inner: &BarInner, width: usize) -> String {
let line = render_template_with_arc_keys(
&inner.state.style.parsed_template,
&inner.state,
width,
&inner.state.style.custom_keys,
);
line.replace('\t', &" ".repeat(inner.tab_width))
}
fn lock_inner(&self) -> MutexGuard<'_, BarInner> {
match self.inner.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
}
impl Default for ProgressBar {
fn default() -> Self {
Self::hidden()
}
}
impl fmt::Debug for ProgressBar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = self.lock_inner();
f.debug_struct("ProgressBar")
.field("pos", &inner.state.pos)
.field("len", &inner.state.len)
.field("message", &inner.state.message)
.field("prefix", &inner.state.prefix)
.field("finished", &inner.state.finished)
.field("hidden", &self.is_hidden())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_bar_initial_state() {
let pb = ProgressBar::with_draw_target(10, DrawTarget::hidden());
assert_eq!(pb.position(), 0);
assert_eq!(pb.length(), Some(10));
}
#[test]
fn test_inc_updates_position() {
let pb = ProgressBar::hidden();
pb.inc(3);
assert_eq!(pb.position(), 3);
}
#[test]
fn test_set_position_directly() {
let pb = ProgressBar::hidden();
pb.set_position(9);
assert_eq!(pb.position(), 9);
}
#[test]
fn test_set_length() {
let pb = ProgressBar::hidden();
pb.set_length(7);
assert_eq!(pb.length(), Some(7));
}
#[test]
fn test_set_length_unknown() {
let pb = ProgressBar::hidden();
pb.set_length(7);
pb.set_length_unknown();
assert_eq!(pb.length(), None);
}
#[test]
fn test_remaining_fraction_percent() {
let pb = ProgressBar::hidden();
pb.set_length(8);
pb.inc(2);
assert_eq!(pb.remaining(), Some(6));
assert_eq!(pb.fraction(), 0.25);
assert_eq!(pb.percent(), 25.0);
}
#[test]
fn test_finish_marks_done() {
let pb = ProgressBar::hidden();
pb.finish();
assert!(pb.is_finished());
}
#[test]
fn test_finish_with_message() {
let pb = ProgressBar::hidden();
pb.finish_with_message("done");
assert_eq!(pb.message(), "done");
}
#[test]
fn test_finish_with_symbol() {
let pb = ProgressBar::hidden();
pb.finish_with_symbol("ok", "done");
assert!(pb.is_finished());
assert_eq!(pb.prefix(), "ok");
assert_eq!(pb.message(), "done");
}
#[test]
fn test_finish_and_clear() {
let pb = ProgressBar::hidden();
pb.finish_and_clear();
assert!(pb.is_finished());
}
#[test]
fn test_abandon_does_not_mark_finished() {
let pb = ProgressBar::hidden();
pb.abandon();
assert!(!pb.is_finished());
assert!(pb.is_abandoned());
assert!(pb.is_complete_for_join());
}
#[test]
fn test_reset_clears_pos() {
let pb = ProgressBar::hidden();
pb.inc(4);
pb.reset();
assert_eq!(pb.position(), 0);
}
#[test]
fn test_is_finished_false_initially() {
assert!(!ProgressBar::hidden().is_finished());
}
#[test]
fn test_hidden_bar() {
assert!(ProgressBar::hidden().is_hidden());
}
#[test]
fn test_clone_shares_state() {
let pb = ProgressBar::hidden();
let clone = pb.clone();
clone.inc(2);
assert_eq!(pb.position(), 2);
}
#[test]
fn test_per_sec_nonzero_after_progress() {
let pb = ProgressBar::hidden();
{
let mut inner = pb.lock_inner();
inner.state.started_at = Instant::now() - Duration::from_secs(1);
}
pb.inc(1);
assert!(pb.per_sec() > 0.0);
}
#[test]
fn test_set_style() -> Result<(), crate::bar::template::TemplateError> {
let pb = ProgressBar::hidden();
pb.set_style(ProgressStyle::with_template("{pos}")?);
assert!(!pb.render_line_for_width(80).is_empty());
Ok(())
}
#[test]
fn test_set_template() -> Result<(), crate::bar::template::TemplateError> {
let pb = ProgressBar::hidden();
pb.set_template("{pos}/{len}")?;
assert_eq!(pb.render_line_for_width(80), "0/?");
Ok(())
}
#[test]
fn test_set_draw_delta() {
let pb = ProgressBar::hidden();
pb.set_draw_delta(10);
let inner = pb.lock_inner();
assert_eq!(inner.state.draw_delta, 10);
}
#[test]
fn test_builder_pattern() {
let pb = ProgressBar::builder()
.length(10)
.message("hello")
.prefix("job")
.target(DrawTarget::hidden())
.build();
assert_eq!(pb.length(), Some(10));
assert_eq!(pb.message(), "hello");
assert_eq!(pb.prefix(), "job");
}
#[test]
fn test_builder_template() -> Result<(), crate::bar::template::TemplateError> {
let pb = ProgressBar::builder()
.template("{pos}/{len}")?
.length(3)
.target(DrawTarget::hidden())
.build();
assert_eq!(pb.render_line_for_width(80), "0/3");
Ok(())
}
#[test]
fn test_with_style_and_template() -> Result<(), crate::bar::template::TemplateError> {
let styled = ProgressBar::with_style(3, ProgressStyle::with_template("{percent}")?);
assert_eq!(styled.length(), Some(3));
let templated = ProgressBar::with_template(4, "{pos}/{len}")?;
assert_eq!(templated.length(), Some(4));
Ok(())
}
#[test]
fn test_wrap_iter_length() {
let pb = ProgressBar::hidden();
let iter = pb.wrap_iter(0..3);
assert_eq!(iter.len(), 3);
}
}