use fnv::FnvHasher;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::hash::BuildHasherDefault;
use std::io::Read;
use std::path::{Path, PathBuf};
use crate::capability::{Capability, Value};
use crate::error::{self, Error};
use crate::names;
use crate::parser::compiled;
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Database {
name: String,
aliases: Vec<String>,
description: String,
inner: HashMap<String, Value, BuildHasherDefault<FnvHasher>>,
}
#[derive(Default, Debug)]
pub struct Builder {
name: Option<String>,
aliases: Vec<String>,
description: Option<String>,
inner: HashMap<String, Value, BuildHasherDefault<FnvHasher>>,
}
impl Builder {
pub fn build(self) -> Result<Database, ()> {
Ok(Database {
name: self.name.ok_or(())?,
aliases: self.aliases,
description: self.description.unwrap_or_default(),
inner: self.inner,
})
}
pub fn name<T: Into<String>>(&mut self, name: T) -> &mut Self {
self.name = Some(name.into());
self
}
pub fn aliases<T, I>(&mut self, iter: I) -> &mut Self
where
T: Into<String>,
I: IntoIterator<Item = T>,
{
self.aliases = iter.into_iter().map(|a| a.into()).collect();
self
}
pub fn description<T: Into<String>>(&mut self, description: T) -> &mut Self {
self.description = Some(description.into());
self
}
pub fn set<'a, C: Capability<'a>>(&'a mut self, value: C) -> &mut Self {
if !self.inner.contains_key(C::name()) {
if let Some(value) = C::into(value) {
self.inner.insert(C::name().into(), value);
}
}
self
}
pub fn raw<S: AsRef<str>, V: Into<Value>>(&mut self, name: S, value: V) -> &mut Self {
let name = name.as_ref();
let name = names::ALIASES.get(name).copied().unwrap_or(name);
if !self.inner.contains_key(name) {
self.inner.insert(name.into(), value.into());
}
self
}
}
impl Database {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> Builder {
Builder::default()
}
pub fn from_env() -> error::Result<Self> {
if let Ok(name) = env::var("TERM") {
Self::from_name(name)
} else {
Err(Error::NotFound)
}
}
pub fn from_name<N: AsRef<str>>(name: N) -> error::Result<Self> {
let name = name.as_ref();
let first = name.chars().next().ok_or(Error::NotFound)?;
let mut search = Vec::<PathBuf>::new();
#[allow(deprecated)]
if let Some(dir) = env::var_os("TERMINFO") {
search.push(dir.into());
} else if let Some(mut home) = std::env::home_dir() {
home.push(".terminfo");
search.push(home);
}
if let Ok(dirs) = env::var("TERMINFO_DIRS") {
for dir in dirs.split(':') {
search.push(dir.into());
}
}
if let Ok(prefix) = env::var("PREFIX") {
let path = Path::new(&prefix);
search.push(path.join("etc/terminfo"));
search.push(path.join("lib/terminfo"));
search.push(path.join("share/terminfo"));
}
search.push("/etc/terminfo".into());
search.push("/lib/terminfo".into());
search.push("/usr/share/terminfo".into());
search.push("/usr/local/share/terminfo".into());
search.push("/usr/local/share/site-terminfo".into());
search.push("/boot/system/data/terminfo".into());
for path in search {
if fs::metadata(&path).is_err() {
continue;
}
{
let mut path = path.clone();
path.push(first.to_string());
path.push(name);
if fs::metadata(&path).is_ok() {
return Self::from_path(path);
}
}
{
let mut path = path.clone();
path.push(format!("{:x}", first as usize));
path.push(name);
if fs::metadata(&path).is_ok() {
return Self::from_path(path);
}
}
}
Err(Error::NotFound)
}
pub fn from_path<P: AsRef<Path>>(path: P) -> error::Result<Self> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Self::from_buffer(buffer)
}
pub fn from_buffer<T: AsRef<[u8]>>(buffer: T) -> error::Result<Self> {
if let Ok((_, database)) = compiled::parse(buffer.as_ref()) {
Ok(database.into())
} else {
Err(Error::Parse)
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn aliases(&self) -> &[String] {
&self.aliases
}
pub fn description(&self) -> &str {
&self.description
}
pub fn get<'a, C: Capability<'a>>(&'a self) -> Option<C> {
C::from(self.inner.get(C::name()))
}
pub fn raw<S: AsRef<str>>(&self, name: S) -> Option<&Value> {
let name = name.as_ref();
let name = names::ALIASES.get(name).copied().unwrap_or(name);
self.inner.get(name)
}
}