#[macro_use]
extern crate lazy_static;
use std::collections::HashSet;
use std::env;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str;
pub mod cap;
#[macro_use]
mod print;
pub use self::print::{CapError, Param, ToParamFromInt, ToParamFromStr, Vars,
tparm, tputs};
use self::cap::{Cap, ICap, CapName, UserDef};
#[derive(Clone, Debug)]
pub struct Desc {
names: Vec<String>,
bools: Vec<bool>,
nums: Vec<u16>,
strings: Vec<Vec<u8>>,
ext: Vec<ICap>,
}
impl Desc {
pub fn file(term_name: &str) -> Result<File, DescError> {
fn path_to_root(path: &str) -> PathBuf {
FS_ROOT.join(path)
}
if term_name.is_empty() {
return Err(name_error(term_name));
}
match Path::new(term_name).file_name() {
Some(fname) if fname == Path::new(term_name).as_os_str() => (),
_ => return Err(name_error(term_name)),
}
let first_char =
term_name.chars().next().expected("non-empty term_name");
let first_hex = format!("{:x}", first_char as usize);
let first_char = first_char.to_string();
let d1 = to_path(env::var("TERMINFO").ok());
let d2 = to_path(env::home_dir()).map(|d| d.join(".terminfo"));
let d3 = to_paths(env::var("TERMINFO_DIRS").ok());
let d3 = d3.into_iter().map(|d| if d.as_os_str().is_empty() {
path_to_root("usr/share/terminfo")
} else {
d
});
let d4 = vec![
path_to_root("etc/terminfo"),
path_to_root("lib/terminfo"),
path_to_root("usr/share/terminfo"),
];
let ds = d1.into_iter()
.chain(d2.into_iter())
.chain(d3)
.chain(d4.into_iter());
for d in ds {
if let Ok(f) = File::open(d.join(&first_char).join(term_name)) {
return Ok(f);
}
if let Ok(f) = File::open(d.join(&first_hex).join(term_name)) {
return Ok(f);
}
}
Err(absent_error(term_name))
}
pub fn parse(r: &mut Read) -> Result<Desc, DescError> {
let mut r = &mut AlignReader::new(r);
let header = r.read_words(6)?;
if header[0] != 282 {
return Err(parse_error("wrong magic number"));
}
let name_sz = header[1] as usize;
if name_sz == 0 {
return Err(parse_error("zero-length names"));
}
let name_buf = r.read_bytes(name_sz)?;
if name_buf[name_sz - 1] != 0 {
return Err(parse_error("names are not null-terminated"));
}
let names = str::from_utf8(&name_buf[0..name_sz - 1])?;
let names: Vec<_> = names.split('|').map(str::to_owned).collect();
let bools_num = header[2] as usize;
if bools_num > cap::NUM_BOOLS {
return Err(parse_error("too many boolean flags"));
}
let bools = Desc::read_bools(r, bools_num)?;
let ints_num = header[3] as usize;
if ints_num > cap::NUM_INTS {
return Err(parse_error("too many numbers"));
}
let nums = r.read_words(ints_num)?;
let strings_num = header[4] as usize;
let string_sz = header[5] as usize;
if strings_num > cap::NUM_STRS {
return Err(parse_error("too many strings"));
}
let offsets = r.read_words(strings_num)?;
let table = r.read_bytes(string_sz)?;
let strings = Desc::read_strs(&[&offsets], &table)?
.pop()
.expected("read_strs with length 1");
Ok(Desc {
names,
bools,
nums,
strings,
ext: Desc::parse_user(r)?,
})
}
pub fn current() -> &'static Desc {
&*CURRENT
}
fn parse_user(r: &mut AlignReader) -> Result<Vec<ICap>, DescError> {
let ext_header = r.read_words(5);
if let Err(e) = ext_header {
return match e.kind() {
io::ErrorKind::UnexpectedEof => Ok(Vec::new()),
_ => Err(DescError::from(e)),
};
}
let ext_header = ext_header.expected("Ok() ext_header");
let mut ext_bools = Desc::read_bools(r, ext_header[0] as usize)?;
let mut ext_nums = r.read_words(ext_header[1] as usize)?;
let ext_offs = r.read_words(ext_header[2] as usize)?;
let ext_name_offs =
r.read_words(ext_bools.len() + ext_nums.len() + ext_offs.len())?;
let ext_table = r.read_bytes(ext_header[4] as usize)?;
let mut ext_data =
Desc::read_strs(&[&ext_offs, &ext_name_offs], &ext_table)?;
let mut ext_names = ext_data.pop().expected("ext_data.len() == 2");
let mut ext_strs = ext_data.pop().expected("ext_data.len() == 2");
let mut ext = Vec::new();
ext_strs.reverse();
for val in ext_strs {
let name = &ext_names
.pop()
.expected("names.len == (strs + nums + bools).len");
let name = UserDef(str::from_utf8(name)?.to_owned());
ext.push(ICap::Str(CapName::U(name), val));
}
ext_nums.reverse();
for val in ext_nums {
let name = &ext_names
.pop()
.expected("names.len == (strs + nums + bools).len");
let name = UserDef(str::from_utf8(name)?.to_owned());
ext.push(ICap::Num(CapName::U(name), val));
}
ext_bools.reverse();
for val in ext_bools {
let name = &ext_names
.pop()
.expected("names.len == (strs + nums + bools).len");
let name = UserDef(str::from_utf8(name)?.to_owned());
ext.push(ICap::Bool(CapName::U(name), val));
}
Ok(ext)
}
fn read_bools(r: &mut AlignReader, n: usize) -> io::Result<Vec<bool>> {
let buf = r.read_bytes(n)?;
Ok(buf.into_iter().map(|b| !(b == 0)).collect())
}
fn read_strs(
offsets: &[&[u16]],
table: &[u8],
) -> Result<Vec<Vec<Vec<u8>>>, DescError> {
let mut strs = Vec::new();
let mut start = 0;
for &offs in offsets {
let mut len = 0;
let mut strings = Vec::new();
for &pos in offs {
let pos = pos as usize + start;
if pos == 0xffff || pos == 0xfffe {
strings.push(Vec::new());
} else if pos >= table.len() {
return Err(parse_error("invalid string offset"));
} else {
match table[pos..].iter().position(|&b| b == 0) {
None => {
return Err(parse_error("unterminated string"));
}
Some(end) => {
len += end + 1;
strings.push(table[pos..pos + end].to_vec());
}
}
}
}
start += len;
strs.push(strings);
}
Ok(strs)
}
pub fn names(&self) -> &[String] {
&self.names
}
pub fn get_bool_ext(&self, name: &UserDef) -> bool {
for ecap in self.ext.iter().rev() {
match *ecap {
ICap::Bool(CapName::U(ref n), v) if n == name => {
return v;
}
_ => (),
}
}
false
}
pub fn bool_exts(&self) -> Vec<&UserDef> {
let mut names = HashSet::new();
let mut exts: Vec<&UserDef> = Vec::new();
for ecap in self.ext.iter().rev() {
match *ecap {
ICap::Bool(CapName::U(ref n), _) => {
if !names.contains(n.name()) {
exts.push(n);
names.insert(n.name());
}
}
_ => (),
}
}
exts
}
pub fn get_num_ext(&self, name: &UserDef) -> u16 {
for ecap in self.ext.iter().rev() {
match *ecap {
ICap::Num(CapName::U(ref n), v) if n == name => {
return v;
}
_ => (),
}
}
0xffff
}
pub fn num_exts(&self) -> Vec<&UserDef> {
let mut names = HashSet::new();
let mut exts: Vec<&UserDef> = Vec::new();
for ecap in self.ext.iter().rev() {
match *ecap {
ICap::Num(CapName::U(ref n), _) => {
if !names.contains(n.name()) {
exts.push(n);
names.insert(n.name());
}
}
_ => (),
}
}
exts
}
pub fn get_str_ext(&self, name: &UserDef) -> &[u8] {
for ecap in self.ext.iter().rev() {
match *ecap {
ICap::Str(CapName::U(ref n), ref v) if n == name => {
return v;
}
_ => (),
}
}
&[]
}
pub fn str_exts(&self) -> Vec<&UserDef> {
let mut names = HashSet::new();
let mut exts: Vec<&UserDef> = Vec::new();
for ecap in self.ext.iter().rev() {
match *ecap {
ICap::Str(CapName::U(ref n), _) => {
if !names.contains(n.name()) {
exts.push(n);
names.insert(n.name());
}
}
_ => (),
}
}
exts
}
fn update(&mut self, caps: &[Cap]) {
fn add_val<T: Default>(vs: &mut Vec<T>, idx: usize, val: T) {
if idx >= vs.len() {
for _ in 0..(idx - vs.len()) {
vs.push(Default::default());
}
vs.push(val);
} else {
vs[idx] = val;
}
}
fn add_num_val(vs: &mut Vec<u16>, idx: usize, val: u16) {
if idx >= vs.len() {
for _ in 0..(idx - vs.len()) {
vs.push(0xffff);
}
vs.push(val);
} else {
vs[idx] = val;
}
}
use self::ICap::*;
for cap in caps {
match cap.0 {
Bool(CapName::P(idx), v) => {
add_val(&mut self.bools, idx, v);
}
Num(CapName::P(idx), v) => {
add_num_val(&mut self.nums, idx, v);
}
Str(CapName::P(idx), ref v) => {
add_val(&mut self.strings, idx, v.to_vec());
}
ref udc => self.ext.push(udc.clone()),
}
}
}
#[doc(hidden)]
pub fn from_literal(names: &[String], caps: &[Cap]) -> Desc {
let mut desc = Desc {
names: Vec::from(names),
bools: Vec::new(),
nums: Vec::new(),
strings: Vec::new(),
ext: Vec::new(),
};
desc.update(caps);
desc
}
}
#[macro_export]
macro_rules! desc {
(@acc [$($ns:expr),*] [$($ps:expr),*]) => {
$crate::Desc::from_literal(
&[$(::std::string::String::from($ns)),*][..],
&[$($ps),*][..]
);
};
(@acc [$($ns:expr),*] [$($ps:expr),*] $n:expr) => {
desc!(@acc [$($ns,)* $n] [$($ps),*]);
};
(@acc [$($ns:expr),*] [$($ps:expr),*] $n:expr, $($xs:tt)*) => {
desc!(@acc [$($ns,)* $n] [$($ps),*] $($xs)*);
};
(@acc [$($ns:expr),*] [$($ps:expr),*] $k:expr => $v:expr) => {
desc!(@acc [$($ns),*] [$($ps,)* ($k, $v).into()]);
};
(@acc [$($ns:expr),*] [$($ps:expr),*] $k:expr => $v:expr, $($xs:tt)*) => {
desc!(@acc [$($ns),*] [$($ps,)* ($k, $v).into()] $($xs)*);
};
($n:expr) => { desc!(@acc [$n] []); };
($n:expr, $($xs:tt)*) => { desc!(@acc [$n] [] $($xs)*); };
($k:expr => $v:expr) => { desc!(@acc [] [($k, $v).into()]); };
($k:expr => $v:expr, $($xs:tt)*) => {
desc!(@acc [] [($k, $v).into()] $($xs)*);
};
}
struct AlignReader<'a> {
r: &'a mut Read,
n: usize,
}
impl<'a> AlignReader<'a> {
fn new(r: &mut Read) -> AlignReader {
AlignReader { r, n: 0 }
}
fn align(&mut self) -> io::Result<()> {
if self.n % 2 != 0 {
self.read_bytes(1)?;
}
Ok(())
}
fn read_bytes(&mut self, n: usize) -> io::Result<Vec<u8>> {
let mut buf = Vec::with_capacity(n);
if self.r.take(n as u64).read_to_end(&mut buf)? < n {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
));
}
self.n += n;
Ok(buf)
}
fn read_words(&mut self, n: usize) -> io::Result<Vec<u16>> {
self.align()?;
let mut buf_16: Vec<u16> = vec![0; n];
let mut buf_8 = unsafe {
::std::slice::from_raw_parts_mut(
buf_16.as_mut_ptr() as *mut u8,
buf_16.len() * 2,
)
};
self.r.read_exact(&mut buf_8)?;
if cfg!(target_endian = "big") {
for w in &mut buf_16 {
*w = w.swap_bytes();
}
}
self.n += n * 2;
Ok(buf_16)
}
}
fn to_path<T: Into<PathBuf>>(var: Option<T>) -> Option<PathBuf> {
match var {
Some(d) => {
let d = d.into();
if d.as_os_str().is_empty() {
None
} else {
Some(d)
}
}
None => None,
}
}
fn to_paths(var: Option<String>) -> Vec<PathBuf> {
match var {
None => Vec::new(),
Some(ref d) if d.is_empty() => Vec::new(),
Some(d) => env::split_paths(&d).collect(),
}
}
lazy_static! {
static ref CURRENT: Desc = {
env::var("TERM")
.ok()
.and_then(|t| if t.is_empty() { None } else { Some(t) })
.and_then(|t| Desc::file(&t).ok())
.and_then(|mut f| Desc::parse(&mut f).ok())
.unwrap_or(
desc![
"dumb", "80-column dumb tty",
cap::am => true,
cap::cols => 80,
cap::bel => "\x07",
cap::cr => "\r",
cap::cud1 => "\n",
cap::ind => "\n",
]
)
};
static ref FS_ROOT: PathBuf = {
if cfg!(target_os = "windows") {
let paths = to_paths(env::var("PATH").ok());
for p in paths {
let p = p.display().to_string();
if p.ends_with("\\usr\\local\\bin") {
let (p1, _) = p.split_at(p.len() - 14);
return PathBuf::from(p1);
}
}
PathBuf::new()
} else {
PathBuf::from("/")
}
};
}
#[derive(Debug)]
pub struct DescError {
inner: DescErrorImpl,
}
fn parse_error(msg: &str) -> DescError {
DescError { inner: DescErrorImpl::Parse(msg.to_owned()) }
}
fn absent_error(name: &str) -> DescError {
DescError { inner: DescErrorImpl::Absent(name.to_owned()) }
}
fn name_error(name: &str) -> DescError {
DescError { inner: DescErrorImpl::Name(name.to_owned()) }
}
#[derive(Debug)]
enum DescErrorImpl {
Io(io::Error),
Parse(String),
Absent(String),
Name(String),
}
impl ::std::fmt::Display for DescError {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use self::DescErrorImpl::*;
match self.inner {
Io(ref err) => err.fmt(f),
Parse(ref msg) => write!(f, "{}", msg),
Absent(ref name) => write!(f, "no description found for {}", name),
Name(ref name) => write!(f, "invalid terminal name '{}'", name),
}
}
}
impl ::std::error::Error for DescError {
fn description(&self) -> &str {
use self::DescErrorImpl::*;
match self.inner {
Io(ref err) => err.description(),
Parse(..) => "invalid terminfo description",
Absent(..) => "missing terminfo description",
Name(..) => "invalid terminal name",
}
}
fn cause(&self) -> Option<&::std::error::Error> {
use self::DescErrorImpl::*;
match self.inner {
Io(ref err) => Some(err),
_ => None,
}
}
}
impl From<io::Error> for DescError {
fn from(err: io::Error) -> DescError {
DescError { inner: DescErrorImpl::Io(err) }
}
}
impl From<str::Utf8Error> for DescError {
fn from(err: str::Utf8Error) -> DescError {
parse_error(&err.to_string())
}
}
#[cfg(test)]
mod tests;
trait Expectation<T> {
fn expected(self, msg: &str) -> T;
}
impl<T> Expectation<T> for Option<T> {
fn expected(self, msg: &str) -> T {
match self {
Some(val) => val,
_ => expectation_failed(msg),
}
}
}
impl<T, E> Expectation<T> for Result<T, E> {
fn expected(self, msg: &str) -> T {
match self {
Ok(val) => val,
_ => expectation_failed(msg),
}
}
}
#[inline(never)]
#[cold]
fn expectation_failed(msg: &str) -> ! {
panic!("expected {}", msg)
}