Skip to content

Commit

Permalink
Use TeX dist package manager to resolve files
Browse files Browse the repository at this point in the history
  • Loading branch information
efoerster committed Jun 9, 2019
1 parent 3f646e4 commit 1e1cfb1
Show file tree
Hide file tree
Showing 14 changed files with 401 additions and 265 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ members = ["./jsonrpc", "./jsonrpc_derive"]

[dependencies]
bytes = "0.4.12"
byteorder = "1"
clap = "2.33"
copy_dir = "0.1.2"
futures-boxed = { path = "futures_boxed" }
Expand Down
2 changes: 1 addition & 1 deletion src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::sync::Mutex;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Action {
RegisterCapabilities,
LoadResolver,
LoadDistribution,
ResolveIncludes,
PublishDiagnostics,
RunLinter(Uri),
Expand Down
41 changes: 25 additions & 16 deletions src/completion/latex/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ where
{
LatexCombinators::argument(request, &commands, 0, async move |_| {
request
.resolver
.files_by_name
.values()
.distribution
.packages
.iter()
.flat_map(|package| &package.run_files)
.filter(|file| file.extension().and_then(OsStr::to_str) == Some(extension))
.flat_map(|file| file.file_stem().unwrap().to_str())
.map(|name| factory(Cow::from(name.to_owned())))
.map(Arc::new)
.map(|file| file.file_stem().unwrap().to_str().unwrap())
.map(|name| Arc::new(factory(Cow::from(name.to_owned()))))
.collect()
})
.await
Expand All @@ -67,18 +67,27 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::distribution::{PackageManifest, TexDistribution};
use crate::feature::{test_feature, FeatureSpec};
use crate::resolver::TexResolver;
use lsp_types::Position;
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::PathBuf;

fn create_resolver() -> TexResolver {
let mut files_by_name = HashMap::new();
files_by_name.insert(OsString::from("foo.sty"), PathBuf::from("./foo.sty"));
files_by_name.insert(OsString::from("bar.cls"), PathBuf::from("./bar.cls"));
TexResolver { files_by_name }
fn create_distribution() -> TexDistribution {
let packages = vec![
PackageManifest {
run_files: vec![PathBuf::from("./foo.sty")],
..PackageManifest::default()
},
PackageManifest {
run_files: vec![PathBuf::from("./bar.cls")],
..PackageManifest::default()
},
];

TexDistribution {
packages,
..TexDistribution::default()
}
}

#[test]
Expand All @@ -89,7 +98,7 @@ mod tests {
files: vec![FeatureSpec::file("foo.tex", "\\documentclass{}")],
main_file: "foo.tex",
position: Position::new(0, 15),
resolver: create_resolver(),
distribution: create_distribution(),
..FeatureSpec::default()
},
);
Expand All @@ -106,7 +115,7 @@ mod tests {
files: vec![FeatureSpec::file("foo.tex", "\\usepackage{}")],
main_file: "foo.tex",
position: Position::new(0, 12),
resolver: create_resolver(),
distribution: create_distribution(),
..FeatureSpec::default()
},
);
Expand Down
193 changes: 193 additions & 0 deletions src/distribution/ini.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
use std::collections::HashMap;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Value<'a> {
String(&'a str),
Array(Vec<&'a str>),
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Section<'a> {
pub name: &'a str,
pub entries: HashMap<&'a str, Value<'a>>,
}

impl<'a> Section<'a> {
pub fn get_string_value(&self, key: &str) -> Option<&str> {
match &self.entries.get(key)? {
Value::String(value) => Some(value),
Value::Array(_) => None,
}
}

pub fn get_array_value(&self, key: &str) -> Option<&Vec<&str>> {
match &self.entries.get(key)? {
Value::String(_) => None,
Value::Array(values) => Some(values),
}
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ini<'a> {
pub sections: Vec<Section<'a>>,
}

pub mod parser {
use super::*;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_till1};
use nom::character::complete::{multispace1, not_line_ending};
use nom::combinator::{map, opt};
use nom::multi::{fold_many0, many0, many1};
use nom::IResult;

pub fn parse(input: &str) -> IResult<&str, Ini> {
let (input, _) = trivia(input)?;
let (input, sections) = many0(section)(input)?;
Ok((input, Ini { sections }))
}

fn section(input: &str) -> IResult<&str, Section> {
let (input, _) = tag("[")(input)?;
let (input, name) = ident(input)?;
let (input, _) = tag("]")(input)?;
let (input, _) = trivia(input)?;
let (input, entries) = many1(entry)(input)?;
let (input, _) = trivia(input)?;
Ok((
input,
Section {
name,
entries: entries.into_iter().collect(),
},
))
}

fn entry(input: &str) -> IResult<&str, (&str, Value)> {
let (input, key) = ident(input)?;
let (input, is_array) = map(opt(tag("[]")), |x| x.is_some())(input)?;
let (input, _) = tag("=")(input)?;
let (input, value) = not_line_ending(input)?;
let (input, _) = trivia(input)?;

let (input, value) = if is_array {
let add_to_vec = |mut acc: Vec<_>, item| {
acc.push(item);
acc
};

let (input, values) = fold_many0(array_value(key), vec![value], add_to_vec)(input)?;
(input, Value::Array(values))
} else {
(input, Value::String(value))
};

Ok((input, (key, value)))
}

fn array_value(key: &str) -> impl Fn(&str) -> IResult<&str, &str> + '_ {
move |input: &str| {
let (input, _) = tag(key)(input)?;
let (input, _) = tag("[]")(input)?;
let (input, _) = tag("=")(input)?;
let (input, value) = not_line_ending(input)?;
let (input, _) = trivia(input)?;
Ok((input, value))
}
}

fn ident(input: &str) -> IResult<&str, &str> {
take_till1(|c| ";[]=\r\n".contains(c))(input)
}

fn trivia(input: &str) -> IResult<&str, ()> {
map(many0(alt((multispace1, comment))), |_| ())(input)
}

fn comment(input: &str) -> IResult<&str, &str> {
let (input, _) = tag(";")(input)?;
not_line_ending(input)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_comment() {
let (input, result) = comment(";foo").unwrap();
assert!(input.is_empty());
assert_eq!(result, "foo");
}

#[test]
fn test_parse_trivia() {
let (input, _) = trivia(";foo\r\n \r\n").unwrap();
assert!(input.is_empty());
}

#[test]
fn test_parse_ident() {
let id = "_foo-bar";
let (input, result) = ident(id).unwrap();
assert!(input.is_empty());
assert_eq!(result, id);
}

#[test]
fn test_parse_entry_string() {
let input = format!("foo=[bar]\r\n");
let (input, (key, value)) = entry(&input).unwrap();
assert!(input.is_empty());
assert_eq!(key, "foo");
match value {
Value::String(value) => assert_eq!(value, "[bar]"),
Value::Array(_) => panic!("Expected String"),
};
}

#[test]
fn test_parse_entry_array() {
let input = format!("foo[]=bar\r\n");
let (input, (key, value)) = entry(&input).unwrap();
assert!(input.is_empty());
assert_eq!(key, "foo");
match value {
Value::String(_) => panic!("Expected Array"),
Value::Array(values) => {
assert_eq!(values.len(), 1);
assert_eq!(values[0], "bar");
}
};
}

#[test]
fn test_parse_section() {
let (input, result) = section("[foo]\r\nfoo[]=bar\r\nfoo[]=baz\r\nbaz=\r\n").unwrap();
assert!(input.is_empty());
assert_eq!(result.entries.len(), 2);
assert!(result.entries.contains_key("foo"));
assert!(result.entries.contains_key("baz"));

match &result.entries["foo"] {
Value::String(_) => {
panic!("Expected Array");
}
Value::Array(values) => {
assert_eq!(values.len(), 2);
assert_eq!(values[0], "bar");
assert_eq!(values[1], "baz");
}
};
}

#[test]
fn test_parse() {
let (input, result) =
parse(";foo\r\n[foo]\r\nfoo=bar\r\n[bar]\r\nbar=baz\r\n").unwrap();
assert!(input.is_empty());
assert_eq!(result.sections.len(), 2);
}
}
}
57 changes: 57 additions & 0 deletions src/distribution/miktex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use super::ini;
use super::*;
use std::fs;
use std::path::Path;

pub const DATABASE_PATH: &str = "miktex/config/package-manifests.ini";
const INSTALLED_PATH: &str = "miktex/config/packages.ini";

pub fn read_database(file: &Path, root_dir: &Path) -> Option<Vec<PackageManifest>> {
let manifests = fs::read_to_string(&file).ok()?;
let (_, manifests) = ini::parser::parse(&manifests).ok()?;

let installed = fs::read_to_string(&root_dir.join(INSTALLED_PATH)).ok()?;
let (_, installed) = ini::parser::parse(&installed).ok()?;

let packages = manifests
.sections
.into_iter()
.filter(|x| !x.name.starts_with('_'))
.flat_map(|x| read_manifest(&x, &installed, root_dir))
.collect();

Some(packages)
}

fn read_manifest(
section: &ini::Section,
installed: &ini::Ini,
root_dir: &Path,
) -> Option<PackageManifest> {
let name = section.name.to_owned();
let title = section.get_string_value("title")?.to_owned();
let description = section
.get_array_value("description")
.and_then(|x| x.first())
.map(|x| x.to_string());
let doc_files = section
.get_array_value("doc")?
.iter()
.map(|x| root_dir.join(x))
.collect();
let run_files = section
.get_array_value("run")?
.iter()
.map(|x| root_dir.join(x))
.collect();
let is_installed = installed.sections.iter().any(|x| x.name == name);

Some(PackageManifest {
name,
title,
description,
doc_files,
run_files,
is_installed,
})
}
Loading

0 comments on commit 1e1cfb1

Please sign in to comment.