indicatif
A Rust library for indicating progress in command line applications to users.
This currently primarily provides progress bars and spinners as well as basic color support, but there are bigger plans for the future of this!
Category: Rust / Command-line |
Watchers: 16 |
Star: 3.4k |
Fork: 198 |
Last update: Mar 26, 2023 |
A Rust library for indicating progress in command line applications to users.
This currently primarily provides progress bars and spinners as well as basic color support, but there are bigger plans for the future of this!
joining
field of MultiProgress
from AtomicBool
to Mutex
.
Because the internal join_impl
can now be called multiple times
I figured it's better to use a Mutex as it automatically unlocks
preventing panics "bricking" the progress bar.self.rx.recv()
) seems too small. Because of that I
allow the user to configure how long do they want a tick to last
adding greater flexibility.is_done
public to let the user know whether they still need
to invoke tick
Fixes #33, seems like fixes #125
Currently messages with println!()
are overwritten by the next progress bar tick. It would be awesome to have something like pb.log("hello")
and have hello printed with the next tick above the bars.
While using indicatif, I had modified the iterator API to both a) work with iterator chaining and b) support parallel iterators with Rayon. The changes look like:
let mut v = vec![1, 2, 3];
// used to be:
let pb = ProgressBar::new(v.len());
for item in pb.wrap_iter(v.iter()) {
...
}
// now is:
for item in v.iter().progress_count(v.len()) {
...
}
// also parallel iterators
v.par_iter().progress().map(|i| { ... }).collect()
I added a feature gate so the user does not have to download Rayon if they're not using it.
I wanted to show this initial PR to see if the maintainers would be interested in either change. If so, I can go ahead and add more tests/documentation.
I did another try of indicatif 0.13.0, and found that as soon as I only one ProgressBar into MultiProgress it starts to flicker the same I described in https://github.com/mitsuhiko/indicatif/issues/42.
Regards,
This allows logging some information without interfering with the progress bar.
extern crate indicatif;
use std::thread;
use std::time::Duration;
use indicatif::ProgressBar;
fn main() {
let pb = ProgressBar::new(100);
for i in 0..100 {
thread::sleep(Duration::from_millis(250));
pb.println(format!("[+] finished #{}", i));
pb.inc(1);
}
pb.finish_with_message("done");
}
Resolves #27
It's my first time using Rayon and Indicatif so apologies if this is a naive question. I'm trying to use Rayon to iterate over a set of files. Code is like this:
let mp = MultiProgress::new();
// Long calculation is running. Bars are created but do not paint.
let total_bytes: u64 = config.input_files.par_iter()
.map(|f| process_log_file(&mp, f))
.sum();
// This appears first.
println!("Rayon iteration complete");
// Then the bars appear (and update themselves 0..100%) when we get to here.
// But by this point we are done!
mp.join().unwrap();
Inside process_log_file
I am creating a new ProgressBar
, adding it to mp, incrementing it and eventually calling finish
.
As the comments say, the problem is that nothing appears until Rayon's par_iter has actually finished doing work. You have lots of examples of using thread::spawn
and a tokio example, but I couldn't see anything like this.
Is there a fix I can make or is it impossible at the moment?
For reference, the current code in complete form is at https://github.com/PhilipDaniels/log-file-processor/blob/f-rewrite/src/main.rs
Thanks for any advice you can offer.
Please consider keeping either CHANGELOG.md
or putting changes into github release.
In my company, we are using indicatif for our internal tools, and it's great. However, the lack of information about what changed between 0.14 and 0.15 blocks me from upgrading to the newest version. Of course, I can compare tags and see what commits were made but it's far from pleasant.
What do I mean: Like wide_msg / wide_bar something like wide_char:[some char here] that is simply repeated.
Why? You could easily draw boxed around your progress bar using the unicode box drawing characters:
╔{wide_char:═}╗ ║{wide_bar}║ ╚{wide_char:═}╝
The implementation would not be too complex an maybe some people with retro-ideas would benefit from it.
When processing several hundreds of thousands of items per second with a ParallelIterator
, ProgressBar
becomes a bottleneck because it grabs a global shared lock whenever the state needs to be updated. Instead, it would be better to store internal progress for each rayon thread separately and sum these counters on read.
Having said that as a Rust beginner, I'm not sure how hard such implementation could be in Rust and whether Rust has a similar structure like Java LongAdder
(https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html).
RelaxedCounter
from https://docs.rs/atomic-counter/1.0.1/atomic_counter/ maybe?
I wanted to suggest a modification to the default behavior of the {bar}
and {msg}
format specifiers.
My suggestion is to make both of them truncate by default, and to make {wide_bar}
and {wide_msg}
serve as hints as to what should be truncated first.
So, if the format specifier is {bar} {msg}
, both with be truncated by default, and if the format specifier is {wide_bar} {msg}
, the bar will be truncated before the message.
I think that truncation by default is probably the most useful behavior, and if users specifically want the current behavior of overflow/repitition of the bar on narrow terminals, something like {fixed_bar}
and {fixed_msg}
could be added to support that.
For background/description, see #132
use indicatif::{MultiProgress, ProgressBar, TickTimeLimit, ProgressStyle, ProgressDrawTarget};
use rand::Rng;
use std::thread;
use std::time::Duration;
fn main() {
let sty = ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta}) File")
.progress_chars("##-");
let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::stderr_nohz());
let pb1 = mpb.add(ProgressBar::new(30));
let pb2 = mpb.add(ProgressBar::new(30));
pb1.set_style(sty.clone());
pb2.set_style(sty);
// To use thread, uncomment this and comment mbp.tick() at bottom.
/*
let _ = thread::spawn(move || {
loop {
mpb.tick(TickTimeLimit::Indefinite);
thread::sleep(Duration::from_millis(10));
}
});
*/
let mut rng = rand::thread_rng();
for _ in 0..50 {
thread::sleep(Duration::from_millis(2000));
pb1.set_position(rng.gen_range(0, 30));
pb2.set_position(rng.gen_range(0, 30));
mpb.tick(TickTimeLimit::Indefinite);
}
}
When calling enable_steady_tick
and then calling it again, the update interval doesn't change. The example below updates only every 3s despite calling enable_steady_tick(1000)
.
use indicatif::{ProgressBar, ProgressStyle};
fn main() {
let pb = ProgressBar::new(1).with_style(ProgressStyle::default_bar().template("{elapsed_precise}"));
pb.enable_steady_tick(3000);
pb.enable_steady_tick(1000);
loop {}
}