1use libc::isatty;
6use std::fmt;
7use std::io;
8use std::io::prelude::*;
9use term::{self, Attr, TerminfoTerminal};
10use term::Terminal as RawTerminal;
11use term::color::{Color, BLACK};
12
13#[derive(Clone, Copy, PartialEq)]
15pub enum ColorConfig {
16 Auto,
18
19 Always,
21
22 Never,
24}
25
26impl fmt::Display for ColorConfig {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 match *self {
29 ColorConfig::Auto => "auto",
30 ColorConfig::Always => "always",
31 ColorConfig::Never => "never",
32 }.fmt(f)
33 }
34}
35
36#[derive(Clone, Copy)]
38pub struct ShellConfig {
39 pub color_config: ColorConfig,
41
42 pub tty: bool,
44}
45
46impl Default for ShellConfig {
47 fn default() -> Self {
48 Self {
49 color_config: ColorConfig::Auto,
50 tty: is_tty(),
51 }
52 }
53}
54
55fn is_tty() -> bool {
57 #[allow(unsafe_code)]
58 unsafe {
59 isatty(0) == 1
60 }
61}
62
63enum Terminal {
64 NoColor(Box<Write + Send>),
65 Colored(Box<RawTerminal<Output = Box<Write + Send>> + Send>),
66}
67
68pub struct Shell {
70 terminal: Terminal,
71 config: ShellConfig,
72}
73
74impl Shell {
75 pub fn create<T: FnMut() -> Box<Write + Send>>(mut out_fn: T, config: ShellConfig) -> Shell {
77 let terminal = Shell::get_term(out_fn()).unwrap_or_else(|_| Terminal::NoColor(out_fn()));
78 Shell { terminal, config }
79 }
80
81 fn get_term(out: Box<Write + Send>) -> term::Result<Terminal> {
83 Ok(Shell::get_terminfo_term(out))
84 }
85
86 fn get_terminfo_term(out: Box<Write + Send>) -> Terminal {
88 match ::term::terminfo::TermInfo::from_env() {
89 Ok(ti) => {
90 let term = TerminfoTerminal::new_with_terminfo(out, ti);
91 if term.supports_color() {
92 Terminal::Colored(Box::new(term))
93 } else {
94 Terminal::NoColor(term.into_inner())
95 }
96 }
97 Err(_) => Terminal::NoColor(out),
98 }
99 }
100
101 pub fn status<T, U>(
103 &mut self,
104 color: Color,
105 status: T,
106 message: U,
107 justified: bool,
108 ) -> term::Result<()>
109 where
110 T: fmt::Display,
111 U: fmt::Display,
112 {
113 self.reset()?;
114
115 if color != BLACK {
116 self.fg(color)?;
117 }
118
119 if self.supports_attr(Attr::Bold) {
120 self.attr(Attr::Bold)?;
121 }
122
123 if justified {
124 write!(self, "{:>12}", status.to_string())?;
125 } else {
126 write!(self, "{}", status)?;
127 }
128
129 self.reset()?;
130 write!(self, " {}\n", message)?;
131 self.flush()?;
132
133 Ok(())
134 }
135
136 fn fg(&mut self, color: Color) -> term::Result<bool> {
137 let colored = self.colored();
138
139 match self.terminal {
140 Terminal::Colored(ref mut c) if colored => c.fg(color)?,
141 _ => return Ok(false),
142 }
143
144 Ok(true)
145 }
146
147 fn attr(&mut self, attr: Attr) -> term::Result<bool> {
148 let colored = self.colored();
149
150 match self.terminal {
151 Terminal::Colored(ref mut c) if colored => c.attr(attr)?,
152 _ => return Ok(false),
153 }
154
155 Ok(true)
156 }
157
158 fn supports_attr(&self, attr: Attr) -> bool {
159 let colored = self.colored();
160
161 match self.terminal {
162 Terminal::Colored(ref c) if colored => c.supports_attr(attr),
163 _ => false,
164 }
165 }
166
167 fn reset(&mut self) -> term::Result<()> {
168 let colored = self.colored();
169
170 match self.terminal {
171 Terminal::Colored(ref mut c) if colored => c.reset()?,
172 _ => (),
173 }
174
175 Ok(())
176 }
177
178 fn colored(&self) -> bool {
179 self.config.tty && ColorConfig::Auto == self.config.color_config
180 || ColorConfig::Always == self.config.color_config
181 }
182}
183
184impl Default for Shell {
185 fn default() -> Self {
186 Shell::create(|| Box::new(io::stdout()), ShellConfig::default())
187 }
188}
189
190impl Write for Shell {
191 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
192 match self.terminal {
193 Terminal::Colored(ref mut c) => c.write(buf),
194 Terminal::NoColor(ref mut n) => n.write(buf),
195 }
196 }
197
198 fn flush(&mut self) -> io::Result<()> {
199 match self.terminal {
200 Terminal::Colored(ref mut c) => c.flush(),
201 Terminal::NoColor(ref mut n) => n.flush(),
202 }
203 }
204}