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>;