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;
}
enum Destination {
- Terminal(Box<term::Terminal<WriterWrapper> + Send>),
+ Terminal(Box<term::StderrTerminal>),
Raw(Box<Write + Send>),
}
fn print_maybe_styled(&mut self,
args: fmt::Arguments,
- color: term::attr::Attr,
+ color: term::Attr,
print_newline_at_end: bool) -> io::Result<()> {
match self.dst {
Terminal(ref mut t) => {
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 => ()
s.pop();
}
- try!(println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
+ try!(println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s));
}
}
}
}
s.push('^');
- println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
+ println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s)
}
-// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
+// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
//! 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
//! ```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());
//! }
//! ```
//!
#![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>))
}
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,
/// 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;
}
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.
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.
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",
}
}
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)
}
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()
}
}
- 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 {
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 {
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),
+ }
+ }
}
//! 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,
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,
#[derive(Clone)]
pub enum Param {
Words(String),
- Number(isize),
+ Number(i32),
}
/// Container for static and dynamic variable arrays
// 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 {
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,
'\'' => 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());
- }
- }
- '+' => {
- 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());
- }
- }
- '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());
+ 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(if x == y {
- 1
- } else {
- 0
- }))
- }
- _ => 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());
- }
- }
- '>' => {
- 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()),
+ (Some(_), Some(_)) => {
+ return Err(format!("non-numbers on stack with {}", cur))
}
- } else {
- return Err("stack is empty".to_owned());
+ _ => 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(if x < y {
- 1
- } else {
- 0
- }))
- }
- _ => 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());
- }
- }
- '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()),
+ (Some(_), Some(_)) => {
+ return Err(format!("non-numbers on stack with {}", cur))
}
- } else {
- return Err("stack is empty".to_owned());
+ _ => return Err("stack is empty".to_string()),
}
}
- '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()),
+ '!' | '~' => {
+ 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.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' => {
// 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 => {
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;
- }
- '0'...'9' => {
- state = IntConstant(i * 10 + (cur as isize - '0' as isize));
- old_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()),
}
- _ => 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, '#') => {
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, '.') => {
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) => {
Ok(output)
}
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Copy, PartialEq, Clone)]
struct Flags {
width: usize,
precision: usize,
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())),
}
}
};
} 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_;
}
}
}
#[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]
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",
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",
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());
}
#[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]]);
}
}
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),
// 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))
// 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",
"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",
"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",
"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",
"OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
"box1"];
-/// Parse a compiled terminfo entry, using long capability names if `longnames` is true
-pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String> {
- macro_rules! try { ($e:expr) => (
+fn read_le_u16(r: &mut io::Read) -> io::Result<u16> {
+ let mut b = [0; 2];
+ let mut amt = 0;
+ while amt < b.len() {
+ match try!(r.read(&mut b[amt..])) {
+ 0 => return Err(io::Error::new(io::ErrorKind::Other, "end of file")),
+ n => amt += n,
+ }
+ }
+ Ok((b[0] as u16) | ((b[1] as u16) << 8))
+}
+
+fn read_byte(r: &mut io::Read) -> io::Result<u8> {
+ match r.bytes().next() {
+ Some(s) => s,
+ None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
+ }
+}
+
+/// Parse a compiled terminfo entry, using long capability names if `longnames`
+/// is true
+pub fn parse(file: &mut io::Read, longnames: bool) -> Result<TermInfo, String> {
+ macro_rules! try( ($e:expr) => (
match $e {
Ok(e) => e,
- Err(e) => return Err(format!("{:?}", e))
+ Err(e) => return Err(format!("{}", e))
}
- ) }
+ ) );
- let bnames;
- let snames;
- let nnames;
-
- if longnames {
- bnames = boolfnames;
- snames = stringfnames;
- nnames = numfnames;
+ let (bnames, snames, nnames) = if longnames {
+ (boolfnames, stringfnames, numfnames)
} else {
- bnames = boolnames;
- snames = stringnames;
- nnames = numnames;
- }
+ (boolnames, stringnames, numnames)
+ };
// Check magic number
let magic = try!(read_le_u16(file));
if magic != 0x011A {
return Err(format!("invalid magic number: expected {:x}, found {:x}",
- 0x011A_usize,
- magic as usize));
+ 0x011A,
+ magic));
}
- let names_bytes = try!(read_le_u16(file)) as isize;
- let bools_bytes = try!(read_le_u16(file)) as isize;
- let numbers_count = try!(read_le_u16(file)) as isize;
- let string_offsets_count = try!(read_le_u16(file)) as isize;
- let string_table_bytes = try!(read_le_u16(file)) as isize;
+ // According to the spec, these fields must be >= -1 where -1 means that the feature is not
+ // supported. Using 0 instead of -1 works because we skip sections with length 0.
+ macro_rules! read_nonneg {
+ () => {{
+ match try!(read_le_u16(file)) as i16 {
+ n if n >= 0 => n as usize,
+ -1 => 0,
+ _ => return Err("incompatible file: length fields must be >= -1".to_string()),
+ }
+ }}
+ }
+
+ let names_bytes = read_nonneg!();
+ let bools_bytes = read_nonneg!();
+ let numbers_count = read_nonneg!();
+ let string_offsets_count = read_nonneg!();
+ let string_table_bytes = read_nonneg!();
- assert!(names_bytes > 0);
+ if names_bytes == 0 {
+ return Err("incompatible file: names field must be at least 1 byte wide".to_string());
+ }
- if (bools_bytes as usize) > boolnames.len() {
- return Err("incompatible file: more booleans than expected".to_owned());
+ if bools_bytes > boolnames.len() {
+ return Err("incompatible file: more booleans than expected".to_string());
}
- if (numbers_count as usize) > numnames.len() {
- return Err("incompatible file: more numbers than expected".to_owned());
+ if numbers_count > numnames.len() {
+ return Err("incompatible file: more numbers than expected".to_string());
}
- if (string_offsets_count as usize) > stringnames.len() {
- return Err("incompatible file: more string offsets than expected".to_owned());
+ if string_offsets_count > stringnames.len() {
+ return Err("incompatible file: more string offsets than expected".to_string());
}
// don't read NUL
- let bytes = try!(read_exact(file, names_bytes as usize - 1));
+ let mut bytes = Vec::new();
+ try!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
let names_str = match String::from_utf8(bytes) {
Ok(s) => s,
- Err(_) => return Err("input not utf-8".to_owned()),
+ Err(_) => return Err("input not utf-8".to_string()),
};
let term_names: Vec<String> = names_str.split('|')
- .map(str::to_owned)
+ .map(|s| s.to_string())
.collect();
-
- try!(read_byte(file)); // consume NUL
-
- let mut bools_map = HashMap::new();
- if bools_bytes != 0 {
- for i in 0..bools_bytes {
- let b = try!(read_byte(file));
- if b == 1 {
- bools_map.insert(bnames[i as usize].to_owned(), true);
- }
- }
+ // consume NUL
+ if try!(read_byte(file)) != b'\0' {
+ return Err("incompatible file: missing null terminator for names section".to_string());
}
+ let bools_map: HashMap<String, bool> = try! {
+ (0..bools_bytes).filter_map(|i| match read_byte(file) {
+ Err(e) => Some(Err(e)),
+ Ok(1) => Some(Ok((bnames[i].to_string(), true))),
+ Ok(_) => None
+ }).collect()
+ };
+
if (bools_bytes + names_bytes) % 2 == 1 {
try!(read_byte(file)); // compensate for padding
}
- let mut numbers_map = HashMap::new();
- if numbers_count != 0 {
- for i in 0..numbers_count {
- let n = try!(read_le_u16(file));
- if n != 0xFFFF {
- numbers_map.insert(nnames[i as usize].to_owned(), n);
- }
- }
- }
-
- let mut string_map = HashMap::new();
-
- if string_offsets_count != 0 {
- let mut string_offsets = Vec::with_capacity(10);
- for _ in 0..string_offsets_count {
- string_offsets.push(try!(read_le_u16(file)));
- }
+ let numbers_map: HashMap<String, u16> = try! {
+ (0..numbers_count).filter_map(|i| match read_le_u16(file) {
+ Ok(0xFFFF) => None,
+ Ok(n) => Some(Ok((nnames[i].to_string(), n))),
+ Err(e) => Some(Err(e))
+ }).collect()
+ };
- let string_table = try!(read_exact(file, string_table_bytes as usize));
+ let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
+ let string_offsets: Vec<u16> = try!((0..string_offsets_count)
+ .map(|_| read_le_u16(file))
+ .collect());
- if string_table.len() != string_table_bytes as usize {
- return Err("error: hit EOF before end of string table".to_owned());
- }
+ let mut string_table = Vec::new();
+ try!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
- for (i, v) in string_offsets.iter().enumerate() {
- let offset = *v;
- if offset == 0xFFFF {
- // non-entry
- continue;
- }
+ try!(string_offsets.into_iter().enumerate().filter(|&(_, offset)| {
+ // non-entry
+ offset != 0xFFFF
+ }).map(|(i, offset)| {
+ let offset = offset as usize;
let name = if snames[i] == "_" {
stringfnames[i]
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,
})
}
-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};
//! 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) => {
};
// Look for the terminal in all of the search directories
- for p in &dirs_to_search {
- if p.exists() {
- let f = first_char.to_string();
- let newp = p.join(&f).join(term);
- if newp.exists() {
- return Some(box newp);
+ for mut p in dirs_to_search {
+ if fs::metadata(&p).is_ok() {
+ p.push(&first_char.to_string());
+ p.push(&term);
+ if fs::metadata(&p).is_ok() {
+ return Some(p);
}
- // on some installations the dir is named after the hex of the char (e.g. OS X)
- let f = format!("{:x}", first_char as usize);
- let newp = p.join(&f).join(term);
- if newp.exists() {
- return Some(box newp);
- }
- }
- }
- None
-}
+ p.pop();
+ p.pop();
-/// Return open file for `term`
-pub fn open(term: &str) -> Result<File, String> {
- match get_dbpath_for_term(term) {
- Some(x) => {
- match File::open(&*x) {
- Ok(file) => Ok(file),
- Err(e) => Err(format!("error opening file: {:?}", e)),
+ // on some installations the dir is named after the hex of the char
+ // (e.g. OS X)
+ p.push(&format!("{:x}", first_char as usize));
+ p.push(term);
+ if fs::metadata(&p).is_ok() {
+ return Some(p);
}
}
- None => Err(format!("could not find terminfo entry for {:?}", term)),
}
+ None
}
#[test]
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());
-}
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> {
/// 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 {
bg = color::BLACK;
}
}
- Some(box WinConsole {
+ Ok(WinConsole {
buf: out,
def_foreground: fg,
def_background: bg,
}
}
-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();
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)
}
}
- 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 {
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
}
}
unsafe impl Send for TestResult {}
enum OutputLocation<T> {
- Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
+ Pretty(Box<term::StdoutTerminal>),
Raw(T),
}