use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use thiserror::Error;
use crate::entry::{Entry, ParseError};
#[derive(Debug, Error)]
pub enum LoadError {
#[error("entry could not be found")]
EntryNotFound,
#[error("reading entry failed: {0}")]
Io(#[from] io::Error),
#[error(transparent)]
Parse(#[from] ParseError),
}
pub struct SearchBuilder {
search_env: bool,
search_home: bool,
search_system: bool,
extra_paths: Vec<PathBuf>,
}
impl SearchBuilder {
pub fn search_env(&mut self, search: bool) -> &mut Self {
self.search_env = search;
self
}
pub fn search_home(&mut self, search: bool) -> &mut Self {
self.search_home = search;
self
}
pub fn search_system(&mut self, search: bool) -> &mut Self {
self.search_system = search;
self
}
pub fn path(&mut self, path: impl AsRef<Path>) -> &mut Self {
self.extra_paths.push(path.as_ref().to_path_buf());
self
}
pub fn build(self) -> Search {
let mut paths = Vec::from_iter(self.extra_paths.into_iter().rev());
if self.search_env {
if let Some(terminfo) = env::var_os("TERMINFO") {
paths.push(PathBuf::from(terminfo));
}
}
if self.search_home {
if let Some(mut home) = dirs::home_dir() {
home.push(".terminfo");
paths.push(home);
}
}
if self.search_env {
if let Some(terminfo_dirs) = env::var_os("TERMINFO_DIRS") {
paths.extend(env::split_paths(&terminfo_dirs))
}
}
if self.search_system {
if cfg!(target_os = "linux") {
paths.push(PathBuf::from("/etc/terminfo"));
paths.push(PathBuf::from("/lib/terminfo"));
}
if cfg!(unix) {
paths.push(PathBuf::from("/usr/share/terminfo"));
}
}
Search { paths: paths.into() }
}
}
#[derive(Clone, Debug)]
pub struct Search {
paths: Arc<[PathBuf]>
}
impl Search {
pub fn new() -> SearchBuilder {
SearchBuilder {
search_env: true,
search_home: true,
search_system: true,
extra_paths: Vec::new(),
}
}
pub fn standard() -> Search {
Search::new().build()
}
pub fn custom() -> SearchBuilder {
SearchBuilder {
search_env: false,
search_home: false,
search_system: false,
extra_paths: Vec::new(),
}
}
pub fn find(&self, term: impl AsRef<OsStr>) -> Option<PathBuf> {
let term = super::valid_terminal(term.as_ref())?;
self._find(term)
}
pub fn read(&self, term: impl AsRef<OsStr>) -> io::Result<Vec<u8>> {
self.find(term)
.ok_or(io::Error::from(io::ErrorKind::NotFound))
.and_then(fs::read)
}
pub fn load(&self, term: impl AsRef<OsStr>) -> Result<Entry, LoadError> {
let p = self.find(term).ok_or(LoadError::EntryNotFound)?;
let b = fs::read(p)?;
let e = Entry::parse(&b)?;
Ok(e)
}
fn _find(&self, term: &str) -> Option<PathBuf> {
let entry_name = format!("{}/{term}", &term[..1]);
let entry_name2 = format!("{:02x}/{term}", term.as_bytes()[0]);
for path in self.paths.iter() {
let p1 = path.join(&entry_name);
if p1.is_file() {
return Some(p1);
}
let p2 = path.join(&entry_name2);
if p2.is_file() {
return Some(p2);
}
}
None
}
}