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.
This commit is contained in:
bors 2015-12-03 19:32:57 +00:00
commit 372e82c9c0
8 changed files with 549 additions and 702 deletions

View File

@ -20,7 +20,7 @@ use std::cell::{RefCell, Cell};
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 @@ impl EmitterWriter {
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 @@ impl EmitterWriter {
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 @@ impl EmitterWriter {
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 @@ impl EmitterWriter {
}
}
s.push('^');
println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s)
}

View File

@ -1,4 +1,4 @@
// 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;
}

View File

@ -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 @@ impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
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 @@ impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
}
}
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 @@ impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<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 @@ impl<T: Write+Send+'static> TerminfoTerminal<T> {
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),
}
}
}

View File

@ -10,15 +10,14 @@
//! Parameterized string expansion
pub use self::Param::*;
use self::Param::*;
use self::States::*;
use self::FormatState::*;
use self::FormatOp::*;
use std::ascii::AsciiExt;
use std::mem::replace;
use std::iter::repeat;
#[derive(Copy, Clone, PartialEq)]
#[derive(Clone, Copy, PartialEq)]
enum States {
Nothing,
Percent,
@ -27,15 +26,15 @@ enum States {
PushParam,
CharConstant,
CharClose,
IntConstant(isize),
IntConstant(i32),
FormatPattern(Flags, FormatState),
SeekIfElse(isize),
SeekIfElsePercent(isize),
SeekIfEnd(isize),
SeekIfEndPercent(isize),
SeekIfElse(usize),
SeekIfElsePercent(usize),
SeekIfEnd(usize),
SeekIfEndPercent(usize),
}
#[derive(Copy, Clone, PartialEq)]
#[derive(Copy, PartialEq, Clone)]
enum FormatState {
FormatStateFlags,
FormatStateWidth,
@ -47,7 +46,7 @@ enum FormatState {
#[derive(Clone)]
pub enum Param {
Words(String),
Number(isize),
Number(i32),
}
/// Container for static and dynamic variable arrays
@ -94,11 +93,11 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
// Copy parameters into a local vector for mutability
let mut mparams = [Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
Number(0), Number(0), Number(0)];
for (dst, src) in mparams.iter_mut().zip(params) {
for (dst, src) in mparams.iter_mut().zip(params.iter()) {
*dst = (*src).clone();
}
for &c in cap {
for &c in cap.iter() {
let cur = c as char;
let mut old_state = state;
match state {
@ -116,20 +115,13 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
state = Nothing
}
'c' => {
if !stack.is_empty() {
match stack.pop().unwrap() {
// if c is 0, use 0200 (128) for ncurses compatibility
Number(c) => {
output.push(if c == 0 {
128
} else {
c as u8
})
}
_ => return Err("a non-char was used with %c".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
match stack.pop() {
// if c is 0, use 0200 (128) for ncurses compatibility
Some(Number(0)) => output.push(128u8),
// Don't check bounds. ncurses just casts and truncates.
Some(Number(c)) => output.push(c as u8),
Some(_) => return Err("a non-char was used with %c".to_string()),
None => return Err("stack is empty".to_string()),
}
}
'p' => state = PushParam,
@ -138,208 +130,89 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
'\'' => state = CharConstant,
'{' => state = IntConstant(0),
'l' => {
if !stack.is_empty() {
match stack.pop().unwrap() {
Words(s) => stack.push(Number(s.len() as isize)),
_ => return Err("a non-str was used with %l".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
match stack.pop() {
Some(Words(s)) => stack.push(Number(s.len() as i32)),
Some(_) => return Err("a non-str was used with %l".to_string()),
None => return Err("stack is empty".to_string()),
}
}
'+' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x + y)),
_ => return Err("non-numbers on stack with +".to_owned()),
'+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
match (stack.pop(), stack.pop()) {
(Some(Number(y)), Some(Number(x))) => {
stack.push(Number(match cur {
'+' => x + y,
'-' => x - y,
'*' => x * y,
'/' => x / y,
'|' => x | y,
'&' => x & y,
'^' => x ^ y,
'm' => x % y,
_ => unreachable!("All cases handled"),
}))
}
} else {
return Err("stack is empty".to_owned());
(Some(_), Some(_)) => {
return Err(format!("non-numbers on stack with {}", cur))
}
_ => return Err("stack is empty".to_string()),
}
}
'-' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x - y)),
_ => return Err("non-numbers on stack with -".to_owned()),
'=' | '>' | '<' | 'A' | 'O' => {
match (stack.pop(), stack.pop()) {
(Some(Number(y)), Some(Number(x))) => {
stack.push(Number(if match cur {
'=' => x == y,
'<' => x < y,
'>' => x > y,
'A' => x > 0 && y > 0,
'O' => x > 0 || y > 0,
_ => unreachable!(),
} {
1
} else {
0
}))
}
} else {
return Err("stack is empty".to_owned());
(Some(_), Some(_)) => {
return Err(format!("non-numbers on stack with {}", cur))
}
_ => return Err("stack is empty".to_string()),
}
}
'*' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x * y)),
_ => return Err("non-numbers on stack with *".to_owned()),
'!' | '~' => {
match stack.pop() {
Some(Number(x)) => {
stack.push(Number(match cur {
'!' if x > 0 => 0,
'!' => 1,
'~' => !x,
_ => unreachable!(),
}))
}
} else {
return Err("stack is empty".to_owned());
}
}
'/' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x / y)),
_ => return Err("non-numbers on stack with /".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'm' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x % y)),
_ => return Err("non-numbers on stack with %".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'&' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x & y)),
_ => return Err("non-numbers on stack with &".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'|' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x | y)),
_ => return Err("non-numbers on stack with |".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'^' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => stack.push(Number(x ^ y)),
_ => return Err("non-numbers on stack with ^".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'=' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => {
stack.push(Number(if x == y {
1
} else {
0
}))
}
_ => return Err("non-numbers on stack with =".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'>' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => {
stack.push(Number(if x > y {
1
} else {
0
}))
}
_ => return Err("non-numbers on stack with >".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'<' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(y), Number(x)) => {
stack.push(Number(if x < y {
1
} else {
0
}))
}
_ => return Err("non-numbers on stack with <".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'A' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(0), Number(_)) => stack.push(Number(0)),
(Number(_), Number(0)) => stack.push(Number(0)),
(Number(_), Number(_)) => stack.push(Number(1)),
_ => return Err("non-numbers on stack with logical and".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'O' => {
if stack.len() > 1 {
match (stack.pop().unwrap(), stack.pop().unwrap()) {
(Number(0), Number(0)) => stack.push(Number(0)),
(Number(_), Number(_)) => stack.push(Number(1)),
_ => return Err("non-numbers on stack with logical or".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'!' => {
if !stack.is_empty() {
match stack.pop().unwrap() {
Number(0) => stack.push(Number(1)),
Number(_) => stack.push(Number(0)),
_ => return Err("non-number on stack with logical not".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
}
}
'~' => {
if !stack.is_empty() {
match stack.pop().unwrap() {
Number(x) => stack.push(Number(!x)),
_ => return Err("non-number on stack with %~".to_owned()),
}
} else {
return Err("stack is empty".to_owned());
Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
None => return Err("stack is empty".to_string()),
}
}
'i' => {
match (mparams[0].clone(), mparams[1].clone()) {
(Number(x), Number(y)) => {
match (&mparams[0], &mparams[1]) {
(&Number(x), &Number(y)) => {
mparams[0] = Number(x + 1);
mparams[1] = Number(y + 1);
}
(_, _) => return Err("first two params not numbers with %i".to_owned()),
(_, _) => {
return Err("first two params not numbers with %i".to_string())
}
}
}
// printf-style support for %doxXs
'd' | 'o' | 'x' | 'X' | 's' => {
if !stack.is_empty() {
if let Some(arg) = stack.pop() {
let flags = Flags::new();
let res = format(stack.pop().unwrap(), FormatOp::from_char(cur), flags);
if res.is_err() {
return res;
}
output.push_all(&res.unwrap())
let res = try!(format(arg, FormatOp::from_char(cur), flags));
output.extend(res.iter().map(|x| *x));
} else {
return Err("stack is empty".to_owned());
return Err("stack is empty".to_string());
}
}
':' | '#' | ' ' | '.' | '0'...'9' => {
@ -362,47 +235,45 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
// conditionals
'?' => (),
't' => {
if !stack.is_empty() {
match stack.pop().unwrap() {
Number(0) => state = SeekIfElse(0),
Number(_) => (),
_ => return Err("non-number on stack with conditional".to_owned()),
match stack.pop() {
Some(Number(0)) => state = SeekIfElse(0),
Some(Number(_)) => (),
Some(_) => {
return Err("non-number on stack with conditional".to_string())
}
} else {
return Err("stack is empty".to_owned());
None => return Err("stack is empty".to_string()),
}
}
'e' => state = SeekIfEnd(0),
';' => (),
_ => return Err(format!("unrecognized format option {:?}", cur)),
_ => return Err(format!("unrecognized format option {}", cur)),
}
}
PushParam => {
// params are 1-indexed
stack.push(mparams[match cur.to_digit(10) {
Some(d) => d as usize - 1,
None => return Err("bad param number".to_owned()),
None => return Err("bad param number".to_string()),
}]
.clone());
}
SetVar => {
if cur >= 'A' && cur <= 'Z' {
if !stack.is_empty() {
if let Some(arg) = stack.pop() {
let idx = (cur as u8) - b'A';
vars.sta[idx as usize] = stack.pop().unwrap();
vars.sta[idx as usize] = arg;
} else {
return Err("stack is empty".to_owned());
return Err("stack is empty".to_string());
}
} else if cur >= 'a' && cur <= 'z' {
if !stack.is_empty() {
if let Some(arg) = stack.pop() {
let idx = (cur as u8) - b'a';
vars.dyn[idx as usize] = stack.pop().unwrap();
vars.dyn[idx as usize] = arg;
} else {
return Err("stack is empty".to_owned());
return Err("stack is empty".to_string());
}
} else {
return Err("bad variable name in %P".to_owned());
return Err("bad variable name in %P".to_string());
}
}
GetVar => {
@ -413,47 +284,45 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
let idx = (cur as u8) - b'a';
stack.push(vars.dyn[idx as usize].clone());
} else {
return Err("bad variable name in %g".to_owned());
return Err("bad variable name in %g".to_string());
}
}
CharConstant => {
stack.push(Number(c as isize));
stack.push(Number(c as i32));
state = CharClose;
}
CharClose => {
if cur != '\'' {
return Err("malformed character constant".to_owned());
return Err("malformed character constant".to_string());
}
}
IntConstant(i) => {
match cur {
'}' => {
stack.push(Number(i));
state = Nothing;
if cur == '}' {
stack.push(Number(i));
state = Nothing;
} else if let Some(digit) = cur.to_digit(10) {
match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) {
Some(i) => {
state = IntConstant(i);
old_state = Nothing;
}
None => return Err("int constant too large".to_string()),
}
'0'...'9' => {
state = IntConstant(i * 10 + (cur as isize - '0' as isize));
old_state = Nothing;
}
_ => return Err("bad isize constant".to_owned()),
} else {
return Err("bad int constant".to_string());
}
}
FormatPattern(ref mut flags, ref mut fstate) => {
old_state = Nothing;
match (*fstate, cur) {
(_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => {
if !stack.is_empty() {
let res = format(stack.pop().unwrap(),
FormatOp::from_char(cur),
*flags);
if res.is_err() {
return res;
}
output.push_all(&res.unwrap());
if let Some(arg) = stack.pop() {
let res = try!(format(arg, FormatOp::from_char(cur), *flags));
output.extend(res.iter().map(|x| *x));
// will cause state to go to Nothing
old_state = FormatPattern(*flags, *fstate);
} else {
return Err("stack is empty".to_owned());
return Err("stack is empty".to_string());
}
}
(FormatStateFlags, '#') => {
@ -479,7 +348,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
let old = flags.width;
flags.width = flags.width * 10 + (cur as usize - '0' as usize);
if flags.width < old {
return Err("format width overflow".to_owned());
return Err("format width overflow".to_string());
}
}
(FormatStateWidth, '.') => {
@ -489,10 +358,10 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
let old = flags.precision;
flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
if flags.precision < old {
return Err("format precision overflow".to_owned());
return Err("format precision overflow".to_string());
}
}
_ => return Err("invalid format specifier".to_owned()),
_ => return Err("invalid format specifier".to_string()),
}
}
SeekIfElse(level) => {
@ -543,7 +412,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
Ok(output)
}
#[derive(Copy, Clone, PartialEq)]
#[derive(Copy, PartialEq, Clone)]
struct Flags {
width: usize,
precision: usize,
@ -600,61 +469,55 @@ impl FormatOp {
fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
let mut s = match val {
Number(d) => {
let s = match (op, flags.sign) {
(FormatDigit, true) => format!("{:+}", d).into_bytes(),
(FormatDigit, false) => format!("{}", d).into_bytes(),
(FormatOctal, _) => format!("{:o}", d).into_bytes(),
(FormatHex, _) => format!("{:x}", d).into_bytes(),
(FormatHEX, _) => format!("{:X}", d).into_bytes(),
(FormatString, _) => return Err("non-number on stack with %s".to_owned()),
};
let mut s: Vec<u8> = s.into_iter().collect();
if flags.precision > s.len() {
let mut s_ = Vec::with_capacity(flags.precision);
let n = flags.precision - s.len();
s_.extend(repeat(b'0').take(n));
s_.extend(s);
s = s_;
}
assert!(!s.is_empty(), "string conversion produced empty result");
match op {
FormatDigit => {
if flags.space && !(s[0] == b'-' || s[0] == b'+') {
s.insert(0, b' ');
if flags.sign {
format!("{:+01$}", d, flags.precision)
} else if d < 0 {
// C doesn't take sign into account in precision calculation.
format!("{:01$}", d, flags.precision + 1)
} else if flags.space {
format!(" {:01$}", d, flags.precision)
} else {
format!("{:01$}", d, flags.precision)
}
}
FormatOctal => {
if flags.alternate && s[0] != b'0' {
s.insert(0, b'0');
if flags.alternate {
// Leading octal zero counts against precision.
format!("0{:01$o}", d, flags.precision.saturating_sub(1))
} else {
format!("{:01$o}", d, flags.precision)
}
}
FormatHex => {
if flags.alternate {
let s_ = replace(&mut s, vec![b'0', b'x']);
s.extend(s_);
if flags.alternate && d != 0 {
format!("0x{:01$x}", d, flags.precision)
} else {
format!("{:01$x}", d, flags.precision)
}
}
FormatHEX => {
s = s.to_ascii_uppercase();
if flags.alternate {
let s_ = replace(&mut s, vec![b'0', b'X']);
s.extend(s_);
if flags.alternate && d != 0 {
format!("0X{:01$X}", d, flags.precision)
} else {
format!("{:01$X}", d, flags.precision)
}
}
FormatString => unreachable!(),
FormatString => return Err("non-number on stack with %s".to_string()),
}
s
.into_bytes()
}
Words(s) => {
match op {
FormatString => {
let mut s = s.as_bytes().to_vec();
let mut s = s.into_bytes();
if flags.precision > 0 && flags.precision < s.len() {
s.truncate(flags.precision);
}
s
}
_ => return Err(format!("non-string on stack with %{:?}", op.to_char())),
_ => return Err(format!("non-string on stack with %{}", op.to_char())),
}
}
};
@ -665,7 +528,7 @@ fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
} else {
let mut s_ = Vec::with_capacity(flags.width);
s_.extend(repeat(b' ').take(n));
s_.extend(s);
s_.extend(s.into_iter());
s = s_;
}
}
@ -673,8 +536,9 @@ fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
}
#[cfg(test)]
mod tests {
use super::{expand, Param, Words, Variables, Number};
mod test {
use super::{expand, Variables};
use super::Param::{self, Words, Number};
use std::result::Result::Ok;
#[test]
@ -711,12 +575,12 @@ mod tests {
vars: &mut Variables)
-> Result<Vec<u8>, String> {
let mut u8v: Vec<_> = fmt.bytes().collect();
u8v.extend(cap.bytes());
u8v.extend(cap.as_bytes().iter().map(|&b| b));
expand(&u8v, params, vars)
}
let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
for &cap in &caps {
for &cap in caps.iter() {
let res = get_res("", cap, &[], vars);
assert!(res.is_err(),
"Op {} succeeded incorrectly with 0 stack entries",
@ -733,7 +597,7 @@ mod tests {
res.err().unwrap());
}
let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
for &cap in &caps {
for &cap in caps.iter() {
let res = expand(cap.as_bytes(), &[], vars);
assert!(res.is_err(),
"Binop {} succeeded incorrectly with 0 stack entries",
@ -744,7 +608,7 @@ mod tests {
cap);
let res = get_res("%{1}%{2}", cap, &[], vars);
assert!(res.is_ok(),
"Binop {} failed with 2 stack entries: {:?}",
"Binop {} failed with 2 stack entries: {}",
cap,
res.err().unwrap());
}
@ -757,20 +621,20 @@ mod tests {
#[test]
fn test_comparison_ops() {
let v = [('<', [1, 0, 0]), ('=', [0, 1, 0]), ('>', [0, 0, 1])];
for &(op, bs) in &v {
let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
for &(op, bs) in v.iter() {
let s = format!("%{{1}}%{{2}}%{}%d", op);
let res = expand(s.as_bytes(), &[], &mut Variables::new());
assert!(res.is_ok(), res.err().unwrap());
assert_eq!(res.unwrap(), [b'0' + bs[0]]);
assert_eq!(res.unwrap(), vec![b'0' + bs[0]]);
let s = format!("%{{1}}%{{1}}%{}%d", op);
let res = expand(s.as_bytes(), &[], &mut Variables::new());
assert!(res.is_ok(), res.err().unwrap());
assert_eq!(res.unwrap(), [b'0' + bs[1]]);
assert_eq!(res.unwrap(), vec![b'0' + bs[1]]);
let s = format!("%{{2}}%{{1}}%{}%d", op);
let res = expand(s.as_bytes(), &[], &mut Variables::new());
assert!(res.is_ok(), res.err().unwrap());
assert_eq!(res.unwrap(), [b'0' + bs[2]]);
assert_eq!(res.unwrap(), vec![b'0' + bs[2]]);
}
}
@ -800,7 +664,7 @@ mod tests {
Words("foo".to_string())],
vars),
Ok("foofoo ffo".bytes().collect::<Vec<_>>()));
assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_owned())], vars),
assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars),
Ok("fo ".bytes().collect::<Vec<_>>()));
assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),

View File

@ -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 @@ use super::super::TermInfo;
// 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 @@ pub static boolfnames: &'static[&'static str] = &["auto_left_margin", "auto_righ
"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 @@ pub static numfnames: &'static[&'static str] = &[ "columns", "init_tabs", "lines
"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 @@ pub static stringfnames: &'static[&'static str] = &[ "back_tab", "bell", "carria
"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 @@ pub static stringnames: &'static[&'static str] = &[ "cbt", "_", "cr", "csr", "tb
"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;
assert!(names_bytes > 0);
if (bools_bytes as usize) > boolnames.len() {
return Err("incompatible file: more booleans than expected".to_owned());
// 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()),
}
}}
}
if (numbers_count as usize) > numnames.len() {
return Err("incompatible file: more numbers than expected".to_owned());
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!();
if names_bytes == 0 {
return Err("incompatible file: names field must be at least 1 byte wide".to_string());
}
if (string_offsets_count as usize) > stringnames.len() {
return Err("incompatible file: more string offsets than expected".to_owned());
if bools_bytes > boolnames.len() {
return Err("incompatible file: more booleans than expected".to_string());
}
if numbers_count > numnames.len() {
return Err("incompatible file: more numbers than expected".to_string());
}
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 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 mut string_map = HashMap::new();
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_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 mut string_table = Vec::new();
try!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
let string_table = try!(read_exact(file, string_table_bytes as usize));
if string_table.len() != string_table_bytes as usize {
return Err("error: hit EOF before end of string table".to_owned());
}
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};

View File

@ -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,37 +58,28 @@ 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);
p.pop();
p.pop();
// 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
}
/// 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)),
}
}
None => Err(format!("could not find terminfo entry for {:?}", term)),
}
}
#[test]
#[ignore(reason = "buildbots don't have ncurses installed and I can't mock everything I need")]
fn test_get_dbpath_for_term() {
@ -109,11 +97,3 @@ fn test_get_dbpath_for_term() {
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());
}

View File

@ -17,9 +17,9 @@ extern crate libc;
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 @@ impl<T: Write+Send+'static> WinConsole<T> {
/// 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 @@ impl<T: Write+Send+'static> WinConsole<T> {
bg = color::BLACK;
}
}
Some(box WinConsole {
Ok(WinConsole {
buf: out,
def_foreground: fg,
def_background: bg,
@ -148,7 +148,9 @@ impl<T: Write> Write for WinConsole<T> {
}
}
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 @@ impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
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 @@ impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
}
}
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 @@ impl<T: Write+Send+'static> Terminal<T> for WinConsole<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
}
}

View File

@ -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),
}