shell/
lib.rs

1// Lifted from https://github.com/rust-lang/cargo/blob/master/src/cargo/core/shell.rs
2// under MIT license https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT
3//
4// Modified by: Ignacio Corderi
5
6#![crate_name = "shell"]
7
8extern crate term;
9extern crate libc;
10
11use std::fmt;
12use std::fmt::Display;
13use std::io::prelude::*;
14use std::io;
15use std::error::Error;
16
17use term::Attr;
18use term::color::{Color, BLACK, RED, GREEN, YELLOW, BRIGHT_YELLOW, BRIGHT_RED};
19use term::{Terminal, TerminfoTerminal, color};
20
21use self::AdequateTerminal::{NoColor, Colored};
22
23static GREY: u16 = 8;
24
25#[derive(Clone, Copy)]
26pub struct ShellConfig {
27    pub color: bool,
28    pub verbose: bool,
29    pub tty: bool
30}
31
32enum AdequateTerminal {
33    NoColor(Box<Write + Send>),
34    Colored(Box<Terminal<Output=Box<Write + Send>> + Send>)
35}
36
37pub struct Shell {
38    terminal: AdequateTerminal,
39    config: ShellConfig,
40}
41
42pub struct MultiShell {
43    out: Shell,
44    err: Shell,
45    verbose: bool
46}
47
48#[cfg(unix)]
49fn isatty(fd: libc::c_int) -> bool {
50    unsafe { libc::isatty(fd) != 0 }
51}
52#[cfg(windows)]
53fn isatty(fd: libc::c_int) -> bool {
54    extern crate kernel32;
55    extern crate winapi;
56    unsafe {
57        let handle = kernel32::GetStdHandle(
58            if fd == libc::STDOUT_FILENO {
59                winapi::winbase::STD_OUTPUT_HANDLE
60            } else {
61                winapi::winbase::STD_ERROR_HANDLE
62            });
63        let mut out = 0;
64        kernel32::GetConsoleMode(handle, &mut out) != 0
65    }
66}
67
68impl MultiShell {
69
70    pub fn new_stdio(verbose: bool) -> MultiShell {
71        let tty = isatty(libc::STDERR_FILENO);
72        let stderr = Box::new(io::stderr()) as Box<Write + Send>;
73
74        let config = ShellConfig { color: true, verbose: verbose, tty: tty };
75        let err = Shell::create(stderr, config);
76
77        let tty = isatty(libc::STDOUT_FILENO);
78        let stdout = Box::new(io::stdout()) as Box<Write + Send>;
79
80        let config = ShellConfig { color: true, verbose: verbose, tty: tty };
81        let out = Shell::create(stdout, config);
82
83        MultiShell::new(out, err, verbose)
84    }
85
86    pub fn new(out: Shell, err: Shell, verbose: bool) -> MultiShell {
87        MultiShell { out: out, err: err, verbose: verbose }
88    }
89
90    pub fn out(&mut self) -> &mut Shell {
91        &mut self.out
92    }
93
94    pub fn err(&mut self) -> &mut Shell {
95        &mut self.err
96    }
97
98    pub fn say<T: ToString>(&mut self, message: T, color: Color) -> io::Result<()> {
99        self.out().say(message, color)
100    }
101
102    pub fn status<T, U>(&mut self, status: T, message: U) -> io::Result<()>
103        where T: fmt::Display, U: fmt::Display
104    {
105        self.out().say_status(status, message, GREEN)
106    }
107
108    pub fn verbose<F>(&mut self, mut callback: F) -> io::Result<()>
109        where F: FnMut(&mut MultiShell) -> io::Result<()>
110    {
111        if self.verbose { return callback(self) }
112        Ok(())
113    }
114
115    pub fn concise<F>(&mut self, mut callback: F) -> io::Result<()>
116        where F: FnMut(&mut MultiShell) -> io::Result<()>
117    {
118        if !self.verbose { return callback(self) }
119        Ok(())
120    }
121
122    pub fn error<T: ToString>(&mut self, message: T) -> io::Result<()> {
123        self.err().say(message, RED)
124    }
125
126    pub fn warn<T: ToString>(&mut self, message: T) -> io::Result<()> {
127        self.err().say(message, YELLOW)
128    }
129
130    pub fn set_verbose(&mut self, verbose: bool) {
131        self.verbose = verbose;
132    }
133
134    pub fn get_verbose(&self) -> bool {
135        self.verbose
136    }
137
138    pub fn tag<T: Display, U: Display>(&mut self, tag: T, message: U) -> io::Result<()>{
139        self.out().say_status(tag, message, BLACK)
140    }
141
142    pub fn header<T: Display>(&mut self, message: T) -> io::Result<()> {
143        self.out().say_attr(message, BLACK, Attr::Underline(true), true)
144    }
145
146    pub fn comment<T: Display>(&mut self, message: T) -> io::Result<()> {
147        self.out().say_attr(message, GREY, Attr::Dim, true)
148    }
149
150    pub fn tag_color<T: Display, U: Display>(&mut self, tag: T, message: U, color: Color) -> io::Result<()>{
151        self.out().say_status(tag, message, color)
152    }
153
154    pub fn error_full(&mut self, e: &Error, mut show_cause: bool) -> io::Result<()>{
155        try!(self.err().say_write(      "error: ", BRIGHT_RED));
156        try!(self.err().say_attr(format!("{}", e.description()), BLACK, Attr::Bold, true));
157
158        let mut e = e;
159        while show_cause {
160            if e.cause().is_some() {
161                e = e.cause().unwrap();
162                try!(self.err().say_write(      "caused by: ", BRIGHT_YELLOW));
163                try!(self.err().say(format!("{}", e.description()), BLACK));
164            } else { show_cause = false; }
165        }
166
167        Ok(())
168    }
169}
170
171impl Shell {
172    pub fn create(out: Box<Write + Send>, config: ShellConfig) -> Shell {
173        if config.tty && config.color {
174            let term = TerminfoTerminal::new(out);
175            term.map(|t| Shell {
176                terminal: Colored(Box::new(t)),
177                config: config
178            }).unwrap_or_else(|| {
179                Shell { terminal: NoColor(Box::new(io::stderr())), config: config }
180            })
181        } else {
182            Shell { terminal: NoColor(out), config: config }
183        }
184    }
185
186    pub fn verbose<F>(&mut self, mut callback: F) -> io::Result<()>
187        where F: FnMut(&mut Shell) -> io::Result<()>
188    {
189        if self.config.verbose { return callback(self) }
190        Ok(())
191    }
192
193    pub fn concise<F>(&mut self, mut callback: F) -> io::Result<()>
194        where F: FnMut(&mut Shell) -> io::Result<()>
195    {
196        if !self.config.verbose { return callback(self) }
197        Ok(())
198    }
199
200    pub fn say_write<T: Display>(&mut self, message: T, color: Color) -> io::Result<()> {
201        try!(self.reset());
202        if color != BLACK { try!(self.fg(color)); }
203        try!(write!(self, "{}", message));
204        try!(self.reset());
205        try!(self.flush());
206        Ok(())
207    }
208
209    pub fn say_attr<T: Display>(&mut self, message: T, color: Color, attr: Attr, new_line: bool) -> io::Result<()> {
210        try!(self.reset());
211        try!(self.attr(attr));
212        if color != BLACK { try!(self.fg(color)); }
213        if new_line {
214            try!(write!(self, "{}\n", message));
215        } else {
216            try!(write!(self, "{}", message));
217        }
218        try!(self.reset());
219        try!(self.flush());
220        Ok(())
221    }
222
223    pub fn say<T: ToString>(&mut self, message: T, color: Color) -> io::Result<()> {
224        try!(self.reset());
225        if color != BLACK { try!(self.fg(color)); }
226        try!(write!(self, "{}\n", message.to_string()));
227        try!(self.reset());
228        try!(self.flush());
229        Ok(())
230    }
231
232    pub fn say_status<T, U>(&mut self, status: T, message: U, color: Color)
233                            -> io::Result<()>
234        where T: fmt::Display, U: fmt::Display
235    {
236        try!(self.reset());
237        if color != BLACK { try!(self.fg(color)); }
238        if self.supports_attr(Attr::Bold) { try!(self.attr(Attr::Bold)); }
239        try!(write!(self, "{:>12}", status.to_string()));
240        try!(self.reset());
241        try!(write!(self, " {}\n", message));
242        try!(self.flush());
243        Ok(())
244    }
245
246    fn fg(&mut self, color: color::Color) -> term::Result<()> {
247        match self.terminal {
248            Colored(ref mut c) => c.fg(color),
249            NoColor(_) => Ok(())
250        }
251    }
252
253    fn attr(&mut self, attr: Attr) -> term::Result<()> {
254        match self.terminal {
255            Colored(ref mut c) => c.attr(attr),
256            NoColor(_) => Ok(())
257        }
258    }
259
260    fn supports_attr(&self, attr: Attr) -> bool {
261        match self.terminal {
262            Colored(ref c) => c.supports_attr(attr),
263            NoColor(_) => false
264        }
265    }
266
267    fn reset(&mut self) -> term::Result<()> {
268        match self.terminal {
269            Colored(ref mut c) => c.reset(),
270            NoColor(_) => Ok(())
271        }
272    }
273}
274
275impl Write for Shell {
276    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
277        match self.terminal {
278            Colored(ref mut c) => c.write(buf),
279            NoColor(ref mut n) => n.write(buf)
280        }
281    }
282
283    fn flush(&mut self) -> io::Result<()> {
284        match self.terminal {
285            Colored(ref mut c) => c.flush(),
286            NoColor(ref mut n) => n.flush()
287        }
288    }
289}
290
291#[cfg(test)]
292mod test {
293
294    use MultiShell;
295
296    #[test]
297    fn create_multishell() {
298       MultiShell::new_stdio(false);
299    }
300
301    #[test]
302    fn create_multishell_verbose() {
303       MultiShell::new_stdio(true);
304    }
305
306}