|
8 | 8 | // option. This file may not be copied, modified, or distributed
|
9 | 9 | // except according to those terms.
|
10 | 10 |
|
11 |
| -//! Simple ANSI color library |
| 11 | +//! Terminal formatting library. |
| 12 | +//! |
| 13 | +//! This crate provides the `Terminal` trait, which abstracts over an [ANSI |
| 14 | +//! Termina][ansi] to provide color printing, among other things. There are two implementations, |
| 15 | +//! the `TerminfoTerminal`, which uses control characters from a |
| 16 | +//! [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console |
| 17 | +//! API][win]. |
| 18 | +//! |
| 19 | +//! ## Example |
| 20 | +//! |
| 21 | +//! ```rust |
| 22 | +//! extern crate term; |
| 23 | +//! |
| 24 | +//! fn main() { |
| 25 | +//! let mut t = term::stdout(); |
| 26 | +//! t.fg(term::color::GREEN); |
| 27 | +//! println!("hello, "); |
| 28 | +//! t.fg(term::color::RED); |
| 29 | +//! println("world!"); |
| 30 | +//! t.reset(); |
| 31 | +//! } |
| 32 | +//! ``` |
| 33 | +//! |
| 34 | +//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code |
| 35 | +//! [win]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682010%28v=vs.85%29.aspx |
| 36 | +//! [ti]: https://en.wikipedia.org/wiki/Terminfo |
12 | 37 |
|
13 | 38 | #![crate_id = "term#0.11.0-pre"]
|
14 | 39 | #![comment = "Simple ANSI color library"]
|
|
19 | 44 | html_favicon_url = "http://www.rust-lang.org/favicon.ico",
|
20 | 45 | html_root_url = "http://static.rust-lang.org/doc/master")]
|
21 | 46 |
|
22 |
| -#![feature(macro_rules)] |
| 47 | +#![feature(macro_rules, phase)] |
23 | 48 |
|
24 | 49 | #![deny(missing_doc)]
|
25 | 50 |
|
| 51 | +#[phase(syntax, link)] extern crate log; |
26 | 52 | extern crate collections;
|
27 | 53 |
|
28 |
| -use std::io; |
29 |
| -use std::os; |
30 |
| -use terminfo::TermInfo; |
31 |
| -use terminfo::searcher::open; |
32 |
| -use terminfo::parser::compiled::{parse, msys_terminfo}; |
33 |
| -use terminfo::parm::{expand, Number, Variables}; |
| 54 | +pub use terminfo::TerminfoTerminal; |
| 55 | +#[cfg(windows)] |
| 56 | +pub use win::WinConsole; |
| 57 | + |
| 58 | +use std::io::IoResult; |
34 | 59 |
|
35 | 60 | pub mod terminfo;
|
36 | 61 |
|
37 |
| -// FIXME (#2807): Windows support. |
| 62 | +#[cfg(windows)] |
| 63 | +mod win; |
| 64 | + |
| 65 | +#[cfg(not(windows))] |
| 66 | +/// Return a Terminal wrapping stdout, or None if a terminal couldn't be |
| 67 | +/// opened. |
| 68 | +pub fn stdout() -> Option<~Terminal<~Writer:Send>:Send> { |
| 69 | + let ti: Option<TerminfoTerminal<~Writer:Send>> |
| 70 | + = Terminal::new(~std::io::stdout() as ~Writer:Send); |
| 71 | + ti.map(|t| ~t as ~Terminal<~Writer:Send>:Send) |
| 72 | +} |
| 73 | + |
| 74 | +#[cfg(windows)] |
| 75 | +/// Return a Terminal wrapping stdout, or None if a terminal couldn't be |
| 76 | +/// opened. |
| 77 | +pub fn stdout() -> Option<~Terminal<~Writer:Send>:Send> { |
| 78 | + let ti: Option<TerminfoTerminal<~Writer:Send>> |
| 79 | + = Terminal::new(~std::io::stdout() as ~Writer:Send); |
| 80 | + |
| 81 | + match ti { |
| 82 | + Some(t) => Some(~t as ~Terminal<~Writer:Send>:Send), |
| 83 | + None => { |
| 84 | + let wc: Option<WinConsole<~Writer:Send>> |
| 85 | + = Terminal::new(~std::io::stdout() as ~Writer:Send); |
| 86 | + wc.map(|w| ~w as ~Terminal<~Writer:Send>:Send) |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +#[cfg(not(windows))] |
| 92 | +/// Return a Terminal wrapping stderr, or None if a terminal couldn't be |
| 93 | +/// opened. |
| 94 | +pub fn stderr() -> Option<~Terminal<~Writer:Send>:Send> { |
| 95 | + let ti: Option<TerminfoTerminal<~Writer:Send>> |
| 96 | + = Terminal::new(~std::io::stderr() as ~Writer:Send); |
| 97 | + ti.map(|t| ~t as ~Terminal<~Writer:Send>:Send) |
| 98 | +} |
| 99 | + |
| 100 | +#[cfg(windows)] |
| 101 | +/// Return a Terminal wrapping stderr, or None if a terminal couldn't be |
| 102 | +/// opened. |
| 103 | +pub fn stderr() -> Option<~Terminal<~Writer:Send>:Send> { |
| 104 | + let ti: Option<TerminfoTerminal<~Writer:Send>> |
| 105 | + = Terminal::new(~std::io::stderr() as ~Writer:Send); |
| 106 | + |
| 107 | + match ti { |
| 108 | + Some(t) => Some(~t as ~Terminal<~Writer:Send>:Send), |
| 109 | + None => { |
| 110 | + let wc: Option<WinConsole<~Writer:Send>> |
| 111 | + = Terminal::new(~std::io::stderr() as ~Writer:Send); |
| 112 | + wc.map(|w| ~w as ~Terminal<~Writer:Send>:Send) |
| 113 | + } |
| 114 | + } |
| 115 | +} |
| 116 | + |
38 | 117 |
|
39 | 118 | /// Terminal color definitions
|
40 | 119 | pub mod color {
|
@@ -91,200 +170,49 @@ pub mod attr {
|
91 | 170 | }
|
92 | 171 | }
|
93 | 172 |
|
94 |
| -fn cap_for_attr(attr: attr::Attr) -> &'static str { |
95 |
| - match attr { |
96 |
| - attr::Bold => "bold", |
97 |
| - attr::Dim => "dim", |
98 |
| - attr::Italic(true) => "sitm", |
99 |
| - attr::Italic(false) => "ritm", |
100 |
| - attr::Underline(true) => "smul", |
101 |
| - attr::Underline(false) => "rmul", |
102 |
| - attr::Blink => "blink", |
103 |
| - attr::Standout(true) => "smso", |
104 |
| - attr::Standout(false) => "rmso", |
105 |
| - attr::Reverse => "rev", |
106 |
| - attr::Secure => "invis", |
107 |
| - attr::ForegroundColor(_) => "setaf", |
108 |
| - attr::BackgroundColor(_) => "setab" |
109 |
| - } |
110 |
| -} |
111 |
| - |
112 |
| -/// A Terminal that knows how many colors it supports, with a reference to its |
113 |
| -/// parsed TermInfo database record. |
114 |
| -pub struct Terminal<T> { |
115 |
| - num_colors: u16, |
116 |
| - out: T, |
117 |
| - ti: Box<TermInfo>, |
118 |
| -} |
119 |
| - |
120 |
| -impl<T: Writer> Terminal<T> { |
121 |
| - /// Returns a wrapped output stream (`Terminal<T>`) as a `Result`. |
122 |
| - /// |
123 |
| - /// Returns `Err()` if the TERM environment variable is undefined. |
124 |
| - /// TERM should be set to something like `xterm-color` or `screen-256color`. |
125 |
| - /// |
126 |
| - /// Returns `Err()` on failure to open the terminfo database correctly. |
127 |
| - /// Also, in the event that the individual terminfo database entry can not |
128 |
| - /// be parsed. |
129 |
| - pub fn new(out: T) -> Result<Terminal<T>, StrBuf> { |
130 |
| - let term = match os::getenv("TERM") { |
131 |
| - Some(t) => t, |
132 |
| - None => { |
133 |
| - return Err("TERM environment variable undefined".to_strbuf()) |
134 |
| - } |
135 |
| - }; |
136 |
| - |
137 |
| - let mut file = match open(term) { |
138 |
| - Ok(file) => file, |
139 |
| - Err(err) => { |
140 |
| - if "cygwin" == term { // msys terminal |
141 |
| - return Ok(Terminal { |
142 |
| - out: out, |
143 |
| - ti: msys_terminfo(), |
144 |
| - num_colors: 8 |
145 |
| - }); |
146 |
| - } |
147 |
| - return Err(err); |
148 |
| - } |
149 |
| - }; |
| 173 | +/// A terminal with similar capabilities to an ANSI Terminal |
| 174 | +/// (foreground/background colors etc). |
| 175 | +pub trait Terminal<T: Writer>: Writer { |
| 176 | + /// Returns `None` whenever the terminal cannot be created for some |
| 177 | + /// reason. |
| 178 | + fn new(out: T) -> Option<Self>; |
150 | 179 |
|
151 |
| - let inf = try!(parse(&mut file, false)); |
152 |
| - |
153 |
| - let nc = if inf.strings.find_equiv(&("setaf")).is_some() |
154 |
| - && inf.strings.find_equiv(&("setab")).is_some() { |
155 |
| - inf.numbers.find_equiv(&("colors")).map_or(0, |&n| n) |
156 |
| - } else { 0 }; |
157 |
| - |
158 |
| - return Ok(Terminal {out: out, ti: inf, num_colors: nc}); |
159 |
| - } |
160 | 180 | /// Sets the foreground color to the given color.
|
161 | 181 | ///
|
162 | 182 | /// If the color is a bright color, but the terminal only supports 8 colors,
|
163 | 183 | /// the corresponding normal color will be used instead.
|
164 | 184 | ///
|
165 | 185 | /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
|
166 | 186 | /// if there was an I/O error.
|
167 |
| - pub fn fg(&mut self, color: color::Color) -> io::IoResult<bool> { |
168 |
| - let color = self.dim_if_necessary(color); |
169 |
| - if self.num_colors > color { |
170 |
| - let s = expand(self.ti |
171 |
| - .strings |
172 |
| - .find_equiv(&("setaf")) |
173 |
| - .unwrap() |
174 |
| - .as_slice(), |
175 |
| - [Number(color as int)], &mut Variables::new()); |
176 |
| - if s.is_ok() { |
177 |
| - try!(self.out.write(s.unwrap().as_slice())); |
178 |
| - return Ok(true) |
179 |
| - } |
180 |
| - } |
181 |
| - Ok(false) |
182 |
| - } |
| 187 | + fn fg(&mut self, color: color::Color) -> IoResult<bool>; |
| 188 | + |
183 | 189 | /// Sets the background color to the given color.
|
184 | 190 | ///
|
185 | 191 | /// If the color is a bright color, but the terminal only supports 8 colors,
|
186 | 192 | /// the corresponding normal color will be used instead.
|
187 | 193 | ///
|
188 | 194 | /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
|
189 | 195 | /// if there was an I/O error.
|
190 |
| - pub fn bg(&mut self, color: color::Color) -> io::IoResult<bool> { |
191 |
| - let color = self.dim_if_necessary(color); |
192 |
| - if self.num_colors > color { |
193 |
| - let s = expand(self.ti |
194 |
| - .strings |
195 |
| - .find_equiv(&("setab")) |
196 |
| - .unwrap() |
197 |
| - .as_slice(), |
198 |
| - [Number(color as int)], &mut Variables::new()); |
199 |
| - if s.is_ok() { |
200 |
| - try!(self.out.write(s.unwrap().as_slice())); |
201 |
| - return Ok(true) |
202 |
| - } |
203 |
| - } |
204 |
| - Ok(false) |
205 |
| - } |
| 196 | + fn bg(&mut self, color: color::Color) -> IoResult<bool>; |
206 | 197 |
|
207 |
| - /// Sets the given terminal attribute, if supported. |
208 |
| - /// Returns `Ok(true)` if the attribute was supported, `Ok(false)` otherwise, |
209 |
| - /// and `Err(e)` if there was an I/O error. |
210 |
| - pub fn attr(&mut self, attr: attr::Attr) -> io::IoResult<bool> { |
211 |
| - match attr { |
212 |
| - attr::ForegroundColor(c) => self.fg(c), |
213 |
| - attr::BackgroundColor(c) => self.bg(c), |
214 |
| - _ => { |
215 |
| - let cap = cap_for_attr(attr); |
216 |
| - let parm = self.ti.strings.find_equiv(&cap); |
217 |
| - if parm.is_some() { |
218 |
| - let s = expand(parm.unwrap().as_slice(), |
219 |
| - [], |
220 |
| - &mut Variables::new()); |
221 |
| - if s.is_ok() { |
222 |
| - try!(self.out.write(s.unwrap().as_slice())); |
223 |
| - return Ok(true) |
224 |
| - } |
225 |
| - } |
226 |
| - Ok(false) |
227 |
| - } |
228 |
| - } |
229 |
| - } |
| 198 | + /// Sets the given terminal attribute, if supported. Returns `Ok(true)` |
| 199 | + /// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if |
| 200 | + /// there was an I/O error. |
| 201 | + fn attr(&mut self, attr: attr::Attr) -> IoResult<bool>; |
230 | 202 |
|
231 | 203 | /// Returns whether the given terminal attribute is supported.
|
232 |
| - pub fn supports_attr(&self, attr: attr::Attr) -> bool { |
233 |
| - match attr { |
234 |
| - attr::ForegroundColor(_) | attr::BackgroundColor(_) => { |
235 |
| - self.num_colors > 0 |
236 |
| - } |
237 |
| - _ => { |
238 |
| - let cap = cap_for_attr(attr); |
239 |
| - self.ti.strings.find_equiv(&cap).is_some() |
240 |
| - } |
241 |
| - } |
242 |
| - } |
| 204 | + fn supports_attr(&self, attr: attr::Attr) -> bool; |
243 | 205 |
|
244 | 206 | /// Resets all terminal attributes and color to the default.
|
245 | 207 | /// Returns `Ok()`.
|
246 |
| - pub fn reset(&mut self) -> io::IoResult<()> { |
247 |
| - let mut cap = self.ti.strings.find_equiv(&("sgr0")); |
248 |
| - if cap.is_none() { |
249 |
| - // are there any terminals that have color/attrs and not sgr0? |
250 |
| - // Try falling back to sgr, then op |
251 |
| - cap = self.ti.strings.find_equiv(&("sgr")); |
252 |
| - if cap.is_none() { |
253 |
| - cap = self.ti.strings.find_equiv(&("op")); |
254 |
| - } |
255 |
| - } |
256 |
| - let s = cap.map_or(Err("can't find terminfo capability \ |
257 |
| - `sgr0`".to_strbuf()), |op| { |
258 |
| - expand(op.as_slice(), [], &mut Variables::new()) |
259 |
| - }); |
260 |
| - if s.is_ok() { |
261 |
| - return self.out.write(s.unwrap().as_slice()) |
262 |
| - } |
263 |
| - Ok(()) |
264 |
| - } |
265 |
| - |
266 |
| - fn dim_if_necessary(&self, color: color::Color) -> color::Color { |
267 |
| - if color >= self.num_colors && color >= 8 && color < 16 { |
268 |
| - color-8 |
269 |
| - } else { color } |
270 |
| - } |
| 208 | + fn reset(&mut self) -> IoResult<()>; |
271 | 209 |
|
272 |
| - /// Returns the contained stream |
273 |
| - pub fn unwrap(self) -> T { self.out } |
| 210 | + /// Returns the contained stream, destroying the `Terminal` |
| 211 | + fn unwrap(self) -> T; |
274 | 212 |
|
275 | 213 | /// Gets an immutable reference to the stream inside
|
276 |
| - pub fn get_ref<'a>(&'a self) -> &'a T { &self.out } |
| 214 | + fn get_ref<'a>(&'a self) -> &'a T; |
277 | 215 |
|
278 | 216 | /// Gets a mutable reference to the stream inside
|
279 |
| - pub fn get_mut<'a>(&'a mut self) -> &'a mut T { &mut self.out } |
280 |
| -} |
281 |
| - |
282 |
| -impl<T: Writer> Writer for Terminal<T> { |
283 |
| - fn write(&mut self, buf: &[u8]) -> io::IoResult<()> { |
284 |
| - self.out.write(buf) |
285 |
| - } |
286 |
| - |
287 |
| - fn flush(&mut self) -> io::IoResult<()> { |
288 |
| - self.out.flush() |
289 |
| - } |
| 217 | + fn get_mut<'a>(&'a mut self) -> &'a mut T; |
290 | 218 | }
|
0 commit comments