Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Logging and more #55

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
6 changes: 6 additions & 0 deletions examples/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {
Expand All @@ -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()));
});
}
Expand All @@ -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();
});
}
Expand Down
49 changes: 47 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,12 @@ macro_rules! printfl {
}}
}

#[macro_use]
extern crate time;
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<T, I>
Expand Down Expand Up @@ -169,3 +168,49 @@ impl<T, I> Iterator for PbIter<T, I>
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<T: Write> 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(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");
}

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<T: Write> ProgressReceiver for T {
}
154 changes: 124 additions & 30 deletions src/multi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use pb::ProgressBar;
use std::str::from_utf8;
use tty::move_cursor_up;
use std::io::{Stdout, Result, Write};
use tty;
use std::io::{self, Stdout, Write};
use std::sync::mpsc;
use std::sync::mpsc::{Sender, Receiver};

Expand All @@ -22,7 +22,7 @@ impl MultiBar<Stdout> {
///
/// # Examples
///
/// ```no_run
/// ```ignore
/// use std::thread;
/// use pbr::MultiBar;
///
Expand Down Expand Up @@ -91,7 +91,7 @@ impl<T: Write> MultiBar<T> {
///
/// # Examples
///
/// ```no_run
/// ```ignore
/// use pbr::MultiBar;
///
/// let mut mb = MultiBar::new();
Expand Down Expand Up @@ -127,7 +127,7 @@ impl<T: Write> MultiBar<T> {
///
/// # Examples
///
/// ```no_run
/// ```ignore
/// use pbr::MultiBar;
///
/// let mut mb = MultiBar::new();
Expand Down Expand Up @@ -159,6 +159,12 @@ impl<T: Write> MultiBar<T> {
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.
///
Expand All @@ -171,8 +177,11 @@ impl<T: Write> MultiBar<T> {
///
/// # Examples
///
/// ```no_run
/// use pbr::MultiBar;
/// ```
/// # extern crate pbr;
/// # use std::thread;
/// # fn main() {
/// use ::pbr::MultiBar;
///
/// let mut mb = MultiBar::new();
///
Expand All @@ -186,29 +195,55 @@ impl<T: Write> MultiBar<T> {
/// });
///
/// // ...
/// # }
/// ```
pub fn listen(&mut self) {
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();
if msg.done {
nbars -= 1;
continue;
match msg {
WriteMsg::ProgressUpdate{level,line} => {
self.lines[level] = line;
},
WriteMsg::ProgressClear{level,line} => {
self.lines[level] = tty::clear_until_newline() + &line;
nbars -= 1;
},
WriteMsg::ProgressFinish{level,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);
},
}
self.lines[msg.level] = msg.string;

// 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;
}
if let Some(line) = log_line {
out += "\r";
out += &tty::clear_after_cursor();
out += &line;
out += "\n";
}
for l in self.lines.iter() {
out.push_str(&format!("\r{}\n", l));
out += "\r";
out += &l;
out += "\n";
}
printfl!(self.handle, "{}", out);
}
Expand All @@ -220,29 +255,88 @@ pub struct Pipe {
chan: Sender<WriteMsg>,
}

impl Write for Pipe {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
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)
pub struct LogTarget {
buf: Vec<u8>,
chan: Sender<WriteMsg>,
}

impl Write for LogTarget {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
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) -> Result<()> {
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{
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 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,
},
Log {
line: String,
},
}
Loading