#![deny(missing_docs)]
use kpathsea_sys::*;
use std::ffi::{CStr, CString};
use which::which;
pub type Result<T> = std::result::Result<T, &'static str>;
pub struct Kpaths(kpathsea);
unsafe impl Send for Kpaths {}
fn get_kpsewhich_path() -> Result<CString> {
let kpsewhich_path = which("kpsewhich").map_err(|_| "Error finding kpsewhich executable")?;
let kpsewhich_path_str = kpsewhich_path.to_string_lossy();
Ok(CString::new(kpsewhich_path_str.into_owned().as_str()).unwrap())
}
impl Kpaths {
pub fn new() -> Result<Self> {
let kpse = unsafe { kpathsea_new() };
let kpsewhich_path = get_kpsewhich_path()?;
unsafe { kpathsea_set_program_name(kpse, kpsewhich_path.as_ptr(), std::ptr::null()) }
Ok(Kpaths(kpse))
}
fn guess_format_from_filename(&self, filename: &str) -> kpse_file_format_type {
if !filename.contains('.') {
return kpse_file_format_type_kpse_tex_format;
}
for format_type in 0..kpse_file_format_type_kpse_last_format {
let format_info: &mut kpse_format_info_type =
unsafe { &mut (*self.0).format_info[format_type as usize] };
if format_info.type_.is_null() {
unsafe {
kpathsea_init_format(self.0, format_type as kpse_file_format_type);
}
}
let mut suffix_ptr = format_info.suffix;
while !suffix_ptr.is_null() && !unsafe { *suffix_ptr }.is_null() {
let suffix_cstr = unsafe { CStr::from_ptr(*suffix_ptr) };
let suffix = suffix_cstr.to_str().unwrap();
if filename.len() > suffix.len()
&& filename.get(filename.len() - suffix.len()..) == Some(suffix)
{
return format_type as kpse_file_format_type;
}
suffix_ptr = unsafe { suffix_ptr.offset(1) };
}
let mut alt_suffix_ptr = format_info.alt_suffix;
while !alt_suffix_ptr.is_null() && !unsafe { *alt_suffix_ptr }.is_null() {
let alt_suffix_cstr = unsafe { CStr::from_ptr(*alt_suffix_ptr) };
let alt_suffix = alt_suffix_cstr.to_str().unwrap();
if filename.get(filename.len() - alt_suffix.len()..) == Some(alt_suffix) {
return format_type as kpse_file_format_type;
}
alt_suffix_ptr = unsafe { alt_suffix_ptr.offset(1) };
}
}
kpse_file_format_type_kpse_tex_format
}
pub fn find_file(&self, name: &str) -> Option<String> {
let c_name = CString::new(name).unwrap();
let file_format_type = self.guess_format_from_filename(name);
let c_filename_buf =
unsafe { kpathsea_find_file(self.0, c_name.as_ptr(), file_format_type, 0) };
if !c_filename_buf.is_null() {
let c_filepath: &CStr = unsafe { CStr::from_ptr(c_filename_buf) };
let filepath = c_filepath.to_str().unwrap().to_owned();
if filepath.is_empty() {
None
} else {
Some(filepath)
}
} else {
None
}
}
}
impl Drop for Kpaths {
fn drop(&mut self) {
unsafe { kpathsea_finish(self.0) };
}
}