quinoa/src/terminfo.rs

116 lines
3.3 KiB
Rust

//! Support for automatically installing terminfo files.
use std::io::{Result, Write};
use std::os::fd::AsRawFd;
use std::path::Path;
use nix::unistd::{Gid, Uid};
#[derive(Clone, Copy)]
struct TerminfoPath<'a> {
path: &'a str,
term: &'a str,
first: char,
}
impl<'a> TerminfoPath<'a> {
pub fn new(path: &'a str, term: &'a str) -> Option<Self> {
let first = *term.as_bytes().iter().next()?;
let first = char::from_u32(first.into())?;
Some(Self { path, term, first })
}
pub fn try_each<T, F>(self, mut f: F) -> Result<Option<T>>
where
F: FnMut(&str) -> Result<Option<T>>,
{
let mut buf = String::new();
if cfg!(target_os = "macos") {
self.write_hex(&mut buf);
} else {
self.write_ltr(&mut buf);
}
f(&buf)
/*self.write_ltr(&mut buf);
if let Some(t) = f(&buf)? {
return Ok(Some(t));
}
self.write_hex(&mut buf);
f(&buf)*/
}
fn write_ltr(self, buf: &mut String) {
buf.clear();
buf.reserve_exact(self.path.len() + 1 + 1 + 1 + self.term.len());
buf.push_str(self.path);
buf.push_str("/");
buf.push(self.first);
buf.push_str("/");
buf.push_str(self.term);
}
fn write_hex(self, buf: &mut String) {
use std::fmt::Write;
buf.clear();
buf.reserve_exact(self.path.len() + 1 + 1 + 1 + self.term.len());
buf.push_str(self.path);
buf.push_str("/");
_ = write!(buf, "{:x}", self.first as u8);
buf.push_str("/");
buf.push_str(self.term);
}
}
pub async fn install_terminfo(
path: &str,
term: &str,
terminfo: &[u8],
owner: (Uid, Gid),
) -> Result<()> {
if let Some(helper) = TerminfoPath::new(path, term) {
tokio::task::block_in_place(move || {
helper
.try_each(move |path| {
if !Path::new(&path).exists() {
info!("Installing terminfo file for {:?} at {:?}", term, path);
let dir = &path[..path.len() - term.len() - 1];
crate::io_util::ignore_already_exists(crate::io_util::create_dir_owned(
dir, owner.0, owner.1,
))?;
let mut f = std::fs::File::create(&path)?;
nix::unistd::fchown(f.as_raw_fd(), Some(owner.0), Some(owner.1))?;
f.write_all(terminfo)?;
Ok(None)
} else {
Ok(Some(()))
}
})
.map(|_| ())
})
} else {
Ok(())
}
}
pub async fn read_terminfo(path: &str, term: &str) -> Result<Vec<u8>> {
if let Some(helper) = TerminfoPath::new(path, term) {
let o = tokio::task::block_in_place(move || {
helper.try_each(move |path| {
if Path::new(&path).exists() {
std::fs::read(&path).map(Some)
} else {
Ok(None)
}
})
})
.transpose();
if let Some(r) = o {
return r;
}
}
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("terminfo file not found in {:?} for {:?}", path, term),
))
}