bibtex-parser 0.1.0

Fast BibTeX parser with a rich Rust-first library API
Documentation
#![deny(clippy::all)]
//! # bibtex-parser
//!
//! Fast BibTeX parsing with a Rust-first [`Library`] API.
//!
//! `bibtex-parser` is built for applications that need both throughput and a
//! practical user-facing API: strict parsing by default, explicit tolerant
//! recovery when a corpus is messy, string and month expansion, comments and
//! preambles, validation, query/edit helpers, and configurable writing.
//!
//! ## Features
//!
//! - Borrowed values where possible for low-allocation parsing.
//! - String variables, concatenation, and standard month constants.
//! - Entries, strings, preambles, comments, and tolerant failures in source order.
//! - Opt-in source-span capture.
//! - DOI normalization, duplicate detection, validation, sorting, and field normalization.
//! - Configurable writer for formatting and file output.
//! - Optional `parallel` feature for parsing multiple files concurrently.
//! - Optional `latex_to_unicode` feature for LaTeX accent conversion helpers.
//!
//! ## Parse
//!
//! ```
//! use bibtex_parser::Library;
//!
//! let input = r#"
//!     @string{venue = "VLDB"}
//!     @article{paper,
//!         author = "Jane Doe and John Smith",
//!         title = "Fast BibTeX",
//!         journal = venue,
//!         year = 2026
//!     }
//! "#;
//!
//! let library = Library::parse(input)?;
//! let entry = library.find_by_key("paper").unwrap();
//!
//! assert_eq!(entry.get("journal"), Some("VLDB"));
//! assert_eq!(entry.year(), Some("2026".to_string()));
//! assert_eq!(entry.authors().len(), 2);
//! # Ok::<(), bibtex_parser::Error>(())
//! ```
//!
//! ## Tolerant Recovery
//!
//! ```
//! use bibtex_parser::{Block, Library};
//!
//! let library = Library::parser()
//!     .tolerant()
//!     .capture_source()
//!     .parse(r#"
//!         @article{ok, title = "Good"}
//!         @article{bad, title = "Missing close"
//!         @book{recovered, title = "Recovered"}
//!     "#)?;
//!
//! assert_eq!(library.entries().len(), 2);
//! assert_eq!(library.failed_blocks().len(), 1);
//!
//! let has_failure_span = library.blocks().iter().any(|block| {
//!     matches!(block, Block::Failed(failed) if failed.source.is_some())
//! });
//! assert!(has_failure_span);
//! # Ok::<(), bibtex_parser::Error>(())
//! ```
//!
//! ## Write
//!
//! ```
//! use bibtex_parser::{Library, Writer, WriterConfig};
//!
//! let library = Library::parse(r#"@article{paper, title = "Fast BibTeX"}"#)?;
//! let mut output = Vec::new();
//! let config = WriterConfig {
//!     align_values: true,
//!     ..Default::default()
//! };
//!
//! Writer::with_config(&mut output, config).write_library(&library)?;
//! assert!(String::from_utf8(output).unwrap().contains("@article{paper"));
//! # Ok::<(), bibtex_parser::Error>(())
//! ```

#![forbid(unsafe_code)]
#![warn(
    clippy::all,
    clippy::pedantic,
    clippy::nursery,
    clippy::cargo,
    missing_docs,
    missing_debug_implementations
)]
#![allow(
    clippy::module_name_repetitions,
    clippy::missing_errors_doc,
    clippy::missing_panics_doc,
    clippy::multiple_crate_versions
)]

pub mod error;
pub mod model;
pub mod parser;

#[cfg(feature = "latex_to_unicode")]
pub mod latex_unicode;

mod database;
mod writer;

pub use database::{
    Block, Comment, FailedBlock, FieldNameCase, FieldNormalizeOptions, IssueSummary, Library,
    LibraryBuilder, LibraryStats, MonthStyle, Parser, Preamble, SortOptions, StringDefinition,
    ValidationReport,
};
pub use error::{Error, Result, SourceSpan};
pub use model::{
    normalize_doi, parse_names, Entry, EntryType, Field, PersonName, ValidationError,
    ValidationLevel, ValidationSeverity, Value,
};
pub use parser::{parse_bibtex, ParsedItem};
pub use writer::{to_file, to_string, Writer, WriterConfig};

/// Re-export of common parser functions
pub mod prelude {
    pub use crate::{
        normalize_doi, parse_bibtex, parse_names, Block, Comment, Entry, EntryType, Error,
        FailedBlock, Field, FieldNameCase, FieldNormalizeOptions, IssueSummary, Library,
        LibraryBuilder, LibraryStats, MonthStyle, ParsedItem, Parser, PersonName, Preamble, Result,
        SortOptions, SourceSpan, StringDefinition, ValidationError, ValidationLevel,
        ValidationReport, ValidationSeverity, Value, Writer, WriterConfig,
    };
}

/// Parse a BibTeX library from a string.
pub fn parse(input: &str) -> Result<Library<'_>> {
    Library::parser().parse(input)
}

/// Parse a BibTeX library from a file.
pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Library<'static>> {
    let content = std::fs::read_to_string(path)?;
    parse(&content).map(Library::into_owned)
}