log_update/
lib.rs

1extern crate ansi_escapes;
2
3use std::io::Error;
4use std::io::Write;
5
6/// Main struct that holds the state for one Write stream
7pub struct LogUpdate<W: Write> {
8    stream: W,
9    previous_line_count: u16,
10    cursor_is_hidden: bool,
11}
12
13impl<W: Write> LogUpdate<W> {
14    /// Create a new LogUpdate instance.
15    pub fn new(mut stream: W) -> Result<Self, Error> {
16        try!(write!(stream, "{}", ansi_escapes::CursorHide));
17        try!(stream.flush());
18
19        Ok(LogUpdate { stream: stream, previous_line_count: 0, cursor_is_hidden: true })
20    }
21
22    /// Update the log to the provided text.
23    pub fn render(&mut self, text: &str) -> Result<(), Error> {
24        try!(write!(self.stream, "{}{}\n", ansi_escapes::EraseLines(self.previous_line_count), text));
25        try!(self.stream.flush());
26
27        self.previous_line_count = text.chars().filter(|x| *x == '\n').count() as u16 + 2;
28
29        Ok(())
30    }
31
32    /// Clear the logged output.
33    pub fn clear(&mut self) -> Result<(), Error> {
34        try!(write!(self.stream, "{}", ansi_escapes::EraseLines(self.previous_line_count)));
35        try!(self.stream.flush());
36
37        self.previous_line_count = 0;
38
39        Ok(())
40    }
41
42    /// Persist the logged output.
43    /// Useful if you want to start a new log session below the current one.
44    pub fn done(&mut self) -> Result<(), Error> {
45        if self.cursor_is_hidden {
46            try!(write!(self.stream, "{}", ansi_escapes::CursorShow));
47            try!(self.stream.flush());
48        }
49
50        self.previous_line_count = 0;
51        self.cursor_is_hidden = false;
52
53        Ok(())
54    }
55}
56
57impl<W: Write> Drop for LogUpdate<W> {
58    fn drop(&mut self) {
59        if self.cursor_is_hidden {
60            write!(self.stream, "{}", ansi_escapes::CursorShow).unwrap();
61            self.stream.flush().unwrap();
62        }
63    }
64}
65
66#[cfg(test)]
67extern crate tempfile;
68
69#[cfg(test)]
70mod tests {
71    use tempfile::tempfile;
72    use std::fs::File;
73    use std::io::{Read, Seek, SeekFrom};
74
75    use super::LogUpdate;
76
77    #[test]
78    fn it_handles_most_common_use_case() {
79        let mut file: File = tempfile().unwrap();
80        let mut log_update = LogUpdate::new(file.try_clone().unwrap()).unwrap();
81
82        log_update.render("test1").unwrap();
83        log_update.render("test2").unwrap();
84
85        log_update.done().unwrap();
86
87        file.seek(SeekFrom::Start(0)).unwrap();
88
89        let mut buf = String::new();
90        file.read_to_string(&mut buf).unwrap();
91
92        assert_eq!(concat!("\x1B[?25l", "test1\n", "\x1B[1000D\x1B[K\x1B[1A\x1B[1000D\x1B[K", "test2\n", "\x1B[?25h"), buf);
93    }
94
95    #[test]
96    fn it_restores_cursor_when_dropped() {
97        let mut file: File = tempfile().unwrap();
98        let mut log_update = LogUpdate::new(file.try_clone().unwrap()).unwrap();
99
100        log_update.render("test").unwrap();
101
102        drop(log_update);
103
104        file.seek(SeekFrom::Start(0)).unwrap();
105
106        let mut buf = String::new();
107        file.read_to_string(&mut buf).unwrap();
108
109        assert_eq!(concat!("\x1B[?25l", "test\n", "\x1B[?25h"), buf);
110    }
111}