summaryrefslogtreecommitdiff
path: root/support/texlab/crates/distro/src
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/crates/distro/src')
-rw-r--r--support/texlab/crates/distro/src/file_name_db.rs80
-rw-r--r--support/texlab/crates/distro/src/kpsewhich.rs25
-rw-r--r--support/texlab/crates/distro/src/language.rs42
-rw-r--r--support/texlab/crates/distro/src/lib.rs75
-rw-r--r--support/texlab/crates/distro/src/miktex.rs78
-rw-r--r--support/texlab/crates/distro/src/texlive.rs36
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
+}