libterm: bring across changes from term
This brings across changes made to the term library to libterm. This includes removing instances or unwrap, fixing format string handling, and removing a TODO. This fix does not bring all changes across, as term now relies on cargo deps that cannot be brought into the rust build at this stage, but has attempted as best to cross port changes not relying on this. This notably limits extra functionality since implemented int he Terminal trait in Term. This is in partly in response to rust issue #29992.
This commit is contained in:
parent
2f95de3b3b
commit
0ee230a094
@ -20,7 +20,7 @@ use std::cell::{RefCell, Cell};
|
||||
use std::{cmp, error, fmt};
|
||||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
use term::{self, WriterWrapper};
|
||||
use term;
|
||||
|
||||
/// maximum number of lines we will print for each error; arbitrary.
|
||||
const MAX_LINES: usize = 6;
|
||||
@ -318,7 +318,7 @@ pub struct EmitterWriter {
|
||||
}
|
||||
|
||||
enum Destination {
|
||||
Terminal(Box<term::Terminal<WriterWrapper> + Send>),
|
||||
Terminal(Box<term::StderrTerminal>),
|
||||
Raw(Box<Write + Send>),
|
||||
}
|
||||
|
||||
@ -365,7 +365,7 @@ impl EmitterWriter {
|
||||
|
||||
fn print_maybe_styled(&mut self,
|
||||
args: fmt::Arguments,
|
||||
color: term::attr::Attr,
|
||||
color: term::Attr,
|
||||
print_newline_at_end: bool) -> io::Result<()> {
|
||||
match self.dst {
|
||||
Terminal(ref mut t) => {
|
||||
@ -408,13 +408,13 @@ impl EmitterWriter {
|
||||
try!(write!(&mut self.dst, "{} ", topic));
|
||||
}
|
||||
|
||||
try!(print_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
|
||||
try!(print_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
|
||||
"{}: ", lvl.to_string()));
|
||||
try!(print_maybe_styled!(self, term::attr::Bold, "{}", msg));
|
||||
try!(print_maybe_styled!(self, term::Attr::Bold, "{}", msg));
|
||||
|
||||
match code {
|
||||
Some(code) => {
|
||||
let style = term::attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
|
||||
let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
|
||||
try!(print_maybe_styled!(self, style, " [{}]", code.clone()));
|
||||
}
|
||||
None => ()
|
||||
@ -646,7 +646,7 @@ impl EmitterWriter {
|
||||
s.pop();
|
||||
}
|
||||
|
||||
try!(println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
|
||||
try!(println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
|
||||
"{}", s));
|
||||
}
|
||||
}
|
||||
@ -719,7 +719,7 @@ impl EmitterWriter {
|
||||
}
|
||||
}
|
||||
s.push('^');
|
||||
println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
|
||||
println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
|
||||
"{}", s)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
@ -11,9 +11,9 @@
|
||||
//! Terminal formatting library.
|
||||
//!
|
||||
//! This crate provides the `Terminal` trait, which abstracts over an [ANSI
|
||||
//! Terminal][ansi] to provide color printing, among other things. There are two implementations,
|
||||
//! the `TerminfoTerminal`, which uses control characters from a
|
||||
//! [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
|
||||
//! Terminal][ansi] to provide color printing, among other things. There are two
|
||||
//! implementations, the `TerminfoTerminal`, which uses control characters from
|
||||
//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
|
||||
//! API][win].
|
||||
//!
|
||||
//! # Examples
|
||||
@ -21,19 +21,18 @@
|
||||
//! ```no_run
|
||||
//! # #![feature(rustc_private)]
|
||||
//! extern crate term;
|
||||
//!
|
||||
//! use std::io::prelude::*;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut t = term::stdout().unwrap();
|
||||
//!
|
||||
//! t.fg(term::color::GREEN).unwrap();
|
||||
//! (write!(t, "hello, ")).unwrap();
|
||||
//! write!(t, "hello, ").unwrap();
|
||||
//!
|
||||
//! t.fg(term::color::RED).unwrap();
|
||||
//! (writeln!(t, "world!")).unwrap();
|
||||
//! writeln!(t, "world!").unwrap();
|
||||
//!
|
||||
//! t.reset().unwrap();
|
||||
//! assert!(t.reset().unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
@ -58,84 +57,60 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#![feature(box_syntax)]
|
||||
#![feature(rustc_private)]
|
||||
#![feature(staged_api)]
|
||||
#![feature(str_char)]
|
||||
#![feature(vec_push_all)]
|
||||
#![cfg_attr(windows, feature(libc))]
|
||||
// Handle rustfmt skips
|
||||
#![feature(custom_attribute)]
|
||||
#![allow(unused_attributes)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use std::io::prelude::*;
|
||||
|
||||
pub use terminfo::TerminfoTerminal;
|
||||
#[cfg(windows)]
|
||||
pub use win::WinConsole;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
use std::io::{self, Stdout, Stderr};
|
||||
|
||||
pub mod terminfo;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win;
|
||||
|
||||
/// A hack to work around the fact that `Box<Write + Send>` does not
|
||||
/// currently implement `Write`.
|
||||
pub struct WriterWrapper {
|
||||
wrapped: Box<Write + Send>,
|
||||
}
|
||||
|
||||
impl Write for WriterWrapper {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.wrapped.write(buf)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.wrapped.flush()
|
||||
}
|
||||
}
|
||||
/// Alias for stdout terminals.
|
||||
pub type StdoutTerminal = Terminal<Output=Stdout> + Send;
|
||||
/// Alias for stderr terminals.
|
||||
pub type StderrTerminal = Terminal<Output=Stderr> + Send;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
/// Return a Terminal wrapping stdout, or None if a terminal couldn't be
|
||||
/// opened.
|
||||
pub fn stdout() -> Option<Box<Terminal<WriterWrapper> + Send>> {
|
||||
TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() })
|
||||
pub fn stdout() -> Option<Box<StdoutTerminal>> {
|
||||
TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Return a Terminal wrapping stdout, or None if a terminal couldn't be
|
||||
/// opened.
|
||||
pub fn stdout() -> Option<Box<Terminal<WriterWrapper> + Send>> {
|
||||
let ti = TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() });
|
||||
|
||||
match ti {
|
||||
Some(t) => Some(t),
|
||||
None => WinConsole::new(WriterWrapper { wrapped: box std::io::stdout() }),
|
||||
}
|
||||
pub fn stdout() -> Option<Box<StdoutTerminal>> {
|
||||
TerminfoTerminal::new(io::stdout())
|
||||
.map(|t| Box::new(t) as Box<StdoutTerminal>)
|
||||
.or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box<StdoutTerminal>))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
/// Return a Terminal wrapping stderr, or None if a terminal couldn't be
|
||||
/// opened.
|
||||
pub fn stderr() -> Option<Box<Terminal<WriterWrapper> + Send>> {
|
||||
TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() })
|
||||
pub fn stderr() -> Option<Box<StderrTerminal>> {
|
||||
TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box<StderrTerminal>)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Return a Terminal wrapping stderr, or None if a terminal couldn't be
|
||||
/// opened.
|
||||
pub fn stderr() -> Option<Box<Terminal<WriterWrapper> + Send>> {
|
||||
let ti = TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() });
|
||||
|
||||
match ti {
|
||||
Some(t) => Some(t),
|
||||
None => WinConsole::new(WriterWrapper { wrapped: box std::io::stderr() }),
|
||||
}
|
||||
pub fn stderr() -> Option<Box<StderrTerminal>> {
|
||||
TerminfoTerminal::new(io::stderr())
|
||||
.map(|t| Box::new(t) as Box<StderrTerminal>)
|
||||
.or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box<StderrTerminal>))
|
||||
}
|
||||
|
||||
|
||||
@ -164,43 +139,41 @@ pub mod color {
|
||||
pub const BRIGHT_WHITE: Color = 15;
|
||||
}
|
||||
|
||||
/// Terminal attributes
|
||||
pub mod attr {
|
||||
pub use self::Attr::*;
|
||||
|
||||
/// Terminal attributes for use with term.attr().
|
||||
///
|
||||
/// Most attributes can only be turned on and must be turned off with term.reset().
|
||||
/// The ones that can be turned off explicitly take a boolean value.
|
||||
/// Color is also represented as an attribute for convenience.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Attr {
|
||||
/// Bold (or possibly bright) mode
|
||||
Bold,
|
||||
/// Dim mode, also called faint or half-bright. Often not supported
|
||||
Dim,
|
||||
/// Italics mode. Often not supported
|
||||
Italic(bool),
|
||||
/// Underline mode
|
||||
Underline(bool),
|
||||
/// Blink mode
|
||||
Blink,
|
||||
/// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
|
||||
Standout(bool),
|
||||
/// Reverse mode, inverts the foreground and background colors
|
||||
Reverse,
|
||||
/// Secure mode, also called invis mode. Hides the printed text
|
||||
Secure,
|
||||
/// Convenience attribute to set the foreground color
|
||||
ForegroundColor(super::color::Color),
|
||||
/// Convenience attribute to set the background color
|
||||
BackgroundColor(super::color::Color),
|
||||
}
|
||||
/// Terminal attributes for use with term.attr().
|
||||
///
|
||||
/// Most attributes can only be turned on and must be turned off with term.reset().
|
||||
/// The ones that can be turned off explicitly take a boolean value.
|
||||
/// Color is also represented as an attribute for convenience.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Attr {
|
||||
/// Bold (or possibly bright) mode
|
||||
Bold,
|
||||
/// Dim mode, also called faint or half-bright. Often not supported
|
||||
Dim,
|
||||
/// Italics mode. Often not supported
|
||||
Italic(bool),
|
||||
/// Underline mode
|
||||
Underline(bool),
|
||||
/// Blink mode
|
||||
Blink,
|
||||
/// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
|
||||
Standout(bool),
|
||||
/// Reverse mode, inverts the foreground and background colors
|
||||
Reverse,
|
||||
/// Secure mode, also called invis mode. Hides the printed text
|
||||
Secure,
|
||||
/// Convenience attribute to set the foreground color
|
||||
ForegroundColor(color::Color),
|
||||
/// Convenience attribute to set the background color
|
||||
BackgroundColor(color::Color),
|
||||
}
|
||||
|
||||
/// A terminal with similar capabilities to an ANSI Terminal
|
||||
/// (foreground/background colors etc).
|
||||
pub trait Terminal<T: Write>: Write {
|
||||
pub trait Terminal: Write {
|
||||
/// The terminal's output writer type.
|
||||
type Output: Write;
|
||||
|
||||
/// Sets the foreground color to the given color.
|
||||
///
|
||||
/// If the color is a bright color, but the terminal only supports 8 colors,
|
||||
@ -222,24 +195,29 @@ pub trait Terminal<T: Write>: Write {
|
||||
/// Sets the given terminal attribute, if supported. Returns `Ok(true)`
|
||||
/// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if
|
||||
/// there was an I/O error.
|
||||
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool>;
|
||||
fn attr(&mut self, attr: Attr) -> io::Result<bool>;
|
||||
|
||||
/// Returns whether the given terminal attribute is supported.
|
||||
fn supports_attr(&self, attr: attr::Attr) -> bool;
|
||||
fn supports_attr(&self, attr: Attr) -> bool;
|
||||
|
||||
/// Resets all terminal attributes and color to the default.
|
||||
/// Returns `Ok()`.
|
||||
fn reset(&mut self) -> io::Result<()>;
|
||||
/// Resets all terminal attributes and colors to their defaults.
|
||||
///
|
||||
/// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
|
||||
/// was an I/O error.
|
||||
///
|
||||
/// *Note: This does not flush.*
|
||||
///
|
||||
/// That means the reset command may get buffered so, if you aren't planning on doing anything
|
||||
/// else that might flush stdout's buffer (e.g. writing a line of text), you should flush after
|
||||
/// calling reset.
|
||||
fn reset(&mut self) -> io::Result<bool>;
|
||||
|
||||
/// Gets an immutable reference to the stream inside
|
||||
fn get_ref<'a>(&'a self) -> &'a T;
|
||||
fn get_ref<'a>(&'a self) -> &'a Self::Output;
|
||||
|
||||
/// Gets a mutable reference to the stream inside
|
||||
fn get_mut<'a>(&'a mut self) -> &'a mut T;
|
||||
}
|
||||
fn get_mut<'a>(&'a mut self) -> &'a mut Self::Output;
|
||||
|
||||
/// A terminal which can be unwrapped.
|
||||
pub trait UnwrappableTerminal<T: Write>: Terminal<T> {
|
||||
/// Returns the contained stream, destroying the `Terminal`
|
||||
fn unwrap(self) -> T;
|
||||
fn into_inner(self) -> Self::Output where Self: Sized;
|
||||
}
|
||||
|
@ -12,16 +12,20 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
|
||||
use attr;
|
||||
use Attr;
|
||||
use color;
|
||||
use Terminal;
|
||||
use UnwrappableTerminal;
|
||||
use self::searcher::open;
|
||||
use self::searcher::get_dbpath_for_term;
|
||||
use self::parser::compiled::{parse, msys_terminfo};
|
||||
use self::parm::{expand, Number, Variables};
|
||||
use self::parm::{expand, Variables, Param};
|
||||
|
||||
|
||||
/// A parsed terminfo database entry.
|
||||
@ -37,6 +41,80 @@ pub struct TermInfo {
|
||||
pub strings: HashMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
/// A terminfo creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// TermUnset Indicates that the environment doesn't include enough information to find
|
||||
/// the terminfo entry.
|
||||
TermUnset,
|
||||
/// MalformedTerminfo indicates that parsing the terminfo entry failed.
|
||||
MalformedTerminfo(String),
|
||||
/// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
"failed to create TermInfo"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
use self::Error::*;
|
||||
match self {
|
||||
&IoError(ref e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
match self {
|
||||
&TermUnset => Ok(()),
|
||||
&MalformedTerminfo(ref e) => e.fmt(f),
|
||||
&IoError(ref e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TermInfo {
|
||||
/// Create a TermInfo based on current environment.
|
||||
pub fn from_env() -> Result<TermInfo, Error> {
|
||||
let term = match env::var("TERM") {
|
||||
Ok(name) => TermInfo::from_name(&name),
|
||||
Err(..) => return Err(Error::TermUnset),
|
||||
};
|
||||
|
||||
if term.is_err() && env::var("MSYSCON").ok().map_or(false, |s| "mintty.exe" == s) {
|
||||
// msys terminal
|
||||
Ok(msys_terminfo())
|
||||
} else {
|
||||
term
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a TermInfo for the named terminal.
|
||||
pub fn from_name(name: &str) -> Result<TermInfo, Error> {
|
||||
get_dbpath_for_term(name)
|
||||
.ok_or_else(|| {
|
||||
Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
|
||||
})
|
||||
.and_then(|p| TermInfo::from_path(&(*p)))
|
||||
}
|
||||
|
||||
/// Parse the given TermInfo.
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
|
||||
Self::_from_path(path.as_ref())
|
||||
}
|
||||
// Keep the metadata small
|
||||
fn _from_path(path: &Path) -> Result<TermInfo, Error> {
|
||||
let file = try!(File::open(path).map_err(|e| Error::IoError(e)));
|
||||
let mut reader = BufReader::new(file);
|
||||
parse(&mut reader, false).map_err(|e| Error::MalformedTerminfo(e))
|
||||
}
|
||||
}
|
||||
|
||||
pub mod searcher;
|
||||
|
||||
/// TermInfo format parsing.
|
||||
@ -47,21 +125,21 @@ pub mod parser {
|
||||
pub mod parm;
|
||||
|
||||
|
||||
fn cap_for_attr(attr: attr::Attr) -> &'static str {
|
||||
fn cap_for_attr(attr: Attr) -> &'static str {
|
||||
match attr {
|
||||
attr::Bold => "bold",
|
||||
attr::Dim => "dim",
|
||||
attr::Italic(true) => "sitm",
|
||||
attr::Italic(false) => "ritm",
|
||||
attr::Underline(true) => "smul",
|
||||
attr::Underline(false) => "rmul",
|
||||
attr::Blink => "blink",
|
||||
attr::Standout(true) => "smso",
|
||||
attr::Standout(false) => "rmso",
|
||||
attr::Reverse => "rev",
|
||||
attr::Secure => "invis",
|
||||
attr::ForegroundColor(_) => "setaf",
|
||||
attr::BackgroundColor(_) => "setab",
|
||||
Attr::Bold => "bold",
|
||||
Attr::Dim => "dim",
|
||||
Attr::Italic(true) => "sitm",
|
||||
Attr::Italic(false) => "ritm",
|
||||
Attr::Underline(true) => "smul",
|
||||
Attr::Underline(false) => "rmul",
|
||||
Attr::Blink => "blink",
|
||||
Attr::Standout(true) => "smso",
|
||||
Attr::Standout(false) => "rmso",
|
||||
Attr::Reverse => "rev",
|
||||
Attr::Secure => "invis",
|
||||
Attr::ForegroundColor(_) => "setaf",
|
||||
Attr::BackgroundColor(_) => "setab",
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,23 +148,15 @@ fn cap_for_attr(attr: attr::Attr) -> &'static str {
|
||||
pub struct TerminfoTerminal<T> {
|
||||
num_colors: u16,
|
||||
out: T,
|
||||
ti: Box<TermInfo>,
|
||||
ti: TermInfo,
|
||||
}
|
||||
|
||||
impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
|
||||
impl<T: Write+Send> Terminal for TerminfoTerminal<T> {
|
||||
type Output = T;
|
||||
fn fg(&mut self, color: color::Color) -> io::Result<bool> {
|
||||
let color = self.dim_if_necessary(color);
|
||||
if self.num_colors > color {
|
||||
let s = expand(self.ti
|
||||
.strings
|
||||
.get("setaf")
|
||||
.unwrap(),
|
||||
&[Number(color as isize)],
|
||||
&mut Variables::new());
|
||||
if s.is_ok() {
|
||||
try!(self.out.write_all(&s.unwrap()));
|
||||
return Ok(true);
|
||||
}
|
||||
return self.apply_cap("setaf", &[Param::Number(color as i32)]);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
@ -94,42 +164,22 @@ impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
|
||||
fn bg(&mut self, color: color::Color) -> io::Result<bool> {
|
||||
let color = self.dim_if_necessary(color);
|
||||
if self.num_colors > color {
|
||||
let s = expand(self.ti
|
||||
.strings
|
||||
.get("setab")
|
||||
.unwrap(),
|
||||
&[Number(color as isize)],
|
||||
&mut Variables::new());
|
||||
if s.is_ok() {
|
||||
try!(self.out.write_all(&s.unwrap()));
|
||||
return Ok(true);
|
||||
}
|
||||
return self.apply_cap("setab", &[Param::Number(color as i32)]);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
|
||||
fn attr(&mut self, attr: Attr) -> io::Result<bool> {
|
||||
match attr {
|
||||
attr::ForegroundColor(c) => self.fg(c),
|
||||
attr::BackgroundColor(c) => self.bg(c),
|
||||
_ => {
|
||||
let cap = cap_for_attr(attr);
|
||||
let parm = self.ti.strings.get(cap);
|
||||
if parm.is_some() {
|
||||
let s = expand(parm.unwrap(), &[], &mut Variables::new());
|
||||
if s.is_ok() {
|
||||
try!(self.out.write_all(&s.unwrap()));
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Attr::ForegroundColor(c) => self.fg(c),
|
||||
Attr::BackgroundColor(c) => self.bg(c),
|
||||
_ => self.apply_cap(cap_for_attr(attr), &[]),
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_attr(&self, attr: attr::Attr) -> bool {
|
||||
fn supports_attr(&self, attr: Attr) -> bool {
|
||||
match attr {
|
||||
attr::ForegroundColor(_) | attr::BackgroundColor(_) => self.num_colors > 0,
|
||||
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
|
||||
_ => {
|
||||
let cap = cap_for_attr(attr);
|
||||
self.ti.strings.get(cap).is_some()
|
||||
@ -137,22 +187,22 @@ impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> io::Result<()> {
|
||||
let mut cap = self.ti.strings.get("sgr0");
|
||||
if cap.is_none() {
|
||||
// are there any terminals that have color/attrs and not sgr0?
|
||||
// Try falling back to sgr, then op
|
||||
cap = self.ti.strings.get("sgr");
|
||||
if cap.is_none() {
|
||||
cap = self.ti.strings.get("op");
|
||||
fn reset(&mut self) -> io::Result<bool> {
|
||||
// are there any terminals that have color/attrs and not sgr0?
|
||||
// Try falling back to sgr, then op
|
||||
let cmd = match ["sg0", "sgr", "op"]
|
||||
.iter()
|
||||
.filter_map(|cap| self.ti.strings.get(*cap))
|
||||
.next() {
|
||||
Some(op) => {
|
||||
match expand(&op, &[], &mut Variables::new()) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
let s = cap.map_or(Err("can't find terminfo capability `sgr0`".to_owned()),
|
||||
|op| expand(op, &[], &mut Variables::new()));
|
||||
if s.is_ok() {
|
||||
return self.out.write_all(&s.unwrap());
|
||||
}
|
||||
Ok(())
|
||||
None => return Ok(false),
|
||||
};
|
||||
self.out.write_all(&cmd).and(Ok(true))
|
||||
}
|
||||
|
||||
fn get_ref<'a>(&'a self) -> &'a T {
|
||||
@ -162,64 +212,36 @@ impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
|
||||
fn get_mut<'a>(&'a mut self) -> &'a mut T {
|
||||
&mut self.out
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write+Send+'static> UnwrappableTerminal<T> for TerminfoTerminal<T> {
|
||||
fn unwrap(self) -> T {
|
||||
fn into_inner(self) -> T
|
||||
where Self: Sized
|
||||
{
|
||||
self.out
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write+Send+'static> TerminfoTerminal<T> {
|
||||
/// Returns `None` whenever the terminal cannot be created for some
|
||||
/// reason.
|
||||
pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
|
||||
let term = match env::var("TERM") {
|
||||
Ok(t) => t,
|
||||
Err(..) => {
|
||||
debug!("TERM environment variable not defined");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut file = match open(&term[..]) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
return match env::var("MSYSCON") {
|
||||
Ok(ref val) if &val[..] == "mintty.exe" => {
|
||||
// msys terminal
|
||||
Some(box TerminfoTerminal {
|
||||
out: out,
|
||||
ti: msys_terminfo(),
|
||||
num_colors: 8,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
debug!("error finding terminfo entry: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let ti = parse(&mut file, false);
|
||||
if ti.is_err() {
|
||||
debug!("error parsing terminfo entry: {:?}", ti.err().unwrap());
|
||||
return None;
|
||||
}
|
||||
|
||||
let inf = ti.unwrap();
|
||||
let nc = if inf.strings.get("setaf").is_some() && inf.strings.get("setab").is_some() {
|
||||
inf.numbers.get("colors").map_or(0, |&n| n)
|
||||
impl<T: Write+Send> TerminfoTerminal<T> {
|
||||
/// Create a new TerminfoTerminal with the given TermInfo and Write.
|
||||
pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
|
||||
let nc = if terminfo.strings.contains_key("setaf") &&
|
||||
terminfo.strings.contains_key("setab") {
|
||||
terminfo.numbers.get("colors").map_or(0, |&n| n)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Some(box TerminfoTerminal {
|
||||
TerminfoTerminal {
|
||||
out: out,
|
||||
ti: inf,
|
||||
ti: terminfo,
|
||||
num_colors: nc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new TerminfoTerminal for the current environment with the given Write.
|
||||
///
|
||||
/// Returns `None` when the terminfo cannot be found or parsed.
|
||||
pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
|
||||
TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
|
||||
}
|
||||
|
||||
fn dim_if_necessary(&self, color: color::Color) -> color::Color {
|
||||
@ -229,6 +251,18 @@ impl<T: Write+Send+'static> TerminfoTerminal<T> {
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
|
||||
match self.ti.strings.get(cmd) {
|
||||
Some(cmd) => {
|
||||
match expand(&cmd, params, &mut Variables::new()) {
|
||||
Ok(s) => self.out.write_all(&s).and(Ok(true)),
|
||||
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
|
||||
}
|
||||
}
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -10,15 +10,14 @@
|
||||
|
||||
//! Parameterized string expansion
|
||||
|
||||
pub use self::Param::*;
|
||||
use self::Param::*;
|
||||
use self::States::*;
|
||||
use self::FormatState::*;
|
||||
use self::FormatOp::*;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::mem::replace;
|
||||
|
||||
use std::iter::repeat;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum States {
|
||||
Nothing,
|
||||
Percent,
|
||||
@ -27,15 +26,15 @@ enum States {
|
||||
PushParam,
|
||||
CharConstant,
|
||||
CharClose,
|
||||
IntConstant(isize),
|
||||
IntConstant(i32),
|
||||
FormatPattern(Flags, FormatState),
|
||||
SeekIfElse(isize),
|
||||
SeekIfElsePercent(isize),
|
||||
SeekIfEnd(isize),
|
||||
SeekIfEndPercent(isize),
|
||||
SeekIfElse(usize),
|
||||
SeekIfElsePercent(usize),
|
||||
SeekIfEnd(usize),
|
||||
SeekIfEndPercent(usize),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, PartialEq, Clone)]
|
||||
enum FormatState {
|
||||
FormatStateFlags,
|
||||
FormatStateWidth,
|
||||
@ -47,7 +46,7 @@ enum FormatState {
|
||||
#[derive(Clone)]
|
||||
pub enum Param {
|
||||
Words(String),
|
||||
Number(isize),
|
||||
Number(i32),
|
||||
}
|
||||
|
||||
/// Container for static and dynamic variable arrays
|
||||
@ -94,11 +93,11 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
// Copy parameters into a local vector for mutability
|
||||
let mut mparams = [Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
|
||||
Number(0), Number(0), Number(0)];
|
||||
for (dst, src) in mparams.iter_mut().zip(params) {
|
||||
for (dst, src) in mparams.iter_mut().zip(params.iter()) {
|
||||
*dst = (*src).clone();
|
||||
}
|
||||
|
||||
for &c in cap {
|
||||
for &c in cap.iter() {
|
||||
let cur = c as char;
|
||||
let mut old_state = state;
|
||||
match state {
|
||||
@ -116,20 +115,13 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
state = Nothing
|
||||
}
|
||||
'c' => {
|
||||
if !stack.is_empty() {
|
||||
match stack.pop().unwrap() {
|
||||
// if c is 0, use 0200 (128) for ncurses compatibility
|
||||
Number(c) => {
|
||||
output.push(if c == 0 {
|
||||
128
|
||||
} else {
|
||||
c as u8
|
||||
})
|
||||
}
|
||||
_ => return Err("a non-char was used with %c".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
match stack.pop() {
|
||||
// if c is 0, use 0200 (128) for ncurses compatibility
|
||||
Some(Number(0)) => output.push(128u8),
|
||||
// Don't check bounds. ncurses just casts and truncates.
|
||||
Some(Number(c)) => output.push(c as u8),
|
||||
Some(_) => return Err("a non-char was used with %c".to_string()),
|
||||
None => return Err("stack is empty".to_string()),
|
||||
}
|
||||
}
|
||||
'p' => state = PushParam,
|
||||
@ -138,208 +130,89 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
'\'' => state = CharConstant,
|
||||
'{' => state = IntConstant(0),
|
||||
'l' => {
|
||||
if !stack.is_empty() {
|
||||
match stack.pop().unwrap() {
|
||||
Words(s) => stack.push(Number(s.len() as isize)),
|
||||
_ => return Err("a non-str was used with %l".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
match stack.pop() {
|
||||
Some(Words(s)) => stack.push(Number(s.len() as i32)),
|
||||
Some(_) => return Err("a non-str was used with %l".to_string()),
|
||||
None => return Err("stack is empty".to_string()),
|
||||
}
|
||||
}
|
||||
'+' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x + y)),
|
||||
_ => return Err("non-numbers on stack with +".to_owned()),
|
||||
'+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
|
||||
match (stack.pop(), stack.pop()) {
|
||||
(Some(Number(y)), Some(Number(x))) => {
|
||||
stack.push(Number(match cur {
|
||||
'+' => x + y,
|
||||
'-' => x - y,
|
||||
'*' => x * y,
|
||||
'/' => x / y,
|
||||
'|' => x | y,
|
||||
'&' => x & y,
|
||||
'^' => x ^ y,
|
||||
'm' => x % y,
|
||||
_ => unreachable!("All cases handled"),
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(format!("non-numbers on stack with {}", cur))
|
||||
}
|
||||
_ => return Err("stack is empty".to_string()),
|
||||
}
|
||||
}
|
||||
'-' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x - y)),
|
||||
_ => return Err("non-numbers on stack with -".to_owned()),
|
||||
'=' | '>' | '<' | 'A' | 'O' => {
|
||||
match (stack.pop(), stack.pop()) {
|
||||
(Some(Number(y)), Some(Number(x))) => {
|
||||
stack.push(Number(if match cur {
|
||||
'=' => x == y,
|
||||
'<' => x < y,
|
||||
'>' => x > y,
|
||||
'A' => x > 0 && y > 0,
|
||||
'O' => x > 0 || y > 0,
|
||||
_ => unreachable!(),
|
||||
} {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(format!("non-numbers on stack with {}", cur))
|
||||
}
|
||||
_ => return Err("stack is empty".to_string()),
|
||||
}
|
||||
}
|
||||
'*' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x * y)),
|
||||
_ => return Err("non-numbers on stack with *".to_owned()),
|
||||
'!' | '~' => {
|
||||
match stack.pop() {
|
||||
Some(Number(x)) => {
|
||||
stack.push(Number(match cur {
|
||||
'!' if x > 0 => 0,
|
||||
'!' => 1,
|
||||
'~' => !x,
|
||||
_ => unreachable!(),
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x / y)),
|
||||
_ => return Err("non-numbers on stack with /".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'm' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x % y)),
|
||||
_ => return Err("non-numbers on stack with %".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'&' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x & y)),
|
||||
_ => return Err("non-numbers on stack with &".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'|' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x | y)),
|
||||
_ => return Err("non-numbers on stack with |".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'^' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => stack.push(Number(x ^ y)),
|
||||
_ => return Err("non-numbers on stack with ^".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'=' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => {
|
||||
stack.push(Number(if x == y {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}))
|
||||
}
|
||||
_ => return Err("non-numbers on stack with =".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'>' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => {
|
||||
stack.push(Number(if x > y {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}))
|
||||
}
|
||||
_ => return Err("non-numbers on stack with >".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'<' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(y), Number(x)) => {
|
||||
stack.push(Number(if x < y {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}))
|
||||
}
|
||||
_ => return Err("non-numbers on stack with <".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'A' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(0), Number(_)) => stack.push(Number(0)),
|
||||
(Number(_), Number(0)) => stack.push(Number(0)),
|
||||
(Number(_), Number(_)) => stack.push(Number(1)),
|
||||
_ => return Err("non-numbers on stack with logical and".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'O' => {
|
||||
if stack.len() > 1 {
|
||||
match (stack.pop().unwrap(), stack.pop().unwrap()) {
|
||||
(Number(0), Number(0)) => stack.push(Number(0)),
|
||||
(Number(_), Number(_)) => stack.push(Number(1)),
|
||||
_ => return Err("non-numbers on stack with logical or".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'!' => {
|
||||
if !stack.is_empty() {
|
||||
match stack.pop().unwrap() {
|
||||
Number(0) => stack.push(Number(1)),
|
||||
Number(_) => stack.push(Number(0)),
|
||||
_ => return Err("non-number on stack with logical not".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
}
|
||||
}
|
||||
'~' => {
|
||||
if !stack.is_empty() {
|
||||
match stack.pop().unwrap() {
|
||||
Number(x) => stack.push(Number(!x)),
|
||||
_ => return Err("non-number on stack with %~".to_owned()),
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
|
||||
None => return Err("stack is empty".to_string()),
|
||||
}
|
||||
}
|
||||
'i' => {
|
||||
match (mparams[0].clone(), mparams[1].clone()) {
|
||||
(Number(x), Number(y)) => {
|
||||
match (&mparams[0], &mparams[1]) {
|
||||
(&Number(x), &Number(y)) => {
|
||||
mparams[0] = Number(x + 1);
|
||||
mparams[1] = Number(y + 1);
|
||||
}
|
||||
(_, _) => return Err("first two params not numbers with %i".to_owned()),
|
||||
(_, _) => {
|
||||
return Err("first two params not numbers with %i".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// printf-style support for %doxXs
|
||||
'd' | 'o' | 'x' | 'X' | 's' => {
|
||||
if !stack.is_empty() {
|
||||
if let Some(arg) = stack.pop() {
|
||||
let flags = Flags::new();
|
||||
let res = format(stack.pop().unwrap(), FormatOp::from_char(cur), flags);
|
||||
if res.is_err() {
|
||||
return res;
|
||||
}
|
||||
output.push_all(&res.unwrap())
|
||||
let res = try!(format(arg, FormatOp::from_char(cur), flags));
|
||||
output.extend(res.iter().map(|x| *x));
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
return Err("stack is empty".to_string());
|
||||
}
|
||||
}
|
||||
':' | '#' | ' ' | '.' | '0'...'9' => {
|
||||
@ -362,47 +235,45 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
// conditionals
|
||||
'?' => (),
|
||||
't' => {
|
||||
if !stack.is_empty() {
|
||||
match stack.pop().unwrap() {
|
||||
Number(0) => state = SeekIfElse(0),
|
||||
Number(_) => (),
|
||||
_ => return Err("non-number on stack with conditional".to_owned()),
|
||||
match stack.pop() {
|
||||
Some(Number(0)) => state = SeekIfElse(0),
|
||||
Some(Number(_)) => (),
|
||||
Some(_) => {
|
||||
return Err("non-number on stack with conditional".to_string())
|
||||
}
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
None => return Err("stack is empty".to_string()),
|
||||
}
|
||||
}
|
||||
'e' => state = SeekIfEnd(0),
|
||||
';' => (),
|
||||
|
||||
_ => return Err(format!("unrecognized format option {:?}", cur)),
|
||||
_ => return Err(format!("unrecognized format option {}", cur)),
|
||||
}
|
||||
}
|
||||
PushParam => {
|
||||
// params are 1-indexed
|
||||
stack.push(mparams[match cur.to_digit(10) {
|
||||
Some(d) => d as usize - 1,
|
||||
None => return Err("bad param number".to_owned()),
|
||||
None => return Err("bad param number".to_string()),
|
||||
}]
|
||||
.clone());
|
||||
}
|
||||
SetVar => {
|
||||
if cur >= 'A' && cur <= 'Z' {
|
||||
if !stack.is_empty() {
|
||||
if let Some(arg) = stack.pop() {
|
||||
let idx = (cur as u8) - b'A';
|
||||
vars.sta[idx as usize] = stack.pop().unwrap();
|
||||
vars.sta[idx as usize] = arg;
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
return Err("stack is empty".to_string());
|
||||
}
|
||||
} else if cur >= 'a' && cur <= 'z' {
|
||||
if !stack.is_empty() {
|
||||
if let Some(arg) = stack.pop() {
|
||||
let idx = (cur as u8) - b'a';
|
||||
vars.dyn[idx as usize] = stack.pop().unwrap();
|
||||
vars.dyn[idx as usize] = arg;
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
return Err("stack is empty".to_string());
|
||||
}
|
||||
} else {
|
||||
return Err("bad variable name in %P".to_owned());
|
||||
return Err("bad variable name in %P".to_string());
|
||||
}
|
||||
}
|
||||
GetVar => {
|
||||
@ -413,47 +284,45 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
let idx = (cur as u8) - b'a';
|
||||
stack.push(vars.dyn[idx as usize].clone());
|
||||
} else {
|
||||
return Err("bad variable name in %g".to_owned());
|
||||
return Err("bad variable name in %g".to_string());
|
||||
}
|
||||
}
|
||||
CharConstant => {
|
||||
stack.push(Number(c as isize));
|
||||
stack.push(Number(c as i32));
|
||||
state = CharClose;
|
||||
}
|
||||
CharClose => {
|
||||
if cur != '\'' {
|
||||
return Err("malformed character constant".to_owned());
|
||||
return Err("malformed character constant".to_string());
|
||||
}
|
||||
}
|
||||
IntConstant(i) => {
|
||||
match cur {
|
||||
'}' => {
|
||||
stack.push(Number(i));
|
||||
state = Nothing;
|
||||
if cur == '}' {
|
||||
stack.push(Number(i));
|
||||
state = Nothing;
|
||||
} else if let Some(digit) = cur.to_digit(10) {
|
||||
match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) {
|
||||
Some(i) => {
|
||||
state = IntConstant(i);
|
||||
old_state = Nothing;
|
||||
}
|
||||
None => return Err("int constant too large".to_string()),
|
||||
}
|
||||
'0'...'9' => {
|
||||
state = IntConstant(i * 10 + (cur as isize - '0' as isize));
|
||||
old_state = Nothing;
|
||||
}
|
||||
_ => return Err("bad isize constant".to_owned()),
|
||||
} else {
|
||||
return Err("bad int constant".to_string());
|
||||
}
|
||||
}
|
||||
FormatPattern(ref mut flags, ref mut fstate) => {
|
||||
old_state = Nothing;
|
||||
match (*fstate, cur) {
|
||||
(_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => {
|
||||
if !stack.is_empty() {
|
||||
let res = format(stack.pop().unwrap(),
|
||||
FormatOp::from_char(cur),
|
||||
*flags);
|
||||
if res.is_err() {
|
||||
return res;
|
||||
}
|
||||
output.push_all(&res.unwrap());
|
||||
if let Some(arg) = stack.pop() {
|
||||
let res = try!(format(arg, FormatOp::from_char(cur), *flags));
|
||||
output.extend(res.iter().map(|x| *x));
|
||||
// will cause state to go to Nothing
|
||||
old_state = FormatPattern(*flags, *fstate);
|
||||
} else {
|
||||
return Err("stack is empty".to_owned());
|
||||
return Err("stack is empty".to_string());
|
||||
}
|
||||
}
|
||||
(FormatStateFlags, '#') => {
|
||||
@ -479,7 +348,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
let old = flags.width;
|
||||
flags.width = flags.width * 10 + (cur as usize - '0' as usize);
|
||||
if flags.width < old {
|
||||
return Err("format width overflow".to_owned());
|
||||
return Err("format width overflow".to_string());
|
||||
}
|
||||
}
|
||||
(FormatStateWidth, '.') => {
|
||||
@ -489,10 +358,10 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
let old = flags.precision;
|
||||
flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
|
||||
if flags.precision < old {
|
||||
return Err("format precision overflow".to_owned());
|
||||
return Err("format precision overflow".to_string());
|
||||
}
|
||||
}
|
||||
_ => return Err("invalid format specifier".to_owned()),
|
||||
_ => return Err("invalid format specifier".to_string()),
|
||||
}
|
||||
}
|
||||
SeekIfElse(level) => {
|
||||
@ -543,7 +412,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, PartialEq, Clone)]
|
||||
struct Flags {
|
||||
width: usize,
|
||||
precision: usize,
|
||||
@ -600,61 +469,55 @@ impl FormatOp {
|
||||
fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
|
||||
let mut s = match val {
|
||||
Number(d) => {
|
||||
let s = match (op, flags.sign) {
|
||||
(FormatDigit, true) => format!("{:+}", d).into_bytes(),
|
||||
(FormatDigit, false) => format!("{}", d).into_bytes(),
|
||||
(FormatOctal, _) => format!("{:o}", d).into_bytes(),
|
||||
(FormatHex, _) => format!("{:x}", d).into_bytes(),
|
||||
(FormatHEX, _) => format!("{:X}", d).into_bytes(),
|
||||
(FormatString, _) => return Err("non-number on stack with %s".to_owned()),
|
||||
};
|
||||
let mut s: Vec<u8> = s.into_iter().collect();
|
||||
if flags.precision > s.len() {
|
||||
let mut s_ = Vec::with_capacity(flags.precision);
|
||||
let n = flags.precision - s.len();
|
||||
s_.extend(repeat(b'0').take(n));
|
||||
s_.extend(s);
|
||||
s = s_;
|
||||
}
|
||||
assert!(!s.is_empty(), "string conversion produced empty result");
|
||||
match op {
|
||||
FormatDigit => {
|
||||
if flags.space && !(s[0] == b'-' || s[0] == b'+') {
|
||||
s.insert(0, b' ');
|
||||
if flags.sign {
|
||||
format!("{:+01$}", d, flags.precision)
|
||||
} else if d < 0 {
|
||||
// C doesn't take sign into account in precision calculation.
|
||||
format!("{:01$}", d, flags.precision + 1)
|
||||
} else if flags.space {
|
||||
format!(" {:01$}", d, flags.precision)
|
||||
} else {
|
||||
format!("{:01$}", d, flags.precision)
|
||||
}
|
||||
}
|
||||
FormatOctal => {
|
||||
if flags.alternate && s[0] != b'0' {
|
||||
s.insert(0, b'0');
|
||||
if flags.alternate {
|
||||
// Leading octal zero counts against precision.
|
||||
format!("0{:01$o}", d, flags.precision.saturating_sub(1))
|
||||
} else {
|
||||
format!("{:01$o}", d, flags.precision)
|
||||
}
|
||||
}
|
||||
FormatHex => {
|
||||
if flags.alternate {
|
||||
let s_ = replace(&mut s, vec![b'0', b'x']);
|
||||
s.extend(s_);
|
||||
if flags.alternate && d != 0 {
|
||||
format!("0x{:01$x}", d, flags.precision)
|
||||
} else {
|
||||
format!("{:01$x}", d, flags.precision)
|
||||
}
|
||||
}
|
||||
FormatHEX => {
|
||||
s = s.to_ascii_uppercase();
|
||||
if flags.alternate {
|
||||
let s_ = replace(&mut s, vec![b'0', b'X']);
|
||||
s.extend(s_);
|
||||
if flags.alternate && d != 0 {
|
||||
format!("0X{:01$X}", d, flags.precision)
|
||||
} else {
|
||||
format!("{:01$X}", d, flags.precision)
|
||||
}
|
||||
}
|
||||
FormatString => unreachable!(),
|
||||
FormatString => return Err("non-number on stack with %s".to_string()),
|
||||
}
|
||||
s
|
||||
.into_bytes()
|
||||
}
|
||||
Words(s) => {
|
||||
match op {
|
||||
FormatString => {
|
||||
let mut s = s.as_bytes().to_vec();
|
||||
let mut s = s.into_bytes();
|
||||
if flags.precision > 0 && flags.precision < s.len() {
|
||||
s.truncate(flags.precision);
|
||||
}
|
||||
s
|
||||
}
|
||||
_ => return Err(format!("non-string on stack with %{:?}", op.to_char())),
|
||||
_ => return Err(format!("non-string on stack with %{}", op.to_char())),
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -665,7 +528,7 @@ fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
|
||||
} else {
|
||||
let mut s_ = Vec::with_capacity(flags.width);
|
||||
s_.extend(repeat(b' ').take(n));
|
||||
s_.extend(s);
|
||||
s_.extend(s.into_iter());
|
||||
s = s_;
|
||||
}
|
||||
}
|
||||
@ -673,8 +536,9 @@ fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{expand, Param, Words, Variables, Number};
|
||||
mod test {
|
||||
use super::{expand, Variables};
|
||||
use super::Param::{self, Words, Number};
|
||||
use std::result::Result::Ok;
|
||||
|
||||
#[test]
|
||||
@ -711,12 +575,12 @@ mod tests {
|
||||
vars: &mut Variables)
|
||||
-> Result<Vec<u8>, String> {
|
||||
let mut u8v: Vec<_> = fmt.bytes().collect();
|
||||
u8v.extend(cap.bytes());
|
||||
u8v.extend(cap.as_bytes().iter().map(|&b| b));
|
||||
expand(&u8v, params, vars)
|
||||
}
|
||||
|
||||
let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
|
||||
for &cap in &caps {
|
||||
for &cap in caps.iter() {
|
||||
let res = get_res("", cap, &[], vars);
|
||||
assert!(res.is_err(),
|
||||
"Op {} succeeded incorrectly with 0 stack entries",
|
||||
@ -733,7 +597,7 @@ mod tests {
|
||||
res.err().unwrap());
|
||||
}
|
||||
let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
|
||||
for &cap in &caps {
|
||||
for &cap in caps.iter() {
|
||||
let res = expand(cap.as_bytes(), &[], vars);
|
||||
assert!(res.is_err(),
|
||||
"Binop {} succeeded incorrectly with 0 stack entries",
|
||||
@ -744,7 +608,7 @@ mod tests {
|
||||
cap);
|
||||
let res = get_res("%{1}%{2}", cap, &[], vars);
|
||||
assert!(res.is_ok(),
|
||||
"Binop {} failed with 2 stack entries: {:?}",
|
||||
"Binop {} failed with 2 stack entries: {}",
|
||||
cap,
|
||||
res.err().unwrap());
|
||||
}
|
||||
@ -757,20 +621,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_comparison_ops() {
|
||||
let v = [('<', [1, 0, 0]), ('=', [0, 1, 0]), ('>', [0, 0, 1])];
|
||||
for &(op, bs) in &v {
|
||||
let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
|
||||
for &(op, bs) in v.iter() {
|
||||
let s = format!("%{{1}}%{{2}}%{}%d", op);
|
||||
let res = expand(s.as_bytes(), &[], &mut Variables::new());
|
||||
assert!(res.is_ok(), res.err().unwrap());
|
||||
assert_eq!(res.unwrap(), [b'0' + bs[0]]);
|
||||
assert_eq!(res.unwrap(), vec![b'0' + bs[0]]);
|
||||
let s = format!("%{{1}}%{{1}}%{}%d", op);
|
||||
let res = expand(s.as_bytes(), &[], &mut Variables::new());
|
||||
assert!(res.is_ok(), res.err().unwrap());
|
||||
assert_eq!(res.unwrap(), [b'0' + bs[1]]);
|
||||
assert_eq!(res.unwrap(), vec![b'0' + bs[1]]);
|
||||
let s = format!("%{{2}}%{{1}}%{}%d", op);
|
||||
let res = expand(s.as_bytes(), &[], &mut Variables::new());
|
||||
assert!(res.is_ok(), res.err().unwrap());
|
||||
assert_eq!(res.unwrap(), [b'0' + bs[2]]);
|
||||
assert_eq!(res.unwrap(), vec![b'0' + bs[2]]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -800,7 +664,7 @@ mod tests {
|
||||
Words("foo".to_string())],
|
||||
vars),
|
||||
Ok("foofoo ffo".bytes().collect::<Vec<_>>()));
|
||||
assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_owned())], vars),
|
||||
assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars),
|
||||
Ok("fo ".bytes().collect::<Vec<_>>()));
|
||||
|
||||
assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),
|
||||
|
@ -8,7 +8,7 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_upper_case_globals, missing_docs)]
|
||||
|
||||
//! ncurses-compatible compiled terminfo format parsing (term(5))
|
||||
|
||||
@ -20,7 +20,6 @@ use super::super::TermInfo;
|
||||
// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
|
||||
|
||||
#[rustfmt_skip]
|
||||
#[allow(missing_docs)]
|
||||
pub static boolfnames: &'static[&'static str] = &["auto_left_margin", "auto_right_margin",
|
||||
"no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
|
||||
"hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
|
||||
@ -34,14 +33,12 @@ pub static boolfnames: &'static[&'static str] = &["auto_left_margin", "auto_righ
|
||||
"return_does_clr_eol"];
|
||||
|
||||
#[rustfmt_skip]
|
||||
#[allow(missing_docs)]
|
||||
pub static boolnames: &'static[&'static str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
|
||||
"gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
|
||||
"nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
|
||||
"xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
|
||||
|
||||
#[rustfmt_skip]
|
||||
#[allow(missing_docs)]
|
||||
pub static numfnames: &'static[&'static str] = &[ "columns", "init_tabs", "lines",
|
||||
"lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
|
||||
"width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
|
||||
@ -53,14 +50,12 @@ pub static numfnames: &'static[&'static str] = &[ "columns", "init_tabs", "lines
|
||||
"new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
|
||||
|
||||
#[rustfmt_skip]
|
||||
#[allow(missing_docs)]
|
||||
pub static numnames: &'static[&'static str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
|
||||
"vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
|
||||
"spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
|
||||
"btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
|
||||
|
||||
#[rustfmt_skip]
|
||||
#[allow(missing_docs)]
|
||||
pub static stringfnames: &'static[&'static str] = &[ "back_tab", "bell", "carriage_return",
|
||||
"change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
|
||||
"column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
|
||||
@ -135,7 +130,6 @@ pub static stringfnames: &'static[&'static str] = &[ "back_tab", "bell", "carria
|
||||
"acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
|
||||
|
||||
#[rustfmt_skip]
|
||||
#[allow(missing_docs)]
|
||||
pub static stringnames: &'static[&'static str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
|
||||
"_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
|
||||
"ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
|
||||
@ -170,114 +164,132 @@ pub static stringnames: &'static[&'static str] = &[ "cbt", "_", "cr", "csr", "tb
|
||||
"OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
|
||||
"box1"];
|
||||
|
||||
/// Parse a compiled terminfo entry, using long capability names if `longnames` is true
|
||||
pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String> {
|
||||
macro_rules! try { ($e:expr) => (
|
||||
fn read_le_u16(r: &mut io::Read) -> io::Result<u16> {
|
||||
let mut b = [0; 2];
|
||||
let mut amt = 0;
|
||||
while amt < b.len() {
|
||||
match try!(r.read(&mut b[amt..])) {
|
||||
0 => return Err(io::Error::new(io::ErrorKind::Other, "end of file")),
|
||||
n => amt += n,
|
||||
}
|
||||
}
|
||||
Ok((b[0] as u16) | ((b[1] as u16) << 8))
|
||||
}
|
||||
|
||||
fn read_byte(r: &mut io::Read) -> io::Result<u8> {
|
||||
match r.bytes().next() {
|
||||
Some(s) => s,
|
||||
None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a compiled terminfo entry, using long capability names if `longnames`
|
||||
/// is true
|
||||
pub fn parse(file: &mut io::Read, longnames: bool) -> Result<TermInfo, String> {
|
||||
macro_rules! try( ($e:expr) => (
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(format!("{:?}", e))
|
||||
Err(e) => return Err(format!("{}", e))
|
||||
}
|
||||
) }
|
||||
) );
|
||||
|
||||
let bnames;
|
||||
let snames;
|
||||
let nnames;
|
||||
|
||||
if longnames {
|
||||
bnames = boolfnames;
|
||||
snames = stringfnames;
|
||||
nnames = numfnames;
|
||||
let (bnames, snames, nnames) = if longnames {
|
||||
(boolfnames, stringfnames, numfnames)
|
||||
} else {
|
||||
bnames = boolnames;
|
||||
snames = stringnames;
|
||||
nnames = numnames;
|
||||
}
|
||||
(boolnames, stringnames, numnames)
|
||||
};
|
||||
|
||||
// Check magic number
|
||||
let magic = try!(read_le_u16(file));
|
||||
if magic != 0x011A {
|
||||
return Err(format!("invalid magic number: expected {:x}, found {:x}",
|
||||
0x011A_usize,
|
||||
magic as usize));
|
||||
0x011A,
|
||||
magic));
|
||||
}
|
||||
|
||||
let names_bytes = try!(read_le_u16(file)) as isize;
|
||||
let bools_bytes = try!(read_le_u16(file)) as isize;
|
||||
let numbers_count = try!(read_le_u16(file)) as isize;
|
||||
let string_offsets_count = try!(read_le_u16(file)) as isize;
|
||||
let string_table_bytes = try!(read_le_u16(file)) as isize;
|
||||
|
||||
assert!(names_bytes > 0);
|
||||
|
||||
if (bools_bytes as usize) > boolnames.len() {
|
||||
return Err("incompatible file: more booleans than expected".to_owned());
|
||||
// According to the spec, these fields must be >= -1 where -1 means that the feature is not
|
||||
// supported. Using 0 instead of -1 works because we skip sections with length 0.
|
||||
macro_rules! read_nonneg {
|
||||
() => {{
|
||||
match try!(read_le_u16(file)) as i16 {
|
||||
n if n >= 0 => n as usize,
|
||||
-1 => 0,
|
||||
_ => return Err("incompatible file: length fields must be >= -1".to_string()),
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
if (numbers_count as usize) > numnames.len() {
|
||||
return Err("incompatible file: more numbers than expected".to_owned());
|
||||
let names_bytes = read_nonneg!();
|
||||
let bools_bytes = read_nonneg!();
|
||||
let numbers_count = read_nonneg!();
|
||||
let string_offsets_count = read_nonneg!();
|
||||
let string_table_bytes = read_nonneg!();
|
||||
|
||||
if names_bytes == 0 {
|
||||
return Err("incompatible file: names field must be at least 1 byte wide".to_string());
|
||||
}
|
||||
|
||||
if (string_offsets_count as usize) > stringnames.len() {
|
||||
return Err("incompatible file: more string offsets than expected".to_owned());
|
||||
if bools_bytes > boolnames.len() {
|
||||
return Err("incompatible file: more booleans than expected".to_string());
|
||||
}
|
||||
|
||||
if numbers_count > numnames.len() {
|
||||
return Err("incompatible file: more numbers than expected".to_string());
|
||||
}
|
||||
|
||||
if string_offsets_count > stringnames.len() {
|
||||
return Err("incompatible file: more string offsets than expected".to_string());
|
||||
}
|
||||
|
||||
// don't read NUL
|
||||
let bytes = try!(read_exact(file, names_bytes as usize - 1));
|
||||
let mut bytes = Vec::new();
|
||||
try!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
|
||||
let names_str = match String::from_utf8(bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Err("input not utf-8".to_owned()),
|
||||
Err(_) => return Err("input not utf-8".to_string()),
|
||||
};
|
||||
|
||||
let term_names: Vec<String> = names_str.split('|')
|
||||
.map(str::to_owned)
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
try!(read_byte(file)); // consume NUL
|
||||
|
||||
let mut bools_map = HashMap::new();
|
||||
if bools_bytes != 0 {
|
||||
for i in 0..bools_bytes {
|
||||
let b = try!(read_byte(file));
|
||||
if b == 1 {
|
||||
bools_map.insert(bnames[i as usize].to_owned(), true);
|
||||
}
|
||||
}
|
||||
// consume NUL
|
||||
if try!(read_byte(file)) != b'\0' {
|
||||
return Err("incompatible file: missing null terminator for names section".to_string());
|
||||
}
|
||||
|
||||
let bools_map: HashMap<String, bool> = try! {
|
||||
(0..bools_bytes).filter_map(|i| match read_byte(file) {
|
||||
Err(e) => Some(Err(e)),
|
||||
Ok(1) => Some(Ok((bnames[i].to_string(), true))),
|
||||
Ok(_) => None
|
||||
}).collect()
|
||||
};
|
||||
|
||||
if (bools_bytes + names_bytes) % 2 == 1 {
|
||||
try!(read_byte(file)); // compensate for padding
|
||||
}
|
||||
|
||||
let mut numbers_map = HashMap::new();
|
||||
if numbers_count != 0 {
|
||||
for i in 0..numbers_count {
|
||||
let n = try!(read_le_u16(file));
|
||||
if n != 0xFFFF {
|
||||
numbers_map.insert(nnames[i as usize].to_owned(), n);
|
||||
}
|
||||
}
|
||||
}
|
||||
let numbers_map: HashMap<String, u16> = try! {
|
||||
(0..numbers_count).filter_map(|i| match read_le_u16(file) {
|
||||
Ok(0xFFFF) => None,
|
||||
Ok(n) => Some(Ok((nnames[i].to_string(), n))),
|
||||
Err(e) => Some(Err(e))
|
||||
}).collect()
|
||||
};
|
||||
|
||||
let mut string_map = HashMap::new();
|
||||
let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
|
||||
let string_offsets: Vec<u16> = try!((0..string_offsets_count)
|
||||
.map(|_| read_le_u16(file))
|
||||
.collect());
|
||||
|
||||
if string_offsets_count != 0 {
|
||||
let mut string_offsets = Vec::with_capacity(10);
|
||||
for _ in 0..string_offsets_count {
|
||||
string_offsets.push(try!(read_le_u16(file)));
|
||||
}
|
||||
let mut string_table = Vec::new();
|
||||
try!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
|
||||
|
||||
let string_table = try!(read_exact(file, string_table_bytes as usize));
|
||||
|
||||
if string_table.len() != string_table_bytes as usize {
|
||||
return Err("error: hit EOF before end of string table".to_owned());
|
||||
}
|
||||
|
||||
for (i, v) in string_offsets.iter().enumerate() {
|
||||
let offset = *v;
|
||||
if offset == 0xFFFF {
|
||||
// non-entry
|
||||
continue;
|
||||
}
|
||||
try!(string_offsets.into_iter().enumerate().filter(|&(_, offset)| {
|
||||
// non-entry
|
||||
offset != 0xFFFF
|
||||
}).map(|(i, offset)| {
|
||||
let offset = offset as usize;
|
||||
|
||||
let name = if snames[i] == "_" {
|
||||
stringfnames[i]
|
||||
@ -288,30 +300,22 @@ pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String>
|
||||
if offset == 0xFFFE {
|
||||
// undocumented: FFFE indicates cap@, which means the capability is not present
|
||||
// unsure if the handling for this is correct
|
||||
string_map.insert(name.to_owned(), Vec::new());
|
||||
continue;
|
||||
return Ok((name.to_string(), Vec::new()));
|
||||
}
|
||||
|
||||
|
||||
// Find the offset of the NUL we want to go to
|
||||
let nulpos = string_table[offset as usize..string_table_bytes as usize]
|
||||
.iter()
|
||||
.position(|&b| b == 0);
|
||||
let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
|
||||
match nulpos {
|
||||
Some(len) => {
|
||||
string_map.insert(name.to_string(),
|
||||
string_table[offset as usize..(offset as usize + len)]
|
||||
.to_vec())
|
||||
}
|
||||
None => {
|
||||
return Err("invalid file: missing NUL in string_table".to_owned());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Some(len) => Ok((name.to_string(), string_table[offset..offset + len].to_vec())),
|
||||
None => Err("invalid file: missing NUL in string_table".to_string()),
|
||||
}
|
||||
}).collect())
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
|
||||
// And that's all there is to it
|
||||
Ok(box TermInfo {
|
||||
Ok(TermInfo {
|
||||
names: term_names,
|
||||
bools: bools_map,
|
||||
numbers: numbers_map,
|
||||
@ -319,42 +323,27 @@ pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String>
|
||||
})
|
||||
}
|
||||
|
||||
fn read_le_u16<R: Read + ?Sized>(r: &mut R) -> io::Result<u16> {
|
||||
let mut b = [0; 2];
|
||||
assert_eq!(try!(r.read(&mut b)), 2);
|
||||
Ok((b[0] as u16) | ((b[1] as u16) << 8))
|
||||
}
|
||||
|
||||
fn read_byte<R: Read + ?Sized>(r: &mut R) -> io::Result<u8> {
|
||||
let mut b = [0; 1];
|
||||
assert_eq!(try!(r.read(&mut b)), 1);
|
||||
Ok(b[0])
|
||||
}
|
||||
|
||||
fn read_exact<R: Read + ?Sized>(r: &mut R, sz: usize) -> io::Result<Vec<u8>> {
|
||||
let mut v = Vec::with_capacity(sz);
|
||||
try!(r.take(sz as u64).read_to_end(&mut v));
|
||||
assert_eq!(v.len(), sz);
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Create a dummy TermInfo struct for msys terminals
|
||||
pub fn msys_terminfo() -> Box<TermInfo> {
|
||||
pub fn msys_terminfo() -> TermInfo {
|
||||
let mut strings = HashMap::new();
|
||||
strings.insert("sgr0".to_owned(), b"\x1B[0m".to_vec());
|
||||
strings.insert("bold".to_owned(), b"\x1B[1m".to_vec());
|
||||
strings.insert("setaf".to_owned(), b"\x1B[3%p1%dm".to_vec());
|
||||
strings.insert("setab".to_owned(), b"\x1B[4%p1%dm".to_vec());
|
||||
box TermInfo {
|
||||
names: vec!["cygwin".to_owned()], // msys is a fork of an older cygwin version
|
||||
strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
|
||||
strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
|
||||
strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
|
||||
strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
|
||||
|
||||
let mut numbers = HashMap::new();
|
||||
numbers.insert("colors".to_string(), 8u16);
|
||||
|
||||
TermInfo {
|
||||
names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
|
||||
bools: HashMap::new(),
|
||||
numbers: HashMap::new(),
|
||||
numbers: numbers,
|
||||
strings: strings,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod test {
|
||||
|
||||
use super::{boolnames, boolfnames, numnames, numfnames, stringnames, stringfnames};
|
||||
|
||||
|
@ -13,29 +13,26 @@
|
||||
//! Does not support hashed database, only filesystem!
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Return path to database entry for `term`
|
||||
#[allow(deprecated)]
|
||||
pub fn get_dbpath_for_term(term: &str) -> Option<Box<PathBuf>> {
|
||||
if term.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let homedir = env::home_dir();
|
||||
|
||||
pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
|
||||
let mut dirs_to_search = Vec::new();
|
||||
let first_char = term.char_at(0);
|
||||
let first_char = match term.chars().next() {
|
||||
Some(c) => c,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// Find search directory
|
||||
match env::var_os("TERMINFO") {
|
||||
Some(dir) => dirs_to_search.push(PathBuf::from(dir)),
|
||||
None => {
|
||||
if homedir.is_some() {
|
||||
if let Some(mut homedir) = env::home_dir() {
|
||||
// ncurses compatibility;
|
||||
dirs_to_search.push(homedir.unwrap().join(".terminfo"))
|
||||
homedir.push(".terminfo");
|
||||
dirs_to_search.push(homedir)
|
||||
}
|
||||
match env::var("TERMINFO_DIRS") {
|
||||
Ok(dirs) => {
|
||||
@ -61,37 +58,28 @@ pub fn get_dbpath_for_term(term: &str) -> Option<Box<PathBuf>> {
|
||||
};
|
||||
|
||||
// Look for the terminal in all of the search directories
|
||||
for p in &dirs_to_search {
|
||||
if p.exists() {
|
||||
let f = first_char.to_string();
|
||||
let newp = p.join(&f).join(term);
|
||||
if newp.exists() {
|
||||
return Some(box newp);
|
||||
for mut p in dirs_to_search {
|
||||
if fs::metadata(&p).is_ok() {
|
||||
p.push(&first_char.to_string());
|
||||
p.push(&term);
|
||||
if fs::metadata(&p).is_ok() {
|
||||
return Some(p);
|
||||
}
|
||||
// on some installations the dir is named after the hex of the char (e.g. OS X)
|
||||
let f = format!("{:x}", first_char as usize);
|
||||
let newp = p.join(&f).join(term);
|
||||
if newp.exists() {
|
||||
return Some(box newp);
|
||||
p.pop();
|
||||
p.pop();
|
||||
|
||||
// on some installations the dir is named after the hex of the char
|
||||
// (e.g. OS X)
|
||||
p.push(&format!("{:x}", first_char as usize));
|
||||
p.push(term);
|
||||
if fs::metadata(&p).is_ok() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Return open file for `term`
|
||||
pub fn open(term: &str) -> Result<File, String> {
|
||||
match get_dbpath_for_term(term) {
|
||||
Some(x) => {
|
||||
match File::open(&*x) {
|
||||
Ok(file) => Ok(file),
|
||||
Err(e) => Err(format!("error opening file: {:?}", e)),
|
||||
}
|
||||
}
|
||||
None => Err(format!("could not find terminfo entry for {:?}", term)),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(reason = "buildbots don't have ncurses installed and I can't mock everything I need")]
|
||||
fn test_get_dbpath_for_term() {
|
||||
@ -109,11 +97,3 @@ fn test_get_dbpath_for_term() {
|
||||
assert!(x("screen") == "/usr/share/terminfo/s/screen");
|
||||
env::remove_var("TERMINFO_DIRS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(reason = "see test_get_dbpath_for_term")]
|
||||
fn test_open() {
|
||||
open("screen").unwrap();
|
||||
let t = open("nonexistent terminal that hopefully does not exist");
|
||||
assert!(t.is_err());
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ extern crate libc;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
|
||||
use attr;
|
||||
use Attr;
|
||||
use color;
|
||||
use {Terminal, UnwrappableTerminal};
|
||||
use Terminal;
|
||||
|
||||
/// A Terminal implementation which uses the Win32 Console API.
|
||||
pub struct WinConsole<T> {
|
||||
@ -115,7 +115,7 @@ impl<T: Write+Send+'static> WinConsole<T> {
|
||||
|
||||
/// Returns `None` whenever the terminal cannot be created for some
|
||||
/// reason.
|
||||
pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
|
||||
pub fn new(out: T) -> io::Result<WinConsole<T>> {
|
||||
let fg;
|
||||
let bg;
|
||||
unsafe {
|
||||
@ -128,7 +128,7 @@ impl<T: Write+Send+'static> WinConsole<T> {
|
||||
bg = color::BLACK;
|
||||
}
|
||||
}
|
||||
Some(box WinConsole {
|
||||
Ok(WinConsole {
|
||||
buf: out,
|
||||
def_foreground: fg,
|
||||
def_background: bg,
|
||||
@ -148,7 +148,9 @@ impl<T: Write> Write for WinConsole<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
|
||||
impl<T: Write+Send+'static> Terminal for WinConsole<T> {
|
||||
type Output = T;
|
||||
|
||||
fn fg(&mut self, color: color::Color) -> io::Result<bool> {
|
||||
self.foreground = color;
|
||||
self.apply();
|
||||
@ -163,14 +165,14 @@ impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
|
||||
fn attr(&mut self, attr: Attr) -> io::Result<bool> {
|
||||
match attr {
|
||||
attr::ForegroundColor(f) => {
|
||||
Attr::ForegroundColor(f) => {
|
||||
self.foreground = f;
|
||||
self.apply();
|
||||
Ok(true)
|
||||
}
|
||||
attr::BackgroundColor(b) => {
|
||||
Attr::BackgroundColor(b) => {
|
||||
self.background = b;
|
||||
self.apply();
|
||||
Ok(true)
|
||||
@ -179,21 +181,21 @@ impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_attr(&self, attr: attr::Attr) -> bool {
|
||||
fn supports_attr(&self, attr: Attr) -> bool {
|
||||
// it claims support for underscore and reverse video, but I can't get
|
||||
// it to do anything -cmr
|
||||
match attr {
|
||||
attr::ForegroundColor(_) | attr::BackgroundColor(_) => true,
|
||||
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> io::Result<()> {
|
||||
fn reset(&mut self) -> io::Result<bool> {
|
||||
self.foreground = self.def_foreground;
|
||||
self.background = self.def_background;
|
||||
self.apply();
|
||||
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn get_ref<'a>(&'a self) -> &'a T {
|
||||
@ -203,10 +205,10 @@ impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
|
||||
fn get_mut<'a>(&'a mut self) -> &'a mut T {
|
||||
&mut self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write+Send+'static> UnwrappableTerminal<T> for WinConsole<T> {
|
||||
fn unwrap(self) -> T {
|
||||
fn into_inner(self) -> T
|
||||
where Self: Sized
|
||||
{
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
@ -428,7 +428,7 @@ pub enum TestResult {
|
||||
unsafe impl Send for TestResult {}
|
||||
|
||||
enum OutputLocation<T> {
|
||||
Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
|
||||
Pretty(Box<term::StdoutTerminal>),
|
||||
Raw(T),
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user