Skip to main content

jsslint_core/
terms.rs

1//! Canonical-form lookup tables — embedded from
2//! `specs/003-jss-rule-catalogue/terms.json` (generated from
3//! `texlint.journals.jss.terms` by `tools/generate_terms_json.py`; run
4//! that script and commit the diff whenever `terms.py` changes).
5
6use serde::Deserialize;
7use std::collections::{HashMap, HashSet};
8use std::sync::LazyLock;
9
10#[derive(Debug, Deserialize)]
11struct TermsJson {
12    languages: Vec<String>,
13    r_packages: Vec<String>,
14    canonical: HashMap<String, String>,
15    publisher_canonical: HashMap<String, String>,
16    journal_canonical: HashMap<String, String>,
17    /// Order is semantically significant (first match wins) — a `Vec`
18    /// of `[prefix, canonical]` pairs, not a map.
19    publisher_prefix_canonical: Vec<[String; 2]>,
20}
21
22pub struct Terms {
23    pub languages: HashSet<String>,
24    pub r_packages: HashSet<String>,
25    pub canonical: HashMap<String, String>,
26    pub publisher_canonical: HashMap<String, String>,
27    pub journal_canonical: HashMap<String, String>,
28    pub publisher_prefix_canonical: Vec<(String, String)>,
29}
30
31// Copied into OUT_DIR by build.rs (rather than a relative include_str!
32// path) because the depth from this file to specs/003-jss-rule-catalogue
33// differs between the canonical crate location and a vendored copy (see
34// jsslintr's src/rust/jsslint-core).
35const TERMS_JSON: &str = include_str!(concat!(env!("OUT_DIR"), "/terms.json"));
36
37pub static TERMS: LazyLock<Terms> = LazyLock::new(|| {
38    let raw: TermsJson = serde_json::from_str(TERMS_JSON)
39        .expect("terms.json must parse (generated by tools/generate_terms_json.py)");
40    Terms {
41        languages: raw.languages.into_iter().collect(),
42        r_packages: raw.r_packages.into_iter().collect(),
43        canonical: raw.canonical,
44        publisher_canonical: raw.publisher_canonical,
45        journal_canonical: raw.journal_canonical,
46        publisher_prefix_canonical: raw
47            .publisher_prefix_canonical
48            .into_iter()
49            .map(|[prefix, canon]| (prefix, canon))
50            .collect(),
51    }
52});
53
54impl Terms {
55    /// Mirrors `texlint.journals.jss.terms.canonical_form`: no case
56    /// folding, no substring matching, no fuzzy matching.
57    pub fn canonical_form(&self, token: &str) -> Option<String> {
58        if token.trim().is_empty() {
59            return None;
60        }
61        if let Some(canon) = self.canonical.get(token) {
62            return Some(canon.clone());
63        }
64        if self.languages.contains(token) {
65            return Some(token.to_string());
66        }
67        if self.r_packages.contains(token) {
68            return Some(token.to_string());
69        }
70        None
71    }
72}