rpm_spec/lib.rs
1//! Parser and pretty-printer for RPM `.spec` files.
2//!
3//! This crate exposes a distribution-independent AST suitable for tooling
4//! such as formatters, linters, and static analyzers. Macros are preserved
5//! as AST nodes (never expanded), so consumers can inspect or rewrite the
6//! source without losing structural information.
7//!
8//! # Overview
9//!
10//! - **AST modelling.** A typed, distribution-independent tree rooted at
11//! [`ast::SpecFile<T>`] that covers preamble lines, sections, conditional
12//! blocks, macro definitions, `%bcond*` toggles, rich/boolean dependency
13//! expressions, structured `%if` / `%elif` expression ASTs
14//! ([`ast::CondExpr`], [`ast::ExprAst`]), comments and blank lines.
15//! - **Span-aware parsing.** A recovery-oriented parser that never panics on
16//! real input. Both [`parser::parse_str`] and
17//! [`parser::parse_str_with_spans`] always return a (possibly partial)
18//! [`parse_result::ParseResult`] with a [`ast::SpecFile`] plus a
19//! `Vec<`[`parse_result::Diagnostic`]`>`. Recoverable issues are surfaced
20//! as diagnostics with stable identifiers from
21//! [`parse_result::codes`] (`rpmspec/E####` for errors,
22//! `rpmspec/W####` for warnings) that are stable across patch releases.
23//! - **Pretty-printer.** [`printer::print`] / [`printer::print_with`]
24//! re-emit an `&`[`ast::SpecFile<T>`] as a normalised but structurally
25//! equivalent source. The classified-token API
26//! ([`printer::PrintWriter`], [`printer::TokenKind`], [`printer::print_to`])
27//! exposes each emitted chunk together with its source-level category, so
28//! syntax highlighters and rich editors can consume printer output
29//! directly without re-tokenising.
30//!
31//! # Crate layout
32//!
33//! - [`ast`] — abstract syntax tree.
34//! - [`parse_result`] — [`parse_result::ParseResult`] /
35//! [`parse_result::Diagnostic`] returned by the parser, plus stable
36//! diagnostic [`parse_result::codes`].
37//! - [`parser`] — `&str → ParseResult` (feature `parser`).
38//! - [`printer`] — `AST → String` and the classified-token writer
39//! (feature `printer`).
40//! - [`error`] — fatal error types, reserved for future I/O-based entry
41//! points.
42//!
43//! # Cargo features
44//!
45//! The default feature set is `["parser", "printer"]`. Every feature is
46//! additive.
47//!
48//! | Feature | Default | Effect |
49//! | --------- | ------- | ------------------------------------------------------------------------------ |
50//! | `parser` | yes | Compiles the [`parser`] module and its entry points. Pulls in `nom`. |
51//! | `printer` | yes | Compiles the [`printer`] module. No extra dependencies. |
52//! | `serde` | no | Derives `Serialize` / `Deserialize` on the AST, diagnostics and config types. |
53//! | `tracing` | no | Adds `#[tracing::instrument]` on hot-path parser entry points. |
54//!
55//! # Quick start
56//!
57//! Parse a spec, inspect diagnostics, and round-trip back to source:
58//!
59//! ```
60//! use rpm_spec::{parser, printer};
61//!
62//! let src = "Name: foo\nVersion: 1.0\n";
63//! let result = parser::parse_str_with_spans(src);
64//! assert!(result.diagnostics.is_empty());
65//!
66//! let printed = printer::print(&result.spec);
67//! assert!(printed.contains("Name:"));
68//! ```
69//!
70//! # Generic `T` parameter
71//!
72//! Every "large" AST node carries a `data: T` field; the root is
73//! [`ast::SpecFile<T>`]. `T` defaults to `()`. [`parser::parse_str`] returns
74//! [`parse_result::ParseResult`]`<()>`, while
75//! [`parser::parse_str_with_spans`] populates `T` with [`ast::Span`]
76//! (byte offset plus 1-based line and column at both ends). Validators may
77//! choose a richer type to thread their own per-node data (resolved macro
78//! values, validator diagnostic ids, …) and map between representations.
79//!
80//! # Macro names are verbatim
81//!
82//! [`ast::MacroRef::name`], [`ast::MacroDef::name`],
83//! [`ast::BuildCondition::name`], and the `Other` variants of [`ast::Tag`],
84//! [`ast::TagQualifier`], and [`ast::BuiltinMacro`] preserve the exact text
85//! written in the source — case is **not** normalised. This invariant exists
86//! so that downstream validators can match names against
87//! distribution-specific registries.
88//!
89//! # Crate-level lints
90//!
91//! The crate is `#![forbid(unsafe_code)]` (no `unsafe` blocks anywhere) and
92//! `#![deny(missing_docs)]` (every public item must be documented).
93
94#![forbid(unsafe_code)]
95#![warn(missing_debug_implementations)]
96#![deny(missing_docs)]
97
98pub mod ast;
99pub mod error;
100pub mod parse_result;
101
102#[cfg(feature = "parser")]
103pub mod parser;
104
105#[cfg(feature = "printer")]
106pub mod printer;