From 80c849763165e6bbe56f75913ef76de9a9daae5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Tue, 25 Jul 2017 10:08:04 +0200 Subject: [PATCH 01/13] use termion for all non-windows platforms --- Cargo.toml | 6 +++-- src/tty/mod.rs | 9 ++------ src/tty/redox.rs | 13 ----------- src/tty/unix.rs | 57 +++++++++++++++++++++++++----------------------- 4 files changed, 36 insertions(+), 49 deletions(-) delete mode 100644 src/tty/redox.rs diff --git a/Cargo.toml b/Cargo.toml index 6c388767..2e6b20de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,13 @@ license = "MIT" [dependencies] libc = "0.2.9" time = "0.1.35" + +[target.'cfg(target_os = "windows")'.dependencies] winapi = "0.2" kernel32-sys = "0.2" -[target.'cfg(target_os = "redox")'.dependencies] -termion = "1.4" +[target.'cfg(not(target_os = "windows"))'.dependencies] +termion = "1.5" [dev-dependencies] rand = "0.3.14" diff --git a/src/tty/mod.rs b/src/tty/mod.rs index db9dfa55..5af63a81 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -12,17 +12,12 @@ pub struct Width(pub u16); #[derive(Debug)] pub struct Height(pub u16); -#[cfg(unix)] +#[cfg(not(windows))] mod unix; -#[cfg(unix)] +#[cfg(not(windows))] pub use self::unix::*; #[cfg(windows)] mod windows; #[cfg(windows)] pub use self::windows::*; - -#[cfg(target_os = "redox")] -mod redox; -#[cfg(target_os = "redox")] -pub use self::redox::*; diff --git a/src/tty/redox.rs b/src/tty/redox.rs deleted file mode 100644 index 2f9bd8c2..00000000 --- a/src/tty/redox.rs +++ /dev/null @@ -1,13 +0,0 @@ -extern crate termion; -use super::{Width, Height}; - -pub fn terminal_size() -> Option<(Width, Height)> { - match termion::terminal_size() { - Ok((cols, rows)) => Some((Width(cols), Height(rows))), - Err(..) => None - } -} - -pub fn move_cursor_up(n: usize) -> String { - format!("{}", termion::cursor::Up(n as u16)) -} diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 8330cd80..65e491df 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,40 +1,41 @@ +extern crate termion; extern crate libc; use super::{Width, Height}; /// Returns the size of the terminal, if available. /// /// If STDOUT is not a tty, returns `None` +#[cfg(target_os = "redox")] pub fn terminal_size() -> Option<(Width, Height)> { - use self::libc::{ioctl, isatty, STDOUT_FILENO, TIOCGWINSZ, winsize}; - let is_tty: bool = unsafe { isatty(STDOUT_FILENO) == 1 }; - - if !is_tty { - return None; + match termion::terminal_size() { + Ok((cols, rows)) => Some((Width(cols), Height(rows))), + Err(..) => None } +} - let (rows, cols) = unsafe { - let mut winsize = winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut winsize); - let rows = if winsize.ws_row > 0 { - winsize.ws_row - } else { - 0 - }; - let cols = if winsize.ws_col > 0 { - winsize.ws_col +#[cfg(not(target_os = "redox"))] +fn terminal_size_fd(fd: libc::c_int) -> Option<(Width, Height)> { + use std::mem; + + unsafe { + let mut size: libc::winsize = mem::zeroed(); + if libc::ioctl(fd, libc::TIOCGWINSZ, &mut size as *mut _) == 0 { + Some((Width(size.ws_col), Height(size.ws_row))) } else { - 0 - }; - (rows as u16, cols as u16) - }; + None + } + } +} - if rows > 0 && cols > 0 { - Some((Width(cols), Height(rows))) +/// Returns the size of the terminal, if available. +/// +/// If neither STDOUT nor STDERR is a tty, returns `None` +#[cfg(not(target_os = "redox"))] +pub fn terminal_size() -> Option<(Width, Height)> { + if unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 } { + terminal_size_fd(libc::STDOUT_FILENO) + } else if unsafe { libc::isatty(libc::STDERR_FILENO) == 1 }{ + terminal_size_fd(libc::STDERR_FILENO) } else { None } @@ -42,9 +43,11 @@ pub fn terminal_size() -> Option<(Width, Height)> { /// Return string that move the cursor `n` lines up. pub fn move_cursor_up(n: usize) -> String { - format!("\x1B[{}A", n) + assert!(n < 0x10000); + format!("{}", termion::cursor::Up(n as u16)) } +#[cfg(not(target_os = "redox"))] #[test] /// Compare with the output of `stty size` fn compare_with_stty() { From f54b4d7eea90c7551988ff815e1b024b9675f92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Tue, 25 Jul 2017 10:08:55 +0200 Subject: [PATCH 02/13] time crate has no macros --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 236f7035..0057850e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,7 +111,6 @@ macro_rules! printfl { }} } -#[macro_use] extern crate time; mod tty; mod pb; From 4bf287f4b50351a0b3fbff0471ef53585f2685ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 14:02:48 +0200 Subject: [PATCH 03/13] fix doc tests or ignore them --- src/multi.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index 86707ea7..c39dc4d1 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -22,7 +22,7 @@ impl MultiBar { /// /// # Examples /// - /// ```no_run + /// ```ignore /// use std::thread; /// use pbr::MultiBar; /// @@ -91,7 +91,7 @@ impl MultiBar { /// /// # Examples /// - /// ```no_run + /// ```ignore /// use pbr::MultiBar; /// /// let mut mb = MultiBar::new(); @@ -127,7 +127,7 @@ impl MultiBar { /// /// # Examples /// - /// ```no_run + /// ```ignore /// use pbr::MultiBar; /// /// let mut mb = MultiBar::new(); @@ -171,8 +171,11 @@ impl MultiBar { /// /// # Examples /// - /// ```no_run - /// use pbr::MultiBar; + /// ``` + /// # extern crate pbr; + /// # use std::thread; + /// # fn main() { + /// use ::pbr::MultiBar; /// /// let mut mb = MultiBar::new(); /// @@ -186,6 +189,7 @@ impl MultiBar { /// }); /// /// // ... + /// # } /// ``` pub fn listen(&mut self) { let mut first = true; From eef0de82cfc45a376238435ab702eb5a373f6588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 12:28:51 +0200 Subject: [PATCH 04/13] avoid temporary String allocation --- src/multi.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/multi.rs b/src/multi.rs index c39dc4d1..941798cb 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -212,7 +212,9 @@ impl MultiBar { first = false; } for l in self.lines.iter() { - out.push_str(&format!("\r{}\n", l)); + out += "\r"; + out += &l; + out += "\n"; } printfl!(self.handle, "{}", out); } From f08b41ef6ddd614f9963ca2b3b2e20893cc63094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 12:31:20 +0200 Subject: [PATCH 05/13] use tty with module name in multi.rs --- src/multi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index 941798cb..d644b94f 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,6 +1,6 @@ use pb::ProgressBar; use std::str::from_utf8; -use tty::move_cursor_up; +use tty; use std::io::{Stdout, Result, Write}; use std::sync::mpsc; use std::sync::mpsc::{Sender, Receiver}; @@ -207,7 +207,7 @@ impl MultiBar { // and draw let mut out = String::new(); if !first { - out += &move_cursor_up(self.nlines); + out += &tty::move_cursor_up(self.nlines); } else { first = false; } From 7c8dc7cbe24f24b4c8c963938f2a41967bf01671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 12:36:48 +0200 Subject: [PATCH 06/13] drop pb handle after finish* --- src/pb.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pb.rs b/src/pb.rs index 461823f7..b78598c7 100644 --- a/src/pb.rs +++ b/src/pb.rs @@ -61,7 +61,7 @@ pub struct ProgressBar { pub show_time_left: bool, pub show_tick: bool, pub show_message: bool, - handle: T, + handle: Option, } impl ProgressBar { @@ -134,7 +134,7 @@ impl ProgressBar { message: String::new(), last_refresh_time: SteadyTime::now(), max_refresh_rate: None, - handle: handle, + handle: Some(handle), }; pb.format(FORMAT); pb.tick_format(TICK_FORMAT); @@ -264,7 +264,7 @@ impl ProgressBar { /// ``` pub fn tick(&mut self) { self.tick_state = (self.tick_state + 1) % self.tick.len(); - if self.current <= self.total { + if self.handle.is_some() && self.current <= self.total { self.draw() } } @@ -391,7 +391,7 @@ impl ProgressBar { out = out + repeat!(" ", gap); } // print - printfl!(self.handle, "\r{}", out); + self.handle.as_mut().map(|h| printfl!(h, "\r{}", out)); self.last_refresh_time = SteadyTime::now(); } @@ -413,7 +413,7 @@ impl ProgressBar { redraw = true; } - if redraw { + if self.handle.is_some() && redraw { self.draw(); } self.is_finish = true; @@ -423,7 +423,7 @@ impl ProgressBar { /// the last time pub fn finish(&mut self) { self.finish_draw(); - printfl!(self.handle, ""); + self.handle.take().map(|mut h| printfl!(h, "")); } @@ -435,8 +435,7 @@ impl ProgressBar { if s.len() < width { out += repeat!(" ", width - s.len()); }; - printfl!(self.handle, "\r{}", out); - self.finish(); + self.handle.take().map(|mut h| printfl!(h, "\r{}", out)); } @@ -451,7 +450,7 @@ impl ProgressBar { return self.finish_print(s); } self.finish_draw(); - printfl!(self.handle, "\n{}", s); + self.handle.take().map(|mut h| printfl!(h, "\n{}", s)); } /// Get terminal width, from configuration, terminal size, or default(80) From 316924c268e188575d5ba5b6739ba9af5e6c1c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Wed, 2 Aug 2017 21:52:28 +0200 Subject: [PATCH 07/13] fix multibar resource leak --- src/pb.rs | 5 ++++- tests/lib.rs | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/pb.rs b/src/pb.rs index b78598c7..ef204036 100644 --- a/src/pb.rs +++ b/src/pb.rs @@ -435,7 +435,10 @@ impl ProgressBar { if s.len() < width { out += repeat!(" ", width - s.len()); }; - self.handle.take().map(|mut h| printfl!(h, "\r{}", out)); + self.handle.take().map(|mut h| { + printfl!(h, "\r{}", out); + printfl!(h, ""); + }); } diff --git a/tests/lib.rs b/tests/lib.rs index 4c291343..65a9f293 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,6 +1,6 @@ extern crate pbr; -use pbr::{ProgressBar, PbIter}; +use pbr::{ProgressBar, PbIter, MultiBar}; use std::time::Duration; use std::thread; @@ -107,3 +107,20 @@ fn npm_bar() { } pb.finish_println("done!"); } + +#[test] +fn multi_finish_print() { + let count = 10; + let mut mb = MultiBar::new(); + let mut pb = mb.create_bar(10); + pb.tick(); + let t = thread::spawn(move || { + mb.listen(); + }); + for _ in 0..count { + thread::sleep(Duration::from_millis(30)); + pb.tick(); + } + pb.finish_print("done"); + t.join().unwrap(); +} From f736eaf578035958f97b8f049edd79a1ae467066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 15:36:45 +0200 Subject: [PATCH 08/13] ProgressReceiver instead of Write --- src/lib.rs | 45 ++++++++++++++++++++++++++++++ src/multi.rs | 77 +++++++++++++++++++++++++++++++++++----------------- src/pb.rs | 18 ++++++------ 3 files changed, 105 insertions(+), 35 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0057850e..36d7975a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,3 +168,48 @@ impl Iterator for PbIter self.iter.size_hint() } } + +mod private { + pub trait SealedProgressReceiver { + // rewrite progress. cursor stays on progress line. + // line must not contain any newlines (`\r` or `\n`) + fn update_progress(&mut self, line: &str); + // replace progress with message. last call. + // line must not contain any newlines (`\r` or `\n`) + fn clear_progress(&mut self, line: &str); + // write below progress. last call. + // line must not contain any newlines (`\r` or `\n`) + // empty line doesn't produce a separate line of output + fn finish_with(&mut self, line: &str); + } +} + +pub trait ProgressReceiver: private::SealedProgressReceiver { +} + +impl private::SealedProgressReceiver for T { + fn update_progress(&mut self, line: &str) { + self.write(b"\r").expect("write() fail"); + self.write(line.as_bytes()).expect("write() fail"); + self.flush().expect("flush() fail"); + } + + fn clear_progress(&mut self, line: &str) { + self.write(b"\r").expect("write() fail"); + self.write(line.as_bytes()).expect("write() fail"); + self.write(b"\n").expect("write() fail"); + self.flush().expect("flush() fail"); + } + + fn finish_with(&mut self, line: &str) { + self.write(b"\n").expect("write() fail"); + if !line.is_empty() { + self.write(line.as_bytes()).expect("write() fail"); + self.write(b"\n").expect("write() fail"); + } + self.flush().expect("flush() fail"); + } +} + +impl ProgressReceiver for T { +} diff --git a/src/multi.rs b/src/multi.rs index d644b94f..8a2d5bca 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,7 +1,6 @@ use pb::ProgressBar; -use std::str::from_utf8; use tty; -use std::io::{Stdout, Result, Write}; +use std::io::{Stdout, Write}; use std::sync::mpsc; use std::sync::mpsc::{Sender, Receiver}; @@ -195,14 +194,23 @@ impl MultiBar { let mut first = true; let mut nbars = self.nbars; while nbars > 0 { - // receive message let msg = self.chan.1.recv().unwrap(); - if msg.done { - nbars -= 1; - continue; + match msg { + WriteMsg::ProgressUpdate{level,line} => { + self.lines[level] = line; + }, + WriteMsg::ProgressClear{level,line} => { + self.lines[level] = line; + nbars -= 1; + }, + WriteMsg::ProgressFinish{level,line} => { + // writing lines below progress not supported; + // replace progress instead + self.lines[level] = line; + nbars -= 1; + }, } - self.lines[msg.level] = msg.string; // and draw let mut out = String::new(); @@ -226,29 +234,48 @@ pub struct Pipe { chan: Sender, } -impl Write for Pipe { - fn write(&mut self, buf: &[u8]) -> Result { - let s = from_utf8(buf).unwrap().to_owned(); - self.chan - .send(WriteMsg { - // finish method emit empty string - done: s == "", - level: self.level, - string: s, - }) - .unwrap(); - Ok(1) +impl ::private::SealedProgressReceiver for Pipe { + fn update_progress(&mut self, line: &str) { + self.chan.send(WriteMsg::ProgressUpdate{ + level: self.level, + line: line.to_string(), + }) + .unwrap(); + } + + fn clear_progress(&mut self, line: &str) { + self.chan.send(WriteMsg::ProgressClear{ + level: self.level, + line: line.to_string(), + }) + .unwrap(); } - fn flush(&mut self) -> Result<()> { - Ok(()) + fn finish_with(&mut self, line: &str) { + self.chan.send(WriteMsg::ProgressFinish{ + level: self.level, + line: line.to_string(), + }) + .unwrap(); } } +impl ::ProgressReceiver for Pipe { +} + // WriteMsg is the message format used to communicate // between MultiBar and its bars -struct WriteMsg { - done: bool, - level: usize, - string: String, +enum WriteMsg { + ProgressUpdate { + level: usize, + line: String, + }, + ProgressClear { + level: usize, + line: String, + }, + ProgressFinish { + level: usize, + line: String, + }, } diff --git a/src/pb.rs b/src/pb.rs index ef204036..a160a286 100644 --- a/src/pb.rs +++ b/src/pb.rs @@ -4,6 +4,7 @@ use std::time::Duration; use time::{self, SteadyTime}; use std::io::Stdout; use tty::{Width, terminal_size}; +use ProgressReceiver; macro_rules! kb_fmt { ($n: ident) => {{ @@ -36,7 +37,7 @@ pub enum Units { Bytes, } -pub struct ProgressBar { +pub struct ProgressBar { start_time: SteadyTime, units: Units, pub total: u64, @@ -88,7 +89,7 @@ impl ProgressBar { } } -impl ProgressBar { +impl ProgressBar { /// Create a new ProgressBar with default configuration but /// pass an arbitrary writer. /// @@ -391,7 +392,7 @@ impl ProgressBar { out = out + repeat!(" ", gap); } // print - self.handle.as_mut().map(|h| printfl!(h, "\r{}", out)); + self.handle.as_mut().map(|h| h.update_progress(&out)); self.last_refresh_time = SteadyTime::now(); } @@ -423,7 +424,7 @@ impl ProgressBar { /// the last time pub fn finish(&mut self) { self.finish_draw(); - self.handle.take().map(|mut h| printfl!(h, "")); + self.handle.take().map(|mut h| h.finish_with("")); } @@ -435,10 +436,7 @@ impl ProgressBar { if s.len() < width { out += repeat!(" ", width - s.len()); }; - self.handle.take().map(|mut h| { - printfl!(h, "\r{}", out); - printfl!(h, ""); - }); + self.handle.take().map(|mut h| h.clear_progress(&out)); } @@ -453,7 +451,7 @@ impl ProgressBar { return self.finish_print(s); } self.finish_draw(); - self.handle.take().map(|mut h| printfl!(h, "\n{}", s)); + self.handle.take().map(|mut h| h.finish_with(s)); } /// Get terminal width, from configuration, terminal size, or default(80) @@ -469,7 +467,7 @@ impl ProgressBar { } // Implement io::Writer -impl Write for ProgressBar { +impl Write for ProgressBar { fn write(&mut self, buf: &[u8]) -> io::Result { let n = buf.len(); self.add(n as u64); From 1adbeb10387d78d8179febaf3cbc44cfe0496533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 13:42:54 +0200 Subject: [PATCH 09/13] use "clear_current_line" instead of padding message with spaces [WIP, missing windows] --- src/lib.rs | 3 ++- src/multi.rs | 4 ++-- src/pb.rs | 12 +----------- src/tty/unix.rs | 4 ++++ 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 36d7975a..69100f65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,13 +189,14 @@ pub trait ProgressReceiver: private::SealedProgressReceiver { impl private::SealedProgressReceiver for T { fn update_progress(&mut self, line: &str) { + self.write(tty::clear_current_line().as_bytes()).expect("write() fail"); self.write(b"\r").expect("write() fail"); self.write(line.as_bytes()).expect("write() fail"); self.flush().expect("flush() fail"); } fn clear_progress(&mut self, line: &str) { - self.write(b"\r").expect("write() fail"); + self.write(tty::clear_current_line().as_bytes()).expect("write() fail"); self.write(line.as_bytes()).expect("write() fail"); self.write(b"\n").expect("write() fail"); self.flush().expect("flush() fail"); diff --git a/src/multi.rs b/src/multi.rs index 8a2d5bca..11f96757 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -201,13 +201,13 @@ impl MultiBar { self.lines[level] = line; }, WriteMsg::ProgressClear{level,line} => { - self.lines[level] = line; + self.lines[level] = tty::clear_current_line() + &line; nbars -= 1; }, WriteMsg::ProgressFinish{level,line} => { // writing lines below progress not supported; // replace progress instead - self.lines[level] = line; + self.lines[level] = tty::clear_current_line() + &line; nbars -= 1; }, } diff --git a/src/pb.rs b/src/pb.rs index a160a286..503e0291 100644 --- a/src/pb.rs +++ b/src/pb.rs @@ -431,12 +431,7 @@ impl ProgressBar { /// Call finish and write string `s` that will replace the progress bar. pub fn finish_print(&mut self, s: &str) { self.finish_draw(); - let width = self.width(); - let mut out = format!("{}", s); - if s.len() < width { - out += repeat!(" ", width - s.len()); - }; - self.handle.take().map(|mut h| h.clear_progress(&out)); + self.handle.take().map(|mut h| h.clear_progress(s)); } @@ -445,11 +440,6 @@ impl ProgressBar { /// If the ProgressBar is part of MultiBar instance, you should use /// `finish_print` to print message. pub fn finish_println(&mut self, s: &str) { - // `finish_println` does not allow in MultiBar mode, because printing - // new line will break the multiBar output. - if self.is_multibar { - return self.finish_print(s); - } self.finish_draw(); self.handle.take().map(|mut h| h.finish_with(s)); } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 65e491df..341ce377 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -47,6 +47,10 @@ pub fn move_cursor_up(n: usize) -> String { format!("{}", termion::cursor::Up(n as u16)) } +pub fn clear_current_line() -> String { + format!("{}", termion::clear::CurrentLine) +} + #[cfg(not(target_os = "redox"))] #[test] /// Compare with the output of `stty size` From 8a76e4e98a91192e9002c50dd18b9137219fa104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 13:45:29 +0200 Subject: [PATCH 10/13] allow boxing ProgressReceiver --- src/pb.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/pb.rs b/src/pb.rs index 503e0291..57f0eaea 100644 --- a/src/pb.rs +++ b/src/pb.rs @@ -37,6 +37,24 @@ pub enum Units { Bytes, } +pub struct ProgressReceiverBox(Box); +impl ::private::SealedProgressReceiver for ProgressReceiverBox { + fn update_progress(&mut self, line: &str) { + self.0.update_progress(line); + } + + fn clear_progress(&mut self, line: &str) { + self.0.clear_progress(line); + } + + fn finish_with(&mut self, line: &str) { + self.0.finish_with(line); + } +} + +impl ProgressReceiver for ProgressReceiverBox { +} + pub struct ProgressBar { start_time: SteadyTime, units: Units, @@ -142,6 +160,40 @@ impl ProgressBar { pb } + pub fn to_box(mut self) -> ProgressBar + where + T: 'static, + { + let handle = self.handle.take().map(|h| ProgressReceiverBox(Box::new(h))); + ProgressBar{ + total: self.total, + current: self.current, + start_time: self.start_time, + units: self.units, + is_finish: self.is_finish, + is_multibar: self.is_multibar, + show_bar: self.show_bar, + show_speed: self.show_speed, + show_percent: self.show_percent, + show_counter: self.show_counter, + show_time_left: self.show_time_left, + show_tick: self.show_tick, + show_message: self.show_message, + bar_start: self.bar_start, + bar_current: self.bar_current, + bar_current_n: self.bar_current_n, + bar_remain: self.bar_remain, + bar_end: self.bar_end, + tick: self.tick, + tick_state: self.tick_state, + width: self.width, + message: self.message, + last_refresh_time: self.last_refresh_time, + max_refresh_rate: self.max_refresh_rate, + handle: handle, + } + } + /// Set units, default is simple numbers /// /// # Examples From 0d39d9efcce551be175c0569e668084cb9c1ae44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 27 Jul 2017 17:32:14 +0200 Subject: [PATCH 11/13] support logging [WIP, missing windows] --- src/lib.rs | 6 ++--- src/multi.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++---- src/tty/unix.rs | 8 ++++-- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 69100f65..82300fac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ mod tty; mod pb; mod multi; pub use pb::{ProgressBar, Units}; -pub use multi::{MultiBar, Pipe}; +pub use multi::{MultiBar, Pipe, LogTarget}; use std::io::{Write, Stdout, stdout}; pub struct PbIter @@ -189,14 +189,14 @@ pub trait ProgressReceiver: private::SealedProgressReceiver { impl private::SealedProgressReceiver for T { fn update_progress(&mut self, line: &str) { - self.write(tty::clear_current_line().as_bytes()).expect("write() fail"); self.write(b"\r").expect("write() fail"); self.write(line.as_bytes()).expect("write() fail"); self.flush().expect("flush() fail"); } fn clear_progress(&mut self, line: &str) { - self.write(tty::clear_current_line().as_bytes()).expect("write() fail"); + self.write(b"\r").expect("write() fail"); + self.write(tty::clear_until_newline().as_bytes()).expect("write() fail"); self.write(line.as_bytes()).expect("write() fail"); self.write(b"\n").expect("write() fail"); self.flush().expect("flush() fail"); diff --git a/src/multi.rs b/src/multi.rs index 11f96757..87158415 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,6 +1,7 @@ use pb::ProgressBar; +use std::str::from_utf8; use tty; -use std::io::{Stdout, Write}; +use std::io::{self, Stdout, Write}; use std::sync::mpsc; use std::sync::mpsc::{Sender, Receiver}; @@ -158,6 +159,12 @@ impl MultiBar { p } + pub fn create_log_target(&mut self) -> LogTarget { + LogTarget{ + buf: Vec::new(), + chan: self.chan.0.clone(), + } + } /// listen start listen to all bars changes. /// @@ -194,6 +201,8 @@ impl MultiBar { let mut first = true; let mut nbars = self.nbars; while nbars > 0 { + let mut log_line = None; + // receive message let msg = self.chan.1.recv().unwrap(); match msg { @@ -201,14 +210,20 @@ impl MultiBar { self.lines[level] = line; }, WriteMsg::ProgressClear{level,line} => { - self.lines[level] = tty::clear_current_line() + &line; + self.lines[level] = tty::clear_until_newline() + &line; nbars -= 1; }, WriteMsg::ProgressFinish{level,line} => { - // writing lines below progress not supported; - // replace progress instead - self.lines[level] = tty::clear_current_line() + &line; + // writing lines below progress not supported; treat + // as log message + let _ = level; nbars -= 1; + if line.is_empty() { continue; } + log_line = Some(line); + }, + WriteMsg::Log{line} => { + if line.is_empty() { continue; } + log_line = Some(line); }, } @@ -219,6 +234,12 @@ impl MultiBar { } else { first = false; } + if let Some(line) = log_line { + out += "\r"; + out += &tty::clear_after_cursor(); + out += &line; + out += "\n"; + } for l in self.lines.iter() { out += "\r"; out += &l; @@ -234,6 +255,43 @@ pub struct Pipe { chan: Sender, } +pub struct LogTarget { + buf: Vec, + chan: Sender, +} + +impl Write for LogTarget { + fn write(&mut self, buf: &[u8]) -> io::Result { + use std::mem::replace; + + self.buf.extend_from_slice(buf); + // find last newline and flush the part before it + for pos in (0..self.buf.len()).rev() { + if self.buf[pos] == b'\n' { + let rem = self.buf.split_off(pos+1); + let msg = replace(&mut self.buf, rem); + self.chan.send(WriteMsg::Log{ + line: from_utf8(&msg[..pos]).unwrap().to_owned(), + }) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + break; + } + } + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + use std::mem::replace; + + let msg = replace(&mut self.buf, Vec::new()); + self.chan.send(WriteMsg::Log{ + line: from_utf8(&msg).unwrap().to_owned(), + }) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + Ok(()) + } +} + impl ::private::SealedProgressReceiver for Pipe { fn update_progress(&mut self, line: &str) { self.chan.send(WriteMsg::ProgressUpdate{ @@ -278,4 +336,7 @@ enum WriteMsg { level: usize, line: String, }, + Log { + line: String, + }, } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 341ce377..2c56af91 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -47,8 +47,12 @@ pub fn move_cursor_up(n: usize) -> String { format!("{}", termion::cursor::Up(n as u16)) } -pub fn clear_current_line() -> String { - format!("{}", termion::clear::CurrentLine) +pub fn clear_until_newline() -> String { + format!("{}", termion::clear::UntilNewline) +} + +pub fn clear_after_cursor() -> String { + format!("{}", termion::clear::AfterCursor) } #[cfg(not(target_os = "redox"))] From cb8030c3dd9ce221543bb0abecb265da94941345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Wed, 2 Aug 2017 13:08:00 +0200 Subject: [PATCH 12/13] use logging in multi example --- examples/multi.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/multi.rs b/examples/multi.rs index c6969b28..09cd88c0 100644 --- a/examples/multi.rs +++ b/examples/multi.rs @@ -4,15 +4,18 @@ use rand::Rng; use pbr::MultiBar; use std::thread; use std::time::Duration; +use std::io::Write; fn main() { let mut mb = MultiBar::new(); + mb.println("---"); mb.println("Your Application Header:"); mb.println(""); for i in 1..6 { let count = 100 * i; let mut pb = mb.create_bar(count); + let mut logger = mb.create_log_target(); pb.tick_format("▏▎▍▌▋▊▉██▉▊▋▌▍▎▏"); pb.show_message = true; thread::spawn(move || { @@ -34,6 +37,7 @@ fn main() { thread::sleep(Duration::from_millis(100)); pb.tick(); } + writeln!(logger, "debug: Pull {} complete", i).unwrap(); pb.finish_print(&format!("{}: Pull complete", rand_string())); }); } @@ -46,12 +50,14 @@ fn main() { for i in 1..4 { let count = 100 * i; let mut pb = mb.create_bar(count); + let mut logger = mb.create_log_target(); thread::spawn(move || { for _ in 0..count { pb.inc(); let n = rand::thread_rng().gen_range(0, 100); thread::sleep(Duration::from_millis(n)); } + writeln!(logger, "debug: sleep {} finished", i).unwrap(); pb.finish(); }); } From 991dec3ce218e6aa264e5c2daac1ea8cb0293772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Wed, 2 Aug 2017 21:56:59 +0200 Subject: [PATCH 13/13] remove Write impl for ProgressBar --- src/pb.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/pb.rs b/src/pb.rs index 57f0eaea..e3f48765 100644 --- a/src/pb.rs +++ b/src/pb.rs @@ -1,4 +1,3 @@ -use std::io::{self, Write}; use std::iter::repeat; use std::time::Duration; use time::{self, SteadyTime}; @@ -508,18 +507,6 @@ impl ProgressBar { } } -// Implement io::Writer -impl Write for ProgressBar { - fn write(&mut self, buf: &[u8]) -> io::Result { - let n = buf.len(); - self.add(n as u64); - Ok(n) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - fn time_to_std(d: time::Duration) -> Duration { assert!(d > time::Duration::zero());