diff options
Diffstat (limited to 'support/texlab/crates/distro/src')
-rw-r--r-- | support/texlab/crates/distro/src/file_name_db.rs | 80 | ||||
-rw-r--r-- | support/texlab/crates/distro/src/kpsewhich.rs | 25 | ||||
-rw-r--r-- | support/texlab/crates/distro/src/language.rs | 42 | ||||
-rw-r--r-- | support/texlab/crates/distro/src/lib.rs | 75 | ||||
-rw-r--r-- | support/texlab/crates/distro/src/miktex.rs | 78 | ||||
-rw-r--r-- | support/texlab/crates/distro/src/texlive.rs | 36 |
6 files changed, 336 insertions, 0 deletions
diff --git a/support/texlab/crates/distro/src/file_name_db.rs b/support/texlab/crates/distro/src/file_name_db.rs new file mode 100644 index 0000000000..750ed11e68 --- /dev/null +++ b/support/texlab/crates/distro/src/file_name_db.rs @@ -0,0 +1,80 @@ +use std::{ + borrow::Borrow, + path::{Path, PathBuf}, +}; + +use anyhow::Result; +use rustc_hash::FxHashSet; + +use crate::Language; + +#[derive(Debug)] +pub struct DistroFile(PathBuf); + +impl DistroFile { + pub fn path(&self) -> &Path { + &self.0 + } + + pub fn name(&self) -> &str { + self.0.file_name().unwrap().to_str().unwrap() + } +} + +impl PartialEq for DistroFile { + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() + } +} + +impl Eq for DistroFile {} + +impl std::hash::Hash for DistroFile { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.name().hash(state) + } +} + +impl Borrow<str> for DistroFile { + fn borrow(&self) -> &str { + self.name() + } +} + +#[derive(Debug, Default)] +pub struct FileNameDB { + files: FxHashSet<DistroFile>, +} + +impl FileNameDB { + pub fn get(&self, name: &str) -> Option<&Path> { + self.files.get(name).map(|file| file.path()) + } + + pub fn iter(&self) -> impl Iterator<Item = (&str, &Path)> + '_ { + self.files.iter().map(|file| (file.name(), file.path())) + } + + pub fn parse( + root_dirs: &[PathBuf], + reader: &mut dyn FnMut(&Path) -> Result<Vec<PathBuf>>, + ) -> Result<Self> { + let files = root_dirs + .iter() + .flat_map(|dir| reader(dir)) + .flatten() + .filter_map(|rel_path| { + Language::from_path(&rel_path)?; + rel_path.file_name()?.to_str()?; + let abs_path = root_dirs + .iter() + .rev() + .map(|dir| dir.join(&rel_path)) + .find_map(|path| std::fs::canonicalize(path).ok())?; + Some(DistroFile(abs_path)) + }) + .collect(); + + Ok(Self { files }) + } +} diff --git a/support/texlab/crates/distro/src/kpsewhich.rs b/support/texlab/crates/distro/src/kpsewhich.rs new file mode 100644 index 0000000000..24f4a3531a --- /dev/null +++ b/support/texlab/crates/distro/src/kpsewhich.rs @@ -0,0 +1,25 @@ +use std::{env, ffi::OsStr, path::PathBuf, process::Command}; + +use anyhow::Result; + +pub fn root_directories() -> Result<Vec<PathBuf>> { + let texmf = run(["-var-value", "TEXMF"])?; + let expand_arg = format!("--expand-braces={}", texmf); + let expanded = run([&expand_arg])?; + let directories = env::split_paths(&expanded.replace('!', "")) + .filter(|path| path.exists()) + .collect(); + Ok(directories) +} + +fn run(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<String> { + let output = Command::new("kpsewhich").args(args).output()?; + + let result = String::from_utf8(output.stdout)? + .lines() + .next() + .unwrap() + .into(); + + Ok(result) +} diff --git a/support/texlab/crates/distro/src/language.rs b/support/texlab/crates/distro/src/language.rs new file mode 100644 index 0000000000..207b700581 --- /dev/null +++ b/support/texlab/crates/distro/src/language.rs @@ -0,0 +1,42 @@ +use std::path::Path; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum Language { + Tex, + Bib, + Aux, + Log, + Root, + Tectonic, +} + +impl Language { + pub fn from_path(path: &Path) -> Option<Self> { + let name = path.file_name()?; + if name.eq_ignore_ascii_case(".texlabroot") || name.eq_ignore_ascii_case("texlabroot") { + return Some(Self::Root); + } + + if name.eq_ignore_ascii_case("Tectonic.toml") { + return Some(Self::Tectonic); + } + + let extname = path.extension()?.to_str()?; + match extname.to_lowercase().as_str() { + "tex" | "sty" | "cls" | "def" | "lco" | "rnw" => Some(Self::Tex), + "bib" | "bibtex" => Some(Self::Bib), + "aux" => Some(Self::Aux), + "log" => Some(Self::Log), + _ => None, + } + } + + pub fn from_id(id: &str) -> Option<Self> { + match id { + "tex" | "latex" => Some(Self::Tex), + "bib" | "bibtex" => Some(Self::Bib), + "texlabroot" => Some(Self::Root), + _ => None, + } + } +} diff --git a/support/texlab/crates/distro/src/lib.rs b/support/texlab/crates/distro/src/lib.rs new file mode 100644 index 0000000000..5ad9a8529c --- /dev/null +++ b/support/texlab/crates/distro/src/lib.rs @@ -0,0 +1,75 @@ +mod file_name_db; +mod kpsewhich; +mod language; +mod miktex; +mod texlive; + +use std::process::{Command, Stdio}; + +use anyhow::Result; + +pub use self::{file_name_db::FileNameDB, language::Language}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DistroKind { + Texlive, + Miktex, + Tectonic, + Unknown, +} + +impl Default for DistroKind { + fn default() -> Self { + Self::Unknown + } +} + +#[derive(Debug, Default)] +pub struct Distro { + pub kind: DistroKind, + pub file_name_db: FileNameDB, +} + +impl Distro { + pub fn detect() -> Result<Self> { + let kind = match Command::new("latex").arg("--version").output() { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout); + if stdout.contains("TeX Live") { + DistroKind::Texlive + } else if stdout.contains("MiKTeX") { + DistroKind::Miktex + } else { + DistroKind::Unknown + } + } + Err(_) => { + if Command::new("tectonic") + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok() + { + DistroKind::Tectonic + } else { + DistroKind::Unknown + } + } + }; + + let file_name_db = match kind { + DistroKind::Texlive => { + let root_dirs = kpsewhich::root_directories()?; + FileNameDB::parse(&root_dirs, &mut texlive::read_database)? + } + DistroKind::Miktex => { + let root_dirs = kpsewhich::root_directories()?; + FileNameDB::parse(&root_dirs, &mut miktex::read_database)? + } + DistroKind::Tectonic | DistroKind::Unknown => FileNameDB::default(), + }; + + Ok(Self { kind, file_name_db }) + } +} diff --git a/support/texlab/crates/distro/src/miktex.rs b/support/texlab/crates/distro/src/miktex.rs new file mode 100644 index 0000000000..1dfe1b9522 --- /dev/null +++ b/support/texlab/crates/distro/src/miktex.rs @@ -0,0 +1,78 @@ +use std::{ + ffi::OsStr, + fs, + io::{self, Cursor, Read}, + path::{Path, PathBuf}, +}; + +use anyhow::{Context, Result}; + +const DATABASE_PATH: &str = "miktex/data/le"; +const FNDB_SIGNATURE: u32 = 0x42_44_4e_46; +const FNDB_WORD_SIZE: u32 = 4; +const FNDB_TABLE_POINTER_OFFSET: u32 = 4 * FNDB_WORD_SIZE; +const FNDB_TABLE_SIZE_OFFSET: u32 = 6 * FNDB_WORD_SIZE; +const FNDB_ENTRY_SIZE: u32 = 4 * FNDB_WORD_SIZE; + +pub(super) fn read_database(directory: &Path) -> Result<Vec<PathBuf>> { + let database_directory = directory.join(DATABASE_PATH); + if !database_directory.exists() { + return Ok(Vec::new()); + } + + let mut database = Vec::new(); + for file in fs::read_dir(database_directory)?.filter_map(Result::ok) { + if file.path().extension().and_then(OsStr::to_str) == Some("fndb-5") { + let bytes = fs::read(file.path())?; + database.extend(parse_database(&bytes).context("parsing kpsewhich database")?); + } + } + + Ok(database) +} + +fn parse_database(bytes: &[u8]) -> io::Result<Vec<PathBuf>> { + let mut reader = Cursor::new(bytes); + if read_u32(&mut reader)? != FNDB_SIGNATURE { + return Err(io::ErrorKind::InvalidData.into()); + } + + reader.set_position(u64::from(FNDB_TABLE_POINTER_OFFSET)); + let table_address = read_u32(&mut reader)?; + + reader.set_position(u64::from(FNDB_TABLE_SIZE_OFFSET)); + let table_size = read_u32(&mut reader)?; + + let mut files = Vec::new(); + for i in 0..table_size { + let offset = table_address + i * FNDB_ENTRY_SIZE; + reader.set_position(u64::from(offset)); + let file_name_offset = read_u32(&mut reader)? as usize; + let directory_offset = read_u32(&mut reader)? as usize; + let file_name = read_string(bytes, file_name_offset)?; + let directory = read_string(bytes, directory_offset)?; + + let file = PathBuf::from(directory).join(file_name); + files.push(file); + } + + Ok(files) +} + +fn read_string(bytes: &[u8], offset: usize) -> io::Result<&str> { + let mut byte = bytes[offset]; + let mut length = 0; + while byte != 0x00 { + length += 1; + byte = bytes[offset + length]; + } + + std::str::from_utf8(&bytes[offset..offset + length]) + .map_err(|_| io::ErrorKind::InvalidData.into()) +} + +fn read_u32(reader: &mut Cursor<&[u8]>) -> io::Result<u32> { + let mut buf = [0u8; std::mem::size_of::<u32>()]; + reader.read_exact(&mut buf)?; + Ok(u32::from_le_bytes(buf)) +} diff --git a/support/texlab/crates/distro/src/texlive.rs b/support/texlab/crates/distro/src/texlive.rs new file mode 100644 index 0000000000..8013033e6d --- /dev/null +++ b/support/texlab/crates/distro/src/texlive.rs @@ -0,0 +1,36 @@ +use std::{ + fs, + path::{Path, PathBuf}, + str::Lines, +}; + +use anyhow::Result; + +const DATABASE_PATH: &str = "ls-R"; + +pub(super) fn read_database(directory: &Path) -> Result<Vec<PathBuf>> { + let file = directory.join(DATABASE_PATH); + if !file.is_file() { + return Ok(Vec::new()); + } + + let text = fs::read_to_string(file)?; + let files = parse_database(text.lines()); + Ok(files) +} + +fn parse_database(lines: Lines) -> Vec<PathBuf> { + let mut paths = Vec::new(); + let mut directory = ""; + + for line in lines.filter(|x| !x.trim().is_empty() && !x.starts_with('%')) { + if let Some(line) = line.strip_suffix(':') { + directory = line; + } else { + let path = PathBuf::from(directory).join(line); + paths.push(path); + } + } + + paths +} |