提交 372e82c9 编写于 作者: B bors

Auto merge of #29999 - SingingTree:libterm_unwrapping, r=alexcrichton

This removes a number of instances of unwrap and replaces them with
pattern matching.

This is in response to rust issue #29992.
......@@ -20,7 +20,7 @@
use std::{cmp, error, fmt};
use std::io::prelude::*;
use std::io;
use term::{self, WriterWrapper};
use term;
/// maximum number of lines we will print for each error; arbitrary.
const MAX_LINES: usize = 6;
......@@ -318,7 +318,7 @@ pub struct EmitterWriter {
}
enum Destination {
Terminal(Box<term::Terminal<WriterWrapper> + Send>),
Terminal(Box<term::StderrTerminal>),
Raw(Box<Write + Send>),
}
......@@ -365,7 +365,7 @@ pub fn new(dst: Box<Write + Send>,
fn print_maybe_styled(&mut self,
args: fmt::Arguments,
color: term::attr::Attr,
color: term::Attr,
print_newline_at_end: bool) -> io::Result<()> {
match self.dst {
Terminal(ref mut t) => {
......@@ -408,13 +408,13 @@ fn print_diagnostic(&mut self, topic: &str, lvl: Level,
try!(write!(&mut self.dst, "{} ", topic));
}
try!(print_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
try!(print_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}: ", lvl.to_string()));
try!(print_maybe_styled!(self, term::attr::Bold, "{}", msg));
try!(print_maybe_styled!(self, term::Attr::Bold, "{}", msg));
match code {
Some(code) => {
let style = term::attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
try!(print_maybe_styled!(self, style, " [{}]", code.clone()));
}
None => ()
......@@ -646,7 +646,7 @@ fn highlight_lines(&mut self,
s.pop();
}
try!(println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
try!(println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s));
}
}
......@@ -719,7 +719,7 @@ fn end_highlight_lines(&mut self,
}
}
s.push('^');
println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s)
}
......
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
......@@ -11,9 +11,9 @@
//! Terminal formatting library.
//!
//! This crate provides the `Terminal` trait, which abstracts over an [ANSI
//! Terminal][ansi] to provide color printing, among other things. There are two implementations,
//! the `TerminfoTerminal`, which uses control characters from a
//! [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
//! Terminal][ansi] to provide color printing, among other things. There are two
//! implementations, the `TerminfoTerminal`, which uses control characters from
//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
//! API][win].
//!
//! # Examples
......@@ -21,19 +21,18 @@
//! ```no_run
//! # #![feature(rustc_private)]
//! extern crate term;
//!
//! use std::io::prelude::*;
//!
//! fn main() {
//! let mut t = term::stdout().unwrap();
//!
//! t.fg(term::color::GREEN).unwrap();
//! (write!(t, "hello, ")).unwrap();
//! write!(t, "hello, ").unwrap();
//!
//! t.fg(term::color::RED).unwrap();
//! (writeln!(t, "world!")).unwrap();
//! writeln!(t, "world!").unwrap();
//!
//! t.reset().unwrap();
//! assert!(t.reset().unwrap());
//! }
//! ```
//!
......@@ -58,84 +57,60 @@
#![deny(missing_docs)]
#![feature(box_syntax)]
#![feature(rustc_private)]
#![feature(staged_api)]
#![feature(str_char)]
#![feature(vec_push_all)]
#![cfg_attr(windows, feature(libc))]
// Handle rustfmt skips
#![feature(custom_attribute)]
#![allow(unused_attributes)]
#[macro_use]
extern crate log;
use std::io::prelude::*;
pub use terminfo::TerminfoTerminal;
#[cfg(windows)]
pub use win::WinConsole;
use std::io::prelude::*;
use std::io;
use std::io::{self, Stdout, Stderr};
pub mod terminfo;
#[cfg(windows)]
mod win;
/// A hack to work around the fact that `Box<Write + Send>` does not
/// currently implement `Write`.
pub struct WriterWrapper {
wrapped: Box<Write + Send>,
}
impl Write for WriterWrapper {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.wrapped.write(buf)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.wrapped.flush()
}
}
/// Alias for stdout terminals.
pub type StdoutTerminal = Terminal<Output=Stdout> + Send;
/// Alias for stderr terminals.
pub type StderrTerminal = Terminal<Output=Stderr> + Send;
#[cfg(not(windows))]
/// Return a Terminal wrapping stdout, or None if a terminal couldn't be
/// opened.
pub fn stdout() -> Option<Box<Terminal<WriterWrapper> + Send>> {
TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() })
pub fn stdout() -> Option<Box<StdoutTerminal>> {
TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>)
}
#[cfg(windows)]
/// Return a Terminal wrapping stdout, or None if a terminal couldn't be
/// opened.
pub fn stdout() -> Option<Box<Terminal<WriterWrapper> + Send>> {
let ti = TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() });
match ti {
Some(t) => Some(t),
None => WinConsole::new(WriterWrapper { wrapped: box std::io::stdout() }),
}
pub fn stdout() -> Option<Box<StdoutTerminal>> {
TerminfoTerminal::new(io::stdout())
.map(|t| Box::new(t) as Box<StdoutTerminal>)
.or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box<StdoutTerminal>))
}
#[cfg(not(windows))]
/// Return a Terminal wrapping stderr, or None if a terminal couldn't be
/// opened.
pub fn stderr() -> Option<Box<Terminal<WriterWrapper> + Send>> {
TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() })
pub fn stderr() -> Option<Box<StderrTerminal>> {
TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box<StderrTerminal>)
}
#[cfg(windows)]
/// Return a Terminal wrapping stderr, or None if a terminal couldn't be
/// opened.
pub fn stderr() -> Option<Box<Terminal<WriterWrapper> + Send>> {
let ti = TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() });
match ti {
Some(t) => Some(t),
None => WinConsole::new(WriterWrapper { wrapped: box std::io::stderr() }),
}
pub fn stderr() -> Option<Box<StderrTerminal>> {
TerminfoTerminal::new(io::stderr())
.map(|t| Box::new(t) as Box<StderrTerminal>)
.or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box<StderrTerminal>))
}
......@@ -164,43 +139,41 @@ pub mod color {
pub const BRIGHT_WHITE: Color = 15;
}
/// Terminal attributes
pub mod attr {
pub use self::Attr::*;
/// Terminal attributes for use with term.attr().
///
/// Most attributes can only be turned on and must be turned off with term.reset().
/// The ones that can be turned off explicitly take a boolean value.
/// Color is also represented as an attribute for convenience.
#[derive(Copy, Clone)]
pub enum Attr {
/// Bold (or possibly bright) mode
Bold,
/// Dim mode, also called faint or half-bright. Often not supported
Dim,
/// Italics mode. Often not supported
Italic(bool),
/// Underline mode
Underline(bool),
/// Blink mode
Blink,
/// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
Standout(bool),
/// Reverse mode, inverts the foreground and background colors
Reverse,
/// Secure mode, also called invis mode. Hides the printed text
Secure,
/// Convenience attribute to set the foreground color
ForegroundColor(super::color::Color),
/// Convenience attribute to set the background color
BackgroundColor(super::color::Color),
}
/// Terminal attributes for use with term.attr().
///
/// Most attributes can only be turned on and must be turned off with term.reset().
/// The ones that can be turned off explicitly take a boolean value.
/// Color is also represented as an attribute for convenience.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Attr {
/// Bold (or possibly bright) mode
Bold,
/// Dim mode, also called faint or half-bright. Often not supported
Dim,
/// Italics mode. Often not supported
Italic(bool),
/// Underline mode
Underline(bool),
/// Blink mode
Blink,
/// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
Standout(bool),
/// Reverse mode, inverts the foreground and background colors
Reverse,
/// Secure mode, also called invis mode. Hides the printed text
Secure,
/// Convenience attribute to set the foreground color
ForegroundColor(color::Color),
/// Convenience attribute to set the background color
BackgroundColor(color::Color),
}
/// A terminal with similar capabilities to an ANSI Terminal
/// (foreground/background colors etc).
pub trait Terminal<T: Write>: Write {
pub trait Terminal: Write {
/// The terminal's output writer type.
type Output: Write;
/// Sets the foreground color to the given color.
///
/// If the color is a bright color, but the terminal only supports 8 colors,
......@@ -222,24 +195,29 @@ pub trait Terminal<T: Write>: Write {
/// Sets the given terminal attribute, if supported. Returns `Ok(true)`
/// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if
/// there was an I/O error.
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool>;
fn attr(&mut self, attr: Attr) -> io::Result<bool>;
/// Returns whether the given terminal attribute is supported.
fn supports_attr(&self, attr: attr::Attr) -> bool;
fn supports_attr(&self, attr: Attr) -> bool;
/// Resets all terminal attributes and color to the default.
/// Returns `Ok()`.
fn reset(&mut self) -> io::Result<()>;
/// Resets all terminal attributes and colors to their defaults.
///
/// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
/// was an I/O error.
///
/// *Note: This does not flush.*
///
/// That means the reset command may get buffered so, if you aren't planning on doing anything
/// else that might flush stdout's buffer (e.g. writing a line of text), you should flush after
/// calling reset.
fn reset(&mut self) -> io::Result<bool>;
/// Gets an immutable reference to the stream inside
fn get_ref<'a>(&'a self) -> &'a T;
fn get_ref<'a>(&'a self) -> &'a Self::Output;
/// Gets a mutable reference to the stream inside
fn get_mut<'a>(&'a mut self) -> &'a mut T;
}
fn get_mut<'a>(&'a mut self) -> &'a mut Self::Output;
/// A terminal which can be unwrapped.
pub trait UnwrappableTerminal<T: Write>: Terminal<T> {
/// Returns the contained stream, destroying the `Terminal`
fn unwrap(self) -> T;
fn into_inner(self) -> Self::Output where Self: Sized;
}
......@@ -12,16 +12,20 @@
use std::collections::HashMap;
use std::env;
use std::error;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::io;
use std::io::BufReader;
use std::path::Path;
use attr;
use Attr;
use color;
use Terminal;
use UnwrappableTerminal;
use self::searcher::open;
use self::searcher::get_dbpath_for_term;
use self::parser::compiled::{parse, msys_terminfo};
use self::parm::{expand, Number, Variables};
use self::parm::{expand, Variables, Param};
/// A parsed terminfo database entry.
......@@ -37,6 +41,80 @@ pub struct TermInfo {
pub strings: HashMap<String, Vec<u8>>,
}
/// A terminfo creation error.
#[derive(Debug)]
pub enum Error {
/// TermUnset Indicates that the environment doesn't include enough information to find
/// the terminfo entry.
TermUnset,
/// MalformedTerminfo indicates that parsing the terminfo entry failed.
MalformedTerminfo(String),
/// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
IoError(io::Error),
}
impl error::Error for Error {
fn description(&self) -> &str {
"failed to create TermInfo"
}
fn cause(&self) -> Option<&error::Error> {
use self::Error::*;
match self {
&IoError(ref e) => Some(e),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
&TermUnset => Ok(()),
&MalformedTerminfo(ref e) => e.fmt(f),
&IoError(ref e) => e.fmt(f),
}
}
}
impl TermInfo {
/// Create a TermInfo based on current environment.
pub fn from_env() -> Result<TermInfo, Error> {
let term = match env::var("TERM") {
Ok(name) => TermInfo::from_name(&name),
Err(..) => return Err(Error::TermUnset),
};
if term.is_err() && env::var("MSYSCON").ok().map_or(false, |s| "mintty.exe" == s) {
// msys terminal
Ok(msys_terminfo())
} else {
term
}
}
/// Create a TermInfo for the named terminal.
pub fn from_name(name: &str) -> Result<TermInfo, Error> {
get_dbpath_for_term(name)
.ok_or_else(|| {
Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
})
.and_then(|p| TermInfo::from_path(&(*p)))
}
/// Parse the given TermInfo.
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
Self::_from_path(path.as_ref())
}
// Keep the metadata small
fn _from_path(path: &Path) -> Result<TermInfo, Error> {
let file = try!(File::open(path).map_err(|e| Error::IoError(e)));
let mut reader = BufReader::new(file);
parse(&mut reader, false).map_err(|e| Error::MalformedTerminfo(e))
}
}
pub mod searcher;
/// TermInfo format parsing.
......@@ -47,21 +125,21 @@ pub mod parser {
pub mod parm;
fn cap_for_attr(attr: attr::Attr) -> &'static str {
fn cap_for_attr(attr: Attr) -> &'static str {
match attr {
attr::Bold => "bold",
attr::Dim => "dim",
attr::Italic(true) => "sitm",
attr::Italic(false) => "ritm",
attr::Underline(true) => "smul",
attr::Underline(false) => "rmul",
attr::Blink => "blink",
attr::Standout(true) => "smso",
attr::Standout(false) => "rmso",
attr::Reverse => "rev",
attr::Secure => "invis",
attr::ForegroundColor(_) => "setaf",
attr::BackgroundColor(_) => "setab",
Attr::Bold => "bold",
Attr::Dim => "dim",
Attr::Italic(true) => "sitm",
Attr::Italic(false) => "ritm",
Attr::Underline(true) => "smul",
Attr::Underline(false) => "rmul",
Attr::Blink => "blink",
Attr::Standout(true) => "smso",
Attr::Standout(false) => "rmso",
Attr::Reverse => "rev",
Attr::Secure => "invis",
Attr::ForegroundColor(_) => "setaf",
Attr::BackgroundColor(_) => "setab",
}
}
......@@ -70,23 +148,15 @@ fn cap_for_attr(attr: attr::Attr) -> &'static str {
pub struct TerminfoTerminal<T> {
num_colors: u16,
out: T,
ti: Box<TermInfo>,
ti: TermInfo,
}
impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
impl<T: Write+Send> Terminal for TerminfoTerminal<T> {
type Output = T;
fn fg(&mut self, color: color::Color) -> io::Result<bool> {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
let s = expand(self.ti
.strings
.get("setaf")
.unwrap(),
&[Number(color as isize)],
&mut Variables::new());
if s.is_ok() {
try!(self.out.write_all(&s.unwrap()));
return Ok(true);
}
return self.apply_cap("setaf", &[Param::Number(color as i32)]);
}
Ok(false)
}
......@@ -94,42 +164,22 @@ fn fg(&mut self, color: color::Color) -> io::Result<bool> {
fn bg(&mut self, color: color::Color) -> io::Result<bool> {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
let s = expand(self.ti
.strings
.get("setab")
.unwrap(),
&[Number(color as isize)],
&mut Variables::new());
if s.is_ok() {
try!(self.out.write_all(&s.unwrap()));
return Ok(true);
}
return self.apply_cap("setab", &[Param::Number(color as i32)]);
}
Ok(false)
}
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
fn attr(&mut self, attr: Attr) -> io::Result<bool> {
match attr {
attr::ForegroundColor(c) => self.fg(c),
attr::BackgroundColor(c) => self.bg(c),
_ => {
let cap = cap_for_attr(attr);
let parm = self.ti.strings.get(cap);
if parm.is_some() {
let s = expand(parm.unwrap(), &[], &mut Variables::new());
if s.is_ok() {
try!(self.out.write_all(&s.unwrap()));
return Ok(true);
}
}
Ok(false)
}
Attr::ForegroundColor(c) => self.fg(c),
Attr::BackgroundColor(c) => self.bg(c),
_ => self.apply_cap(cap_for_attr(attr), &[]),
}
}
fn supports_attr(&self, attr: attr::Attr) -> bool {
fn supports_attr(&self, attr: Attr) -> bool {
match attr {
attr::ForegroundColor(_) | attr::BackgroundColor(_) => self.num_colors > 0,
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
_ => {
let cap = cap_for_attr(attr);
self.ti.strings.get(cap).is_some()
......@@ -137,22 +187,22 @@ fn supports_attr(&self, attr: attr::Attr) -> bool {
}
}
fn reset(&mut self) -> io::Result<()> {
let mut cap = self.ti.strings.get("sgr0");
if cap.is_none() {
// are there any terminals that have color/attrs and not sgr0?
// Try falling back to sgr, then op
cap = self.ti.strings.get("sgr");
if cap.is_none() {
cap = self.ti.strings.get("op");
fn reset(&mut self) -> io::Result<bool> {
// are there any terminals that have color/attrs and not sgr0?
// Try falling back to sgr, then op
let cmd = match ["sg0", "sgr", "op"]
.iter()
.filter_map(|cap| self.ti.strings.get(*cap))
.next() {
Some(op) => {
match expand(&op, &[], &mut Variables::new()) {
Ok(cmd) => cmd,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
}
}
}
let s = cap.map_or(Err("can't find terminfo capability `sgr0`".to_owned()),
|op| expand(op, &[], &mut Variables::new()));
if s.is_ok() {
return self.out.write_all(&s.unwrap());
}
Ok(())
None => return Ok(false),
};
self.out.write_all(&cmd).and(Ok(true))
}
fn get_ref<'a>(&'a self) -> &'a T {
......@@ -162,64 +212,36 @@ fn get_ref<'a>(&'a self) -> &'a T {
fn get_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.out
}
}
impl<T: Write+Send+'static> UnwrappableTerminal<T> for TerminfoTerminal<T> {
fn unwrap(self) -> T {
fn into_inner(self) -> T
where Self: Sized
{
self.out
}
}
impl<T: Write+Send+'static> TerminfoTerminal<T> {
/// Returns `None` whenever the terminal cannot be created for some
/// reason.
pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
let term = match env::var("TERM") {
Ok(t) => t,
Err(..) => {
debug!("TERM environment variable not defined");
return None;
}
};
let mut file = match open(&term[..]) {
Ok(f) => f,
Err(err) => {
return match env::var("MSYSCON") {
Ok(ref val) if &val[..] == "mintty.exe" => {
// msys terminal
Some(box TerminfoTerminal {
out: out,
ti: msys_terminfo(),
num_colors: 8,
})
}
_ => {
debug!("error finding terminfo entry: {:?}", err);
None
}
};
}
};
let ti = parse(&mut file, false);
if ti.is_err() {
debug!("error parsing terminfo entry: {:?}", ti.err().unwrap());
return None;
}
let inf = ti.unwrap();
let nc = if inf.strings.get("setaf").is_some() && inf.strings.get("setab").is_some() {
inf.numbers.get("colors").map_or(0, |&n| n)
impl<T: Write+Send> TerminfoTerminal<T> {
/// Create a new TerminfoTerminal with the given TermInfo and Write.
pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
let nc = if terminfo.strings.contains_key("setaf") &&
terminfo.strings.contains_key("setab") {
terminfo.numbers.get("colors").map_or(0, |&n| n)
} else {
0
};
Some(box TerminfoTerminal {
TerminfoTerminal {
out: out,
ti: inf,
ti: terminfo,
num_colors: nc,
})
}
}
/// Create a new TerminfoTerminal for the current environment with the given Write.
///
/// Returns `None` when the terminfo cannot be found or parsed.
pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
}
fn dim_if_necessary(&self, color: color::Color) -> color::Color {
......@@ -229,6 +251,18 @@ fn dim_if_necessary(&self, color: color::Color) -> color::Color {
color
}
}
fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
match self.ti.strings.get(cmd) {
Some(cmd) => {
match expand(&cmd, params, &mut Variables::new()) {
Ok(s) => self.out.write_all(&s).and(Ok(true)),
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
}
}
None => Ok(false),
}
}
}
......
此差异已折叠。
......@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(non_upper_case_globals)]
#![allow(non_upper_case_globals, missing_docs)]
//! ncurses-compatible compiled terminfo format parsing (term(5))
......@@ -20,7 +20,6 @@
// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
#[rustfmt_skip]
#[allow(missing_docs)]
pub static boolfnames: &'static[&'static str] = &["auto_left_margin", "auto_right_margin",
"no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
"hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
......@@ -34,14 +33,12 @@
"return_does_clr_eol"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static boolnames: &'static[&'static str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
"gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
"nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
"xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static numfnames: &'static[&'static str] = &[ "columns", "init_tabs", "lines",
"lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
"width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
......@@ -53,14 +50,12 @@
"new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static numnames: &'static[&'static str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
"vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
"spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
"btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static stringfnames: &'static[&'static str] = &[ "back_tab", "bell", "carriage_return",
"change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
"column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
......@@ -135,7 +130,6 @@
"acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static stringnames: &'static[&'static str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
"_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
"ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
......@@ -170,114 +164,132 @@
"OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
"box1"];
/// Parse a compiled terminfo entry, using long capability names if `longnames` is true
pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String> {
macro_rules! try { ($e:expr) => (
fn read_le_u16(r: &mut io::Read) -> io::Result<u16> {
let mut b = [0; 2];
let mut amt = 0;
while amt < b.len() {
match try!(r.read(&mut b[amt..])) {
0 => return Err(io::Error::new(io::ErrorKind::Other, "end of file")),
n => amt += n,
}
}
Ok((b[0] as u16) | ((b[1] as u16) << 8))
}
fn read_byte(r: &mut io::Read) -> io::Result<u8> {
match r.bytes().next() {
Some(s) => s,
None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
}
}
/// Parse a compiled terminfo entry, using long capability names if `longnames`
/// is true
pub fn parse(file: &mut io::Read, longnames: bool) -> Result<TermInfo, String> {
macro_rules! try( ($e:expr) => (
match $e {
Ok(e) => e,
Err(e) => return Err(format!("{:?}", e))
Err(e) => return Err(format!("{}", e))
}
) }
) );
let bnames;
let snames;
let nnames;
if longnames {
bnames = boolfnames;
snames = stringfnames;
nnames = numfnames;
let (bnames, snames, nnames) = if longnames {
(boolfnames, stringfnames, numfnames)
} else {
bnames = boolnames;
snames = stringnames;
nnames = numnames;
}
(boolnames, stringnames, numnames)
};
// Check magic number
let magic = try!(read_le_u16(file));
if magic != 0x011A {
return Err(format!("invalid magic number: expected {:x}, found {:x}",
0x011A_usize,
magic as usize));
0x011A,
magic));
}
let names_bytes = try!(read_le_u16(file)) as isize;
let bools_bytes = try!(read_le_u16(file)) as isize;
let numbers_count = try!(read_le_u16(file)) as isize;
let string_offsets_count = try!(read_le_u16(file)) as isize;
let string_table_bytes = try!(read_le_u16(file)) as isize;
// According to the spec, these fields must be >= -1 where -1 means that the feature is not
// supported. Using 0 instead of -1 works because we skip sections with length 0.
macro_rules! read_nonneg {
() => {{
match try!(read_le_u16(file)) as i16 {
n if n >= 0 => n as usize,
-1 => 0,
_ => return Err("incompatible file: length fields must be >= -1".to_string()),
}
}}
}
let names_bytes = read_nonneg!();
let bools_bytes = read_nonneg!();
let numbers_count = read_nonneg!();
let string_offsets_count = read_nonneg!();
let string_table_bytes = read_nonneg!();
assert!(names_bytes > 0);
if names_bytes == 0 {
return Err("incompatible file: names field must be at least 1 byte wide".to_string());
}
if (bools_bytes as usize) > boolnames.len() {
return Err("incompatible file: more booleans than expected".to_owned());
if bools_bytes > boolnames.len() {
return Err("incompatible file: more booleans than expected".to_string());
}
if (numbers_count as usize) > numnames.len() {
return Err("incompatible file: more numbers than expected".to_owned());
if numbers_count > numnames.len() {
return Err("incompatible file: more numbers than expected".to_string());
}
if (string_offsets_count as usize) > stringnames.len() {
return Err("incompatible file: more string offsets than expected".to_owned());
if string_offsets_count > stringnames.len() {
return Err("incompatible file: more string offsets than expected".to_string());
}
// don't read NUL
let bytes = try!(read_exact(file, names_bytes as usize - 1));
let mut bytes = Vec::new();
try!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
let names_str = match String::from_utf8(bytes) {
Ok(s) => s,
Err(_) => return Err("input not utf-8".to_owned()),
Err(_) => return Err("input not utf-8".to_string()),
};
let term_names: Vec<String> = names_str.split('|')
.map(str::to_owned)
.map(|s| s.to_string())
.collect();
try!(read_byte(file)); // consume NUL
let mut bools_map = HashMap::new();
if bools_bytes != 0 {
for i in 0..bools_bytes {
let b = try!(read_byte(file));
if b == 1 {
bools_map.insert(bnames[i as usize].to_owned(), true);
}
}
// consume NUL
if try!(read_byte(file)) != b'\0' {
return Err("incompatible file: missing null terminator for names section".to_string());
}
let bools_map: HashMap<String, bool> = try! {
(0..bools_bytes).filter_map(|i| match read_byte(file) {
Err(e) => Some(Err(e)),
Ok(1) => Some(Ok((bnames[i].to_string(), true))),
Ok(_) => None
}).collect()
};
if (bools_bytes + names_bytes) % 2 == 1 {
try!(read_byte(file)); // compensate for padding
}
let mut numbers_map = HashMap::new();
if numbers_count != 0 {
for i in 0..numbers_count {
let n = try!(read_le_u16(file));
if n != 0xFFFF {
numbers_map.insert(nnames[i as usize].to_owned(), n);
}
}
}
let mut string_map = HashMap::new();
if string_offsets_count != 0 {
let mut string_offsets = Vec::with_capacity(10);
for _ in 0..string_offsets_count {
string_offsets.push(try!(read_le_u16(file)));
}
let numbers_map: HashMap<String, u16> = try! {
(0..numbers_count).filter_map(|i| match read_le_u16(file) {
Ok(0xFFFF) => None,
Ok(n) => Some(Ok((nnames[i].to_string(), n))),
Err(e) => Some(Err(e))
}).collect()
};
let string_table = try!(read_exact(file, string_table_bytes as usize));
let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
let string_offsets: Vec<u16> = try!((0..string_offsets_count)
.map(|_| read_le_u16(file))
.collect());
if string_table.len() != string_table_bytes as usize {
return Err("error: hit EOF before end of string table".to_owned());
}
let mut string_table = Vec::new();
try!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
for (i, v) in string_offsets.iter().enumerate() {
let offset = *v;
if offset == 0xFFFF {
// non-entry
continue;
}
try!(string_offsets.into_iter().enumerate().filter(|&(_, offset)| {
// non-entry
offset != 0xFFFF
}).map(|(i, offset)| {
let offset = offset as usize;
let name = if snames[i] == "_" {
stringfnames[i]
......@@ -288,30 +300,22 @@ pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String>
if offset == 0xFFFE {
// undocumented: FFFE indicates cap@, which means the capability is not present
// unsure if the handling for this is correct
string_map.insert(name.to_owned(), Vec::new());
continue;
return Ok((name.to_string(), Vec::new()));
}
// Find the offset of the NUL we want to go to
let nulpos = string_table[offset as usize..string_table_bytes as usize]
.iter()
.position(|&b| b == 0);
let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
match nulpos {
Some(len) => {
string_map.insert(name.to_string(),
string_table[offset as usize..(offset as usize + len)]
.to_vec())
}
None => {
return Err("invalid file: missing NUL in string_table".to_owned());
}
};
}
}
Some(len) => Ok((name.to_string(), string_table[offset..offset + len].to_vec())),
None => Err("invalid file: missing NUL in string_table".to_string()),
}
}).collect())
} else {
HashMap::new()
};
// And that's all there is to it
Ok(box TermInfo {
Ok(TermInfo {
names: term_names,
bools: bools_map,
numbers: numbers_map,
......@@ -319,42 +323,27 @@ pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String>
})
}
fn read_le_u16<R: Read + ?Sized>(r: &mut R) -> io::Result<u16> {
let mut b = [0; 2];
assert_eq!(try!(r.read(&mut b)), 2);
Ok((b[0] as u16) | ((b[1] as u16) << 8))
}
fn read_byte<R: Read + ?Sized>(r: &mut R) -> io::Result<u8> {
let mut b = [0; 1];
assert_eq!(try!(r.read(&mut b)), 1);
Ok(b[0])
}
fn read_exact<R: Read + ?Sized>(r: &mut R, sz: usize) -> io::Result<Vec<u8>> {
let mut v = Vec::with_capacity(sz);
try!(r.take(sz as u64).read_to_end(&mut v));
assert_eq!(v.len(), sz);
Ok(v)
}
/// Create a dummy TermInfo struct for msys terminals
pub fn msys_terminfo() -> Box<TermInfo> {
pub fn msys_terminfo() -> TermInfo {
let mut strings = HashMap::new();
strings.insert("sgr0".to_owned(), b"\x1B[0m".to_vec());
strings.insert("bold".to_owned(), b"\x1B[1m".to_vec());
strings.insert("setaf".to_owned(), b"\x1B[3%p1%dm".to_vec());
strings.insert("setab".to_owned(), b"\x1B[4%p1%dm".to_vec());
box TermInfo {
names: vec!["cygwin".to_owned()], // msys is a fork of an older cygwin version
strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
let mut numbers = HashMap::new();
numbers.insert("colors".to_string(), 8u16);
TermInfo {
names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
bools: HashMap::new(),
numbers: HashMap::new(),
numbers: numbers,
strings: strings,
}
}
#[cfg(test)]
mod tests {
mod test {
use super::{boolnames, boolfnames, numnames, numfnames, stringnames, stringfnames};
......
......@@ -13,29 +13,26 @@
//! Does not support hashed database, only filesystem!
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::fs;
use std::path::PathBuf;
/// Return path to database entry for `term`
#[allow(deprecated)]
pub fn get_dbpath_for_term(term: &str) -> Option<Box<PathBuf>> {
if term.is_empty() {
return None;
}
let homedir = env::home_dir();
pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
let mut dirs_to_search = Vec::new();
let first_char = term.char_at(0);
let first_char = match term.chars().next() {
Some(c) => c,
None => return None,
};
// Find search directory
match env::var_os("TERMINFO") {
Some(dir) => dirs_to_search.push(PathBuf::from(dir)),
None => {
if homedir.is_some() {
if let Some(mut homedir) = env::home_dir() {
// ncurses compatibility;
dirs_to_search.push(homedir.unwrap().join(".terminfo"))
homedir.push(".terminfo");
dirs_to_search.push(homedir)
}
match env::var("TERMINFO_DIRS") {
Ok(dirs) => {
......@@ -61,35 +58,26 @@ pub fn get_dbpath_for_term(term: &str) -> Option<Box<PathBuf>> {
};
// Look for the terminal in all of the search directories
for p in &dirs_to_search {
if p.exists() {
let f = first_char.to_string();
let newp = p.join(&f).join(term);
if newp.exists() {
return Some(box newp);
for mut p in dirs_to_search {
if fs::metadata(&p).is_ok() {
p.push(&first_char.to_string());
p.push(&term);
if fs::metadata(&p).is_ok() {
return Some(p);
}
// on some installations the dir is named after the hex of the char (e.g. OS X)
let f = format!("{:x}", first_char as usize);
let newp = p.join(&f).join(term);
if newp.exists() {
return Some(box newp);
}
}
}
None
}
p.pop();
p.pop();
/// Return open file for `term`
pub fn open(term: &str) -> Result<File, String> {
match get_dbpath_for_term(term) {
Some(x) => {
match File::open(&*x) {
Ok(file) => Ok(file),
Err(e) => Err(format!("error opening file: {:?}", e)),
// on some installations the dir is named after the hex of the char
// (e.g. OS X)
p.push(&format!("{:x}", first_char as usize));
p.push(term);
if fs::metadata(&p).is_ok() {
return Some(p);
}
}
None => Err(format!("could not find terminfo entry for {:?}", term)),
}
None
}
#[test]
......@@ -109,11 +97,3 @@ fn x(t: &str) -> String {
assert!(x("screen") == "/usr/share/terminfo/s/screen");
env::remove_var("TERMINFO_DIRS");
}
#[test]
#[ignore(reason = "see test_get_dbpath_for_term")]
fn test_open() {
open("screen").unwrap();
let t = open("nonexistent terminal that hopefully does not exist");
assert!(t.is_err());
}
......@@ -17,9 +17,9 @@
use std::io;
use std::io::prelude::*;
use attr;
use Attr;
use color;
use {Terminal, UnwrappableTerminal};
use Terminal;
/// A Terminal implementation which uses the Win32 Console API.
pub struct WinConsole<T> {
......@@ -115,7 +115,7 @@ fn apply(&mut self) {
/// Returns `None` whenever the terminal cannot be created for some
/// reason.
pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
pub fn new(out: T) -> io::Result<WinConsole<T>> {
let fg;
let bg;
unsafe {
......@@ -128,7 +128,7 @@ pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
bg = color::BLACK;
}
}
Some(box WinConsole {
Ok(WinConsole {
buf: out,
def_foreground: fg,
def_background: bg,
......@@ -148,7 +148,9 @@ fn flush(&mut self) -> io::Result<()> {
}
}
impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
impl<T: Write+Send+'static> Terminal for WinConsole<T> {
type Output = T;
fn fg(&mut self, color: color::Color) -> io::Result<bool> {
self.foreground = color;
self.apply();
......@@ -163,14 +165,14 @@ fn bg(&mut self, color: color::Color) -> io::Result<bool> {
Ok(true)
}
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
fn attr(&mut self, attr: Attr) -> io::Result<bool> {
match attr {
attr::ForegroundColor(f) => {
Attr::ForegroundColor(f) => {
self.foreground = f;
self.apply();
Ok(true)
}
attr::BackgroundColor(b) => {
Attr::BackgroundColor(b) => {
self.background = b;
self.apply();
Ok(true)
......@@ -179,21 +181,21 @@ fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
}
}
fn supports_attr(&self, attr: attr::Attr) -> bool {
fn supports_attr(&self, attr: Attr) -> bool {
// it claims support for underscore and reverse video, but I can't get
// it to do anything -cmr
match attr {
attr::ForegroundColor(_) | attr::BackgroundColor(_) => true,
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
_ => false,
}
}
fn reset(&mut self) -> io::Result<()> {
fn reset(&mut self) -> io::Result<bool> {
self.foreground = self.def_foreground;
self.background = self.def_background;
self.apply();
Ok(())
Ok(true)
}
fn get_ref<'a>(&'a self) -> &'a T {
......@@ -203,10 +205,10 @@ fn get_ref<'a>(&'a self) -> &'a T {
fn get_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.buf
}
}
impl<T: Write+Send+'static> UnwrappableTerminal<T> for WinConsole<T> {
fn unwrap(self) -> T {
fn into_inner(self) -> T
where Self: Sized
{
self.buf
}
}
......@@ -428,7 +428,7 @@ pub enum TestResult {
unsafe impl Send for TestResult {}
enum OutputLocation<T> {
Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
Pretty(Box<term::StdoutTerminal>),
Raw(T),
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册