Skip to content

Commit f923b93

Browse files
committed
term: add docs and windows support
Closes #2807
1 parent e30198d commit f923b93

File tree

4 files changed

+440
-180
lines changed

4 files changed

+440
-180
lines changed

mk/crates.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ DEPS_arena := std collections
7373
DEPS_graphviz := std
7474
DEPS_glob := std
7575
DEPS_serialize := std collections log
76-
DEPS_term := std collections
76+
DEPS_term := std collections log
7777
DEPS_semver := std
7878
DEPS_uuid := std serialize rand
7979
DEPS_sync := std

src/libterm/lib.rs

Lines changed: 107 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,32 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

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
1237
1338
#![crate_id = "term#0.11.0-pre"]
1439
#![comment = "Simple ANSI color library"]
@@ -19,22 +44,76 @@
1944
html_favicon_url = "http://www.rust-lang.org/favicon.ico",
2045
html_root_url = "http://static.rust-lang.org/doc/master")]
2146

22-
#![feature(macro_rules)]
47+
#![feature(macro_rules, phase)]
2348

2449
#![deny(missing_doc)]
2550

51+
#[phase(syntax, link)] extern crate log;
2652
extern crate collections;
2753

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;
3459

3560
pub mod terminfo;
3661

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+
38117

39118
/// Terminal color definitions
40119
pub mod color {
@@ -91,200 +170,49 @@ pub mod attr {
91170
}
92171
}
93172

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>;
150179

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-
}
160180
/// Sets the foreground color to the given color.
161181
///
162182
/// If the color is a bright color, but the terminal only supports 8 colors,
163183
/// the corresponding normal color will be used instead.
164184
///
165185
/// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
166186
/// 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+
183189
/// Sets the background color to the given color.
184190
///
185191
/// If the color is a bright color, but the terminal only supports 8 colors,
186192
/// the corresponding normal color will be used instead.
187193
///
188194
/// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
189195
/// 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>;
206197

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>;
230202

231203
/// 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;
243205

244206
/// Resets all terminal attributes and color to the default.
245207
/// 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<()>;
271209

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;
274212

275213
/// 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;
277215

278216
/// 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;
290218
}

0 commit comments

Comments
 (0)