boringbib/lib.rs
1//! A boring BibTeX formatter.
2//!
3//! `boringbib` is a small command-line tool for BibTeX `.bib` files with two
4//! jobs:
5//!
6//! * [`fmt`](printer): sort entries by citation key and pretty-print each
7//! entry with the `=` signs aligned, exactly like LaTeX Workshop's "Align
8//! and sort" action.
9//! * [`keys`]: rewrite citation keys into Google Scholar style
10//! (`vaswani2017attention`), updating in-file references so nothing
11//! dangles.
12//!
13//! The library is organized as a pipeline over a lossless concrete syntax
14//! tree:
15//!
16//! ```text
17//! text ──parse()──▶ Cst ──sort::group()/sort()──▶ printer::format() ──▶ text
18//! │
19//! └──keys::plan()──▶ Plan ──keys::apply()──▶ text
20//! ```
21//!
22//! The parser ([`parser`], built on [`lexer`]) keeps every byte of the input
23//! in the [`Cst`]; `Cst::to_source()` reproduces the input exactly. All
24//! formatting decisions live in [`printer`] and [`sort`]; `keys` edits are
25//! splices of the original text at recorded spans. This separation is what
26//! makes the tool boring: deterministic output, idempotent runs, and no loss
27//! of the user's data.
28//!
29//! The binary lives in [`cli`]; see `DESIGN.md` in the repository for the
30//! decisions behind the grammar and the output rules.
31
32pub mod cli;
33pub mod config;
34pub mod cst;
35pub mod keys;
36pub mod lexer;
37pub mod parser;
38pub mod printer;
39pub mod sort;
40
41pub use cst::Cst;
42pub use lexer::ParseError;
43pub use parser::parse;
44pub use printer::{FmtOptions, format, format_str};
45
46/// Errors reported by the library when working with files.
47///
48/// Each variant's message is complete on its own (the cause is part of the
49/// text rather than a chained source), so that it prints the same whether
50/// or not the caller walks the error chain. Parse errors render as
51/// `file:line:col: message`, the format editors know how to jump to.
52#[derive(Debug, thiserror::Error)]
53pub enum Error {
54 /// The file is not syntactically valid BibTeX.
55 #[error("{path}:{error}")]
56 Parse {
57 /// Display name of the file (`<stdin>` for standard input).
58 path: String,
59 /// The underlying syntax error.
60 error: ParseError,
61 },
62 /// Reading or writing the file failed.
63 #[error("{path}: {error}")]
64 Io {
65 /// Display name of the file.
66 path: String,
67 /// The underlying I/O error.
68 error: std::io::Error,
69 },
70 /// The file is not valid UTF-8.
71 #[error("{path}: not valid UTF-8 (invalid byte sequence at offset {offset})")]
72 Utf8 {
73 /// Display name of the file.
74 path: String,
75 /// Byte offset of the first invalid sequence.
76 offset: usize,
77 },
78 /// The configuration file could not be parsed.
79 #[error("{path}: {message}")]
80 Config {
81 /// Path of the configuration file.
82 path: String,
83 /// What is wrong with it.
84 message: String,
85 },
86}