1#![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}