use std::io::{self, Error, ErrorKind, Write};
use terminal_size::{terminal_size, Height, Width};
pub struct Prgrs<T: Iterator> {
iter: T,
size: usize,
curr: usize,
len: Length,
}
pub enum Length {
Absolute(usize),
Proportional(f64),
}
impl<T: Iterator> Prgrs<T> {
pub fn new(it: T, size: usize) -> Self {
Prgrs::<T> {
iter: it,
size,
curr: 0,
len: Length::Proportional(0.33),
}
}
pub fn set_length(&mut self, len: Length) {
self.len = len;
}
pub fn set_length_move(mut self, len: Length) -> Self {
self.len = len;
self
}
fn get_absolute_length(&self) -> usize {
match self.len {
Length::Absolute(l) => l,
Length::Proportional(mut p) => {
if let Some((Width(x), Height(_y))) = terminal_size() {
if p > 1. {
p = 1.;
} else if p < 0. {
p = 0.;
}
(x as f64 * p) as usize
} else {
50
}
}
}
}
fn get_ratio(&self) -> f64 {
self.curr as f64 / self.size as f64
}
fn create_bar(&self) -> String {
let symbol = "#";
let len = self.get_absolute_length();
let mut steps = 1;
let additional_chars = "[] (100%)".len();
if len > additional_chars + 1 {
steps = len - additional_chars;
}
let mut buf = String::from("[");
if self.size == 0 {
for _ in 0..steps {
buf.push_str(symbol);
}
} else {
let num_symbols = (self.get_ratio() * steps as f64) as usize;
for _ in 0..num_symbols {
buf.push_str(symbol);
}
for _ in 0..steps - num_symbols {
buf.push_str(" ");
}
}
buf.push_str("]");
buf
}
}
impl<T: Iterator> Iterator for Prgrs<T> {
type Item = T::Item;
fn next(&mut self) -> Option<Self::Item> {
let next = self.iter.next();
let mut percentage = self.get_ratio() * 100.;
if percentage > 100. || percentage.is_nan() {
percentage = 100.;
}
if let Some((Width(w), Height(_h))) = terminal_size() {
let whitespaces = std::iter::repeat(" ").take(w as usize).collect::<String>();
print!(
"\r{}\r{} ({:3.0}%)\r",
whitespaces,
self.create_bar(),
percentage
);
} else {
print!("{} ({:3.0}%)\r", self.create_bar(), percentage);
}
io::stdout().flush().ok();
if let None = next {
println!("");
}
self.curr += 1;
next
}
}
pub fn writeln(text: &str) -> Result<(), Error> {
if let Some((Width(w), Height(_h))) = terminal_size() {
let whitespaces = (w as usize).checked_sub(text.len()).unwrap_or(0);
let whitespaces = std::iter::repeat(" ").take(whitespaces).collect::<String>();
println!("\r{}{}", text, whitespaces);
Ok(())
} else {
Err(Error::new(
ErrorKind::Other,
"Issue determining size of your terminal",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prgrs() {
assert_eq!(Prgrs::new(1..100, 100).next(), (1..100).next());
assert_eq!(Prgrs::new(1..100, 100).last(), (1..100).last());
assert_eq!(Prgrs::new(0..0, 0).next(), None);
}
}