From d5fe7cd57d2ee8c68354e124e29714b8a7ca1ac5 Mon Sep 17 00:00:00 2001 From: Avril Date: Sat, 11 Jul 2020 18:33:25 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 + Cargo.toml | 10 +++ src/inter.rs | 36 +++++++++ src/lib.rs | 15 ++++ src/progress.rs | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ src/spinner.rs | 110 +++++++++++++++++++++++++ src/util.rs | 16 ++++ src/wheel.rs | 65 +++++++++++++++ 8 files changed, 463 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/inter.rs create mode 100644 src/lib.rs create mode 100644 src/progress.rs create mode 100644 src/spinner.rs create mode 100644 src/util.rs create mode 100644 src/wheel.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80aca69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*~ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6c3dc95 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "termprogress" +version = "0.1.0" +authors = ["Avril "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +terminal_size = "0.1" \ No newline at end of file diff --git a/src/inter.rs b/src/inter.rs new file mode 100644 index 0000000..0c12f1d --- /dev/null +++ b/src/inter.rs @@ -0,0 +1,36 @@ +/// A trait for all bars' displaying +pub trait Display +{ + /// Refresh the display + fn refresh(&self); + /// Blank the display + fn blank(&self); + /// Blank then print a line, and redisplay. + fn println(&self, string: &str) + { + self.blank(); + println!("{}", string); + self.refresh(); + } + + /// Get the title for this display + fn get_title(&self) -> &str; + /// Set the title for this display + fn set_title(&mut self, from: &str); + + /// Update the max size if needed + fn update_dimensions(&mut self, to: usize); +} + +/// A bar with progress +pub trait ProgressBar: Display +{ + fn set_progress(&mut self, value: f64); + fn get_progress(&self) -> f64; +} + +/// A bar without progress +pub trait Spinner: Display +{ + fn bump(&mut self); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e126805 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +#![allow(dead_code)] + +mod util; +mod inter; +pub use inter::*; +pub mod progress; +pub mod wheel; +pub mod spinner; + + + +#[cfg(test)] +mod tests { + +} diff --git a/src/progress.rs b/src/progress.rs new file mode 100644 index 0000000..a7bdf99 --- /dev/null +++ b/src/progress.rs @@ -0,0 +1,208 @@ +use super::*; +use std::{ + fmt::Write, +}; + +#[derive(Debug)] +pub struct Bar +{ + width: usize, + max_width: usize, + progress: f64, + buffer: String, + title: String, +} + +/* +#[cfg(test)] +mod tests +{ + #[test] + fn display() + { + + let mut bar = Bar::new(50); + bar.set_title("hello world"); + bar.progress = 0.455456749849; + bar.update(); + bar.refresh(); + bar.progress = 0.8; + bar.update(); + bar.refresh(); + bar.progress = 0.88; + bar.update(); + bar.refresh(); + bar.set_title("almost AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + bar.progress = 0.99554; + bar.update(); + bar.refresh(); + + println!("\nuhh"); + panic!("h"); + } +}*/ + +impl Bar +{ + /// Create a new bar `width` long with a title. + pub fn with_title(width: usize, title: impl AsRef) -> Self + { + let mut this = Self::new(width); + this.set_title(title.as_ref()); + this + } + /// Create a new bar with max display width of our terminal + pub fn new(width: usize) -> Self + { + if let Some((terminal_size::Width(tw), _)) = terminal_size::terminal_size() { + let tw = usize::from(tw); + Self::with_max(if width < tw {width} else {tw}, tw) + } else { + Self::with_max(width, width +20) + } + } + /// Create a bar with a max display width + /// + /// # Panics + /// If `width` is larger than or equal to `max_width`. + pub fn with_max(width: usize, max_width: usize) -> Self + { + Self { + width, + max_width, + progress: 0.0, + buffer: String::with_capacity(width), + title: String::with_capacity(max_width - width), + } + } + + /// Fit to terminal's width if possible. + pub fn fit(&mut self) -> bool + { + + if let Some((terminal_size::Width(tw), _)) = terminal_size::terminal_size() { + let tw = usize::from(tw); + self.width = if self.width < tw {self.width} else {tw}; + self.update_dimensions(tw); + return true; + } + false + } + + /// Update the buffer. + pub fn update(&mut self) + { + self.buffer.clear(); + + let pct = (self.progress * (self.width as f64)) as usize; + for i in 0..self.width + { + if i >= pct { + write!(self.buffer, " ").unwrap(); + } else { + write!(self.buffer, "=").unwrap(); + } + } + } + + /// Consume the bar and complete it, regardless of progress. + pub fn complete(self) + { + println!(""); + } +} + +fn ensure_eq(input: String, to: usize) -> String +{ + let chars = input.chars(); + if chars.count() != to { + let mut chars = input.chars(); + let mut output = String::with_capacity(to); + for _ in 0..to + { + if let Some(c) = chars.next() { + write!(output, "{}", c).unwrap(); + } else { + write!(output, " ").unwrap(); + } + } + output + } else { + input + } +} + + +fn ensure_lower(input: String, to: usize) -> String +{ + let chars = input.chars(); + if chars.count() > to + { + let chars = input.chars(); + let mut output = String::with_capacity(to); + for (i,c) in (0..).zip(chars) + { + write!(output, "{}", c).unwrap(); + if to>3 && i == to-3 { + write!(output, "...").unwrap(); + break; + } else if i==to { + break; + } + } + + output + } else { + input + } +} + +impl Display for Bar +{ + fn refresh(&self) + { + let temp = format!("[{}]: {:.2}%", self.buffer, self.progress * 100.00); + let title = ensure_lower(format!(" {}", self.title), self.max_width - temp.chars().count()); + + let temp = ensure_eq(format!("{}{}", temp, title), self.max_width); + print!("\x1B[0m\x1B[K{}", temp); + print!("\n\x1B[1A"); + } + + fn blank(&self) + { + print!("\r{}\r", " ".repeat(self.max_width)); + } + + fn get_title(&self) -> &str + { + &self.title + } + + fn set_title(&mut self, from: &str) + { + self.title = from.to_string(); + } + + fn update_dimensions(&mut self, to: usize) + { + self.max_width = to; + self.refresh(); + } +} + +impl ProgressBar for Bar +{ + fn get_progress(&self) -> f64 + { + self.progress + } + fn set_progress(&mut self, value: f64) + { + if self.progress != value { + self.progress = value; + self.update(); + } + self.refresh(); + } +} diff --git a/src/spinner.rs b/src/spinner.rs new file mode 100644 index 0000000..52ef282 --- /dev/null +++ b/src/spinner.rs @@ -0,0 +1,110 @@ +use super::*; + +pub struct Spin +{ + title: String, + current: char, + chars: wheel::WheelIntoIter, +} + +impl Spin +{ + /// Create a new spinner with title + pub fn with_title(title: &str, whl: wheel::Wheel) -> Self + { + let mut chars = whl.into_iter(); + let current = chars.next().unwrap(); + Self { + title: title.to_string(), + current, + chars, + } + } + /// Create a new blank spinner + pub fn new(whl: wheel::Wheel) -> Self + { + let mut chars = whl.into_iter(); + let current = chars.next().unwrap(); + Self { + title: String::new(), + current, + chars, + } + } + + /// Consume the spinner and complete it + pub fn complete(self) { + println!("{} ", (8u8 as char)); + } + + /// Consume the spinner and complete it with a message + pub fn complete_with(self, msg: &str) + { + println!("{}{}", (8u8 as char), msg); + } +} + +impl Default for Spin +{ + fn default() -> Self + { + Self { + title: String::new(), + chars: wheel::Wheel::default().into_iter(), + current: '|', + } + } +} + +/* +#[cfg(test)] +mod tests +{ + #[test] + fn test() + { + const MAX: usize = 1000000; + let mut spin = Spin::with_title("Working maybe...", Default::default()); + for i in 0..=MAX + { + spin.bump(); + if i == MAX / 2 { + spin.set_title("Working more..."); + } + } + spin.complete_with("OK"); + println!("Oke"); + } +}*/ + +impl Display for Spin +{ + fn refresh(&self) + { + print!("\r{} {}", self.title, self.current); + } + fn blank(&self) + { + print!("\r{} \r", " ".repeat(self.title.chars().count())); + } + fn get_title(&self) -> &str + { + &self.title[..] + } + fn set_title(&mut self, from: &str) + { + self.blank(); + self.title = from.to_string(); + self.refresh(); + } + fn update_dimensions(&mut self, _to:usize){} +} + +impl Spinner for Spin +{ + fn bump(&mut self) + { + self.current = self.chars.next().unwrap(); + self.refresh(); + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..dacaf39 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,16 @@ +/// Copy slice `src` to `dst`, return the number of elements copied. +#[inline] +pub fn copy_slice(mut dst: U, src: V) -> usize +where T: Clone, + U: AsMut<[T]>, + V: AsRef<[T]> +{ + let mut i=0; + for (d,s) in dst.as_mut().iter_mut().zip(src.as_ref().iter()) + { + *d = s.clone(); + i+=1; + } + i +} + diff --git a/src/wheel.rs b/src/wheel.rs new file mode 100644 index 0000000..ad5e278 --- /dev/null +++ b/src/wheel.rs @@ -0,0 +1,65 @@ + +#[derive(Clone,Debug)] +pub enum Wheel +{ + Static(&'static [char]), + Dynamic(Box<[char]>), +} + +impl Wheel +{ + pub fn new(iter: T) -> Self + where T: IntoIterator + { + let col: Vec = iter.into_iter().collect(); + Self::Dynamic(col.into_boxed_slice()) + } + pub fn chars(&self) -> &[char] + { + match &self + { + Wheel::Static(s) => s, + Wheel::Dynamic(b) => &b[..], + } + } +} + +impl Default for Wheel +{ + fn default() -> Self + { + DEFAULT_WHEEL.clone() + } +} + +const DEFAULT_WHEEL: Wheel = Wheel::Static(&['/', '-', '\\', '|']); + +pub struct WheelIntoIter +{ + source: Wheel, + idx: usize, +} + +impl Iterator for WheelIntoIter +{ + type Item = char; + fn next(&mut self) -> Option + { + let chars = self.source.chars(); + self.idx = (self.idx + 1) % chars.len(); + Some(chars[self.idx]) + } +} + +impl IntoIterator for Wheel +{ + type IntoIter= WheelIntoIter; + type Item = char; + fn into_iter(self) -> Self::IntoIter + { + WheelIntoIter{ + source: self, + idx: 0, + } + } +}