use std::collections::HashMap;
use std::sync::LazyLock;
use serde::Deserialize;
const PACKAGE_NAMES_TXT: &str = include_str!("../../data/package_names.txt");
const CLASS_NAMES_TXT: &str = include_str!("../../data/class_names.txt");
static PACKAGE_NAMES: LazyLock<Vec<&'static str>> =
LazyLock::new(|| parse_name_list(PACKAGE_NAMES_TXT));
static CLASS_NAMES: LazyLock<Vec<&'static str>> =
LazyLock::new(|| parse_name_list(CLASS_NAMES_TXT));
fn parse_name_list(text: &'static str) -> Vec<&'static str> {
text.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#') && *line != "---")
.collect()
}
pub fn package_names() -> &'static [&'static str] {
&PACKAGE_NAMES
}
pub fn class_names() -> &'static [&'static str] {
&CLASS_NAMES
}
const COLORS_JSON: &str = include_str!("../../data/colors.json");
const TIKZ_LIBRARIES_JSON: &str = include_str!("../../data/tikz_libraries.json");
const ARG_ENUMS_JSON: &str = include_str!("../../data/arg_enums.json");
#[derive(Deserialize)]
struct ColorsData {
names: Vec<String>,
models: Vec<String>,
}
#[derive(Deserialize)]
struct TikzLibrariesData {
tikz: Vec<String>,
pgf: Vec<String>,
}
static COLORS: LazyLock<ColorsData> = LazyLock::new(|| {
serde_json::from_str(COLORS_JSON).expect("bundled data/colors.json must be valid")
});
static TIKZ_LIBRARIES: LazyLock<TikzLibrariesData> = LazyLock::new(|| {
serde_json::from_str(TIKZ_LIBRARIES_JSON)
.expect("bundled data/tikz_libraries.json must be valid")
});
static ARG_ENUMS: LazyLock<HashMap<String, HashMap<usize, Vec<String>>>> = LazyLock::new(|| {
serde_json::from_str(ARG_ENUMS_JSON).expect("bundled data/arg_enums.json must be valid")
});
fn as_static_slice(names: &'static [String]) -> Vec<&'static str> {
names.iter().map(String::as_str).collect()
}
pub fn color_names() -> &'static [&'static str] {
static NAMES: LazyLock<Vec<&'static str>> = LazyLock::new(|| as_static_slice(&COLORS.names));
&NAMES
}
pub fn color_models() -> &'static [&'static str] {
static MODELS: LazyLock<Vec<&'static str>> = LazyLock::new(|| as_static_slice(&COLORS.models));
&MODELS
}
pub fn tikz_libraries() -> &'static [&'static str] {
static LIBS: LazyLock<Vec<&'static str>> =
LazyLock::new(|| as_static_slice(&TIKZ_LIBRARIES.tikz));
&LIBS
}
pub fn pgf_libraries() -> &'static [&'static str] {
static LIBS: LazyLock<Vec<&'static str>> =
LazyLock::new(|| as_static_slice(&TIKZ_LIBRARIES.pgf));
&LIBS
}
pub fn arg_enum_values(name: &str, index: usize) -> Option<&'static [String]> {
ARG_ENUMS.get(name)?.get(&index).map(Vec::as_slice)
}
type PackageMetaMap = phf::Map<&'static str, PackageMeta>;
#[derive(Debug, Clone, Copy)]
pub struct PackageMeta {
pub desc: Option<&'static str>,
pub ctan: Option<&'static str>,
}
impl PackageMeta {
pub fn ctan_url(&self) -> Option<String> {
self.ctan.map(|id| format!("https://ctan.org/pkg/{id}"))
}
}
const fn meta(desc: Option<&'static str>, ctan: Option<&'static str>) -> PackageMeta {
PackageMeta { desc, ctan }
}
include!(concat!(env!("OUT_DIR"), "/package_metadata.rs"));
pub fn package_metadata(name: &str) -> Option<&'static PackageMeta> {
PACKAGE_METADATA.get(name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arg_enums_load_and_resolve() {
assert_eq!(
arg_enum_values("pagenumbering", 0),
Some(
["arabic", "roman", "Roman", "alph", "Alph"]
.map(String::from)
.as_slice()
)
);
assert!(arg_enum_values("pagestyle", 0).is_some());
assert!(arg_enum_values("pagestyle", 1).is_none());
assert!(arg_enum_values("definitelynotacommand", 0).is_none());
}
#[test]
fn package_metadata_resolves_stem_to_ctan_facts() {
let meta = package_metadata("amsmath").expect("amsmath in metadata DB");
assert_eq!(meta.desc, Some("AMS mathematical facilities for LaTeX"));
assert_eq!(
meta.ctan_url().as_deref(),
Some("https://ctan.org/pkg/latex-amsmath")
);
assert!(package_metadata("definitely-not-a-real-package").is_none());
}
}