Skip to main content

bibtex_parser/
lib.rs

1#![deny(clippy::all)]
2//! # bibtex-parser
3//!
4//! Fast BibTeX parsing with a Rust-first [`Library`] API.
5//!
6//! `bibtex-parser` is built for applications that need both throughput and a
7//! practical user-facing API: strict parsing by default, explicit tolerant
8//! recovery when a corpus is messy, string and month expansion, comments and
9//! preambles, validation, query/edit helpers, and configurable writing.
10//!
11//! ## Features
12//!
13//! - Borrowed values where possible for low-allocation parsing.
14//! - String variables, concatenation, and standard month constants.
15//! - Entries, strings, preambles, comments, and tolerant failures in source order.
16//! - Opt-in source-span capture.
17//! - DOI normalization, duplicate detection, validation, sorting, and field normalization.
18//! - Configurable writer for formatting and file output.
19//! - Optional `parallel` feature for parsing multiple files concurrently.
20//! - Optional `latex_to_unicode` feature for LaTeX accent conversion helpers.
21//!
22//! ## Parse
23//!
24//! ```
25//! use bibtex_parser::Library;
26//!
27//! let input = r#"
28//!     @string{venue = "VLDB"}
29//!     @article{paper,
30//!         author = "Jane Doe and John Smith",
31//!         title = "Fast BibTeX",
32//!         journal = venue,
33//!         year = 2026
34//!     }
35//! "#;
36//!
37//! let library = Library::parse(input)?;
38//! let entry = library.find_by_key("paper").unwrap();
39//!
40//! assert_eq!(entry.get("journal"), Some("VLDB"));
41//! assert_eq!(entry.year(), Some("2026".to_string()));
42//! assert_eq!(entry.authors().len(), 2);
43//! # Ok::<(), bibtex_parser::Error>(())
44//! ```
45//!
46//! ## Tolerant Recovery
47//!
48//! ```
49//! use bibtex_parser::{Block, Library};
50//!
51//! let library = Library::parser()
52//!     .tolerant()
53//!     .capture_source()
54//!     .parse(r#"
55//!         @article{ok, title = "Good"}
56//!         @article{bad, title = "Missing close"
57//!         @book{recovered, title = "Recovered"}
58//!     "#)?;
59//!
60//! assert_eq!(library.entries().len(), 2);
61//! assert_eq!(library.failed_blocks().len(), 1);
62//!
63//! let has_failure_span = library.blocks().iter().any(|block| {
64//!     matches!(block, Block::Failed(failed) if failed.source.is_some())
65//! });
66//! assert!(has_failure_span);
67//! # Ok::<(), bibtex_parser::Error>(())
68//! ```
69//!
70//! ## Write
71//!
72//! ```
73//! use bibtex_parser::{Library, Writer, WriterConfig};
74//!
75//! let library = Library::parse(r#"@article{paper, title = "Fast BibTeX"}"#)?;
76//! let mut output = Vec::new();
77//! let config = WriterConfig {
78//!     align_values: true,
79//!     ..Default::default()
80//! };
81//!
82//! Writer::with_config(&mut output, config).write_library(&library)?;
83//! assert!(String::from_utf8(output).unwrap().contains("@article{paper"));
84//! # Ok::<(), bibtex_parser::Error>(())
85//! ```
86
87#![forbid(unsafe_code)]
88#![warn(
89    clippy::all,
90    clippy::pedantic,
91    clippy::nursery,
92    clippy::cargo,
93    missing_docs,
94    missing_debug_implementations
95)]
96#![allow(
97    clippy::module_name_repetitions,
98    clippy::missing_errors_doc,
99    clippy::missing_panics_doc,
100    clippy::multiple_crate_versions
101)]
102
103pub mod error;
104pub mod model;
105pub mod parser;
106
107#[cfg(feature = "latex_to_unicode")]
108pub mod latex_unicode;
109
110mod database;
111mod writer;
112
113pub use database::{
114    Block, Comment, FailedBlock, FieldNameCase, FieldNormalizeOptions, IssueSummary, Library,
115    LibraryBuilder, LibraryStats, MonthStyle, Parser, Preamble, SortOptions, StringDefinition,
116    ValidationReport,
117};
118pub use error::{Error, Result, SourceSpan};
119pub use model::{
120    normalize_doi, parse_names, Entry, EntryType, Field, PersonName, ValidationError,
121    ValidationLevel, ValidationSeverity, Value,
122};
123pub use parser::{parse_bibtex, ParsedItem};
124pub use writer::{to_file, to_string, Writer, WriterConfig};
125
126/// Re-export of common parser functions
127pub mod prelude {
128    pub use crate::{
129        normalize_doi, parse_bibtex, parse_names, Block, Comment, Entry, EntryType, Error,
130        FailedBlock, Field, FieldNameCase, FieldNormalizeOptions, IssueSummary, Library,
131        LibraryBuilder, LibraryStats, MonthStyle, ParsedItem, Parser, PersonName, Preamble, Result,
132        SortOptions, SourceSpan, StringDefinition, ValidationError, ValidationLevel,
133        ValidationReport, ValidationSeverity, Value, Writer, WriterConfig,
134    };
135}
136
137/// Parse a BibTeX library from a string.
138pub fn parse(input: &str) -> Result<Library<'_>> {
139    Library::parser().parse(input)
140}
141
142/// Parse a BibTeX library from a file.
143pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Library<'static>> {
144    let content = std::fs::read_to_string(path)?;
145    parse(&content).map(Library::into_owned)
146}