Skip to main content

jsslint_core/bib/
model.rs

1//! BibTeX data model — mirrors the subset of `bibtexparser` v2's model
2//! this codebase's rules consume (`Library.entries`, `Library.failed_blocks`,
3//! `Entry.key`/`.entry_type`/`.start_line`/`.fields_dict`,
4//! `Field.value`, `DuplicateBlockKeyBlock`, `DuplicateFieldKeyBlock`).
5//!
6//! Positions are Unicode codepoint offsets (see `tex::node`'s doc
7//! comment on the same point) and `start_line` is 0-based, matching
8//! `bibtexparser` exactly — `_helpers.py::entry_line` adds 1 at the
9//! call site, not here.
10
11use std::collections::HashMap;
12
13#[derive(Debug, Clone)]
14pub struct Field {
15    /// Original-case field name as written in the source (`fields_dict`
16    /// keys preserve source casing — `_lc_fields` in `_helpers.py`
17    /// lowercases them itself, not `bibtexparser`).
18    pub key: String,
19    /// Cleaned value: surrounding `{}`/`""` stripped, `@string` macro
20    /// references resolved, `#`-concatenated parts joined.
21    pub value: String,
22}
23
24#[derive(Debug, Clone)]
25pub struct Entry {
26    pub key: String,
27    /// Already lowercased at parse time — mirrors `bibtexparser`,
28    /// which normalizes `entry.entry_type` regardless of source
29    /// casing (`@TECHREPORT{...}` still reports `entry_type ==
30    /// "techreport"`; verified empirically, not documented anywhere
31    /// obvious). A few call sites still call `.to_lowercase()` on
32    /// this defensively — harmless no-ops now, kept rather than
33    /// touched, not evidence this field is ever non-lowercase.
34    pub entry_type: String,
35    /// 0-based line index of the entry's `@type{` opening.
36    pub start_line: u32,
37    pub fields: Vec<Field>,
38}
39
40impl Entry {
41    /// Case-insensitive field lookup. Mirrors
42    /// `_helpers.py::_lc_fields(entry).get(name.lower())`.
43    pub fn field(&self, name: &str) -> Option<&Field> {
44        self.fields
45            .iter()
46            .find(|f| f.key.eq_ignore_ascii_case(name))
47    }
48}
49
50/// A duplicate-citation-key block: a second (or later) `@entry{key,...}`
51/// using a key already claimed by an earlier entry in the same file.
52/// Mirrors `bibtexparser`'s `DuplicateBlockKeyBlock`.
53#[derive(Debug, Clone)]
54pub struct DuplicateBlockKey {
55    pub key: String,
56    pub start_line: u32,
57    /// The duplicate's own (recovered) entry — used by BIBTEX-002 to
58    /// report a location.
59    pub entry: Entry,
60}
61
62/// An entry that had a field key repeated within itself (last value
63/// wins in the recovered `entry`, matching `bibtexparser`). Mirrors
64/// `DuplicateFieldKeyBlock`.
65#[derive(Debug, Clone)]
66pub struct DuplicateFieldKey {
67    pub start_line: u32,
68    pub duplicate_keys: Vec<String>,
69    pub entry: Entry,
70}
71
72#[derive(Debug, Clone, Default)]
73pub struct Library {
74    pub entries: Vec<Entry>,
75    pub duplicate_block_keys: Vec<DuplicateBlockKey>,
76    pub duplicate_field_keys: Vec<DuplicateFieldKey>,
77}
78
79/// `@string{name = value}` macro table, used only during parsing to
80/// resolve bare-token field values (e.g. `journal = jsstat`).
81pub(crate) type StringTable = HashMap<String, String>;