diff --git a/src/libsyntax/diagnostic.rs b/src/libsyntax/diagnostic.rs index 870dea02212..b854a2f2a0a 100644 --- a/src/libsyntax/diagnostic.rs +++ b/src/libsyntax/diagnostic.rs @@ -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 + Send>), + Terminal(Box), Raw(Box), } @@ -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) } diff --git a/src/libterm/lib.rs b/src/libterm/lib.rs index 478b0b846ed..69ad55d7908 100644 --- a/src/libterm/lib.rs +++ b/src/libterm/lib.rs @@ -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` does not -/// currently implement `Write`. -pub struct WriterWrapper { - wrapped: Box, -} - -impl Write for WriterWrapper { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.wrapped.write(buf) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - self.wrapped.flush() - } -} +/// Alias for stdout terminals. +pub type StdoutTerminal = Terminal + Send; +/// Alias for stderr terminals. +pub type StderrTerminal = Terminal + Send; #[cfg(not(windows))] /// Return a Terminal wrapping stdout, or None if a terminal couldn't be /// opened. -pub fn stdout() -> Option + Send>> { - TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() }) +pub fn stdout() -> Option> { + TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box) } #[cfg(windows)] /// Return a Terminal wrapping stdout, or None if a terminal couldn't be /// opened. -pub fn stdout() -> Option + 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> { + TerminfoTerminal::new(io::stdout()) + .map(|t| Box::new(t) as Box) + .or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box)) } #[cfg(not(windows))] /// Return a Terminal wrapping stderr, or None if a terminal couldn't be /// opened. -pub fn stderr() -> Option + Send>> { - TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() }) +pub fn stderr() -> Option> { + TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box) } #[cfg(windows)] /// Return a Terminal wrapping stderr, or None if a terminal couldn't be /// opened. -pub fn stderr() -> Option + 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> { + TerminfoTerminal::new(io::stderr()) + .map(|t| Box::new(t) as Box) + .or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box)) } @@ -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: 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: 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; + fn attr(&mut self, attr: Attr) -> io::Result; /// 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; /// 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: Terminal { /// Returns the contained stream, destroying the `Terminal` - fn unwrap(self) -> T; + fn into_inner(self) -> Self::Output where Self: Sized; } diff --git a/src/libterm/terminfo/mod.rs b/src/libterm/terminfo/mod.rs index 89c22e555ca..a4e5d00ee7a 100644 --- a/src/libterm/terminfo/mod.rs +++ b/src/libterm/terminfo/mod.rs @@ -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>, } +/// 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 { + 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 { + 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>(path: P) -> Result { + Self::_from_path(path.as_ref()) + } + // Keep the metadata small + fn _from_path(path: &Path) -> Result { + 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 { num_colors: u16, out: T, - ti: Box, + ti: TermInfo, } -impl Terminal for TerminfoTerminal { +impl Terminal for TerminfoTerminal { + type Output = T; fn fg(&mut self, color: color::Color) -> io::Result { 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 Terminal for TerminfoTerminal { fn bg(&mut self, color: color::Color) -> io::Result { 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 { + fn attr(&mut self, attr: Attr) -> io::Result { 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 Terminal for TerminfoTerminal { } } - 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 { + // 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 Terminal for TerminfoTerminal { fn get_mut<'a>(&'a mut self) -> &'a mut T { &mut self.out } -} -impl UnwrappableTerminal for TerminfoTerminal { - fn unwrap(self) -> T { + fn into_inner(self) -> T + where Self: Sized + { self.out } } -impl TerminfoTerminal { - /// Returns `None` whenever the terminal cannot be created for some - /// reason. - pub fn new(out: T) -> Option + 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 TerminfoTerminal { + /// Create a new TerminfoTerminal with the given TermInfo and Write. + pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal { + 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> { + 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 TerminfoTerminal { color } } + + fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result { + 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), + } + } } diff --git a/src/libterm/terminfo/parm.rs b/src/libterm/terminfo/parm.rs index 49db096ce73..aceaa0c10bc 100644 --- a/src/libterm/terminfo/parm.rs +++ b/src/libterm/terminfo/parm.rs @@ -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 Result { - 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 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 (), '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 { - 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 { @@ -489,10 +358,10 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result 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 Result, 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 = 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, 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, 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, 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::>())); - 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::>())); assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars), diff --git a/src/libterm/terminfo/parser/compiled.rs b/src/libterm/terminfo/parser/compiled.rs index 7c8d9983e78..558d35c2ae2 100644 --- a/src/libterm/terminfo/parser/compiled.rs +++ b/src/libterm/terminfo/parser/compiled.rs @@ -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, String> { - macro_rules! try { ($e:expr) => ( +fn read_le_u16(r: &mut io::Read) -> io::Result { + 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 { + 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 { + 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 = 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 = 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 = 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> = if string_offsets_count > 0 { + let string_offsets: Vec = 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, 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, String> }) } -fn read_le_u16(r: &mut R) -> io::Result { - 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: &mut R) -> io::Result { - let mut b = [0; 1]; - assert_eq!(try!(r.read(&mut b)), 1); - Ok(b[0]) -} - -fn read_exact(r: &mut R, sz: usize) -> io::Result> { - 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 { +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}; diff --git a/src/libterm/terminfo/searcher.rs b/src/libterm/terminfo/searcher.rs index 397e7aa2254..e869c508337 100644 --- a/src/libterm/terminfo/searcher.rs +++ b/src/libterm/terminfo/searcher.rs @@ -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> { - if term.is_empty() { - return None; - } - - let homedir = env::home_dir(); - +pub fn get_dbpath_for_term(term: &str) -> Option { 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> { }; // 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 { - 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()); -} diff --git a/src/libterm/win.rs b/src/libterm/win.rs index fa53d783194..2cb7018669d 100644 --- a/src/libterm/win.rs +++ b/src/libterm/win.rs @@ -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 { @@ -115,7 +115,7 @@ impl WinConsole { /// Returns `None` whenever the terminal cannot be created for some /// reason. - pub fn new(out: T) -> Option + Send + 'static>> { + pub fn new(out: T) -> io::Result> { let fg; let bg; unsafe { @@ -128,7 +128,7 @@ impl WinConsole { bg = color::BLACK; } } - Some(box WinConsole { + Ok(WinConsole { buf: out, def_foreground: fg, def_background: bg, @@ -148,7 +148,9 @@ impl Write for WinConsole { } } -impl Terminal for WinConsole { +impl Terminal for WinConsole { + type Output = T; + fn fg(&mut self, color: color::Color) -> io::Result { self.foreground = color; self.apply(); @@ -163,14 +165,14 @@ impl Terminal for WinConsole { Ok(true) } - fn attr(&mut self, attr: attr::Attr) -> io::Result { + fn attr(&mut self, attr: Attr) -> io::Result { 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 Terminal for WinConsole { } } - 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 { 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 Terminal for WinConsole { fn get_mut<'a>(&'a mut self) -> &'a mut T { &mut self.buf } -} -impl UnwrappableTerminal for WinConsole { - fn unwrap(self) -> T { + fn into_inner(self) -> T + where Self: Sized + { self.buf } } diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 125677dc932..71eddd80c74 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -428,7 +428,7 @@ pub enum TestResult { unsafe impl Send for TestResult {} enum OutputLocation { - Pretty(Box + Send>), + Pretty(Box), Raw(T), }