oma_debcontrol/lib.rs
1//! A crate for parsing [Debian control files].
2//!
3//! [Debian control files]: https://www.debian.org/doc/debian-policy/ch-controlfields.html
4//!
5//! # Parse complete input
6//! The [`parse_str`](fn.parse_str.html) function will parse a complete control file into a vec of
7//! [`Paragraph`](struct.Paragraph.html) values:
8//! ```
9//! # use debcontrol::{Paragraph, Field, parse_str};
10//! # fn main() -> Result<(), debcontrol::SyntaxError<'static>> {
11//! let paragraphs = parse_str("
12//! a-field: with a value
13//! another-field: with a...
14//! ...continuation
15//! ")?;
16//!
17//! assert_eq!(paragraphs, vec![Paragraph {
18//! fields: vec![
19//! Field { name: "a-field", value: String::from("with a value") },
20//! Field { name: "another-field", value: String::from("with a...\n...continuation") }
21//! ]
22//! }]);
23//! # Ok(())
24//! # }
25//! ```
26//!
27//! # Parse streaming input
28//! The [`parse_streaming`](fn.parse_streaming.html) and [`parse_finish`](fn.parse_finish.html)
29//! functions can be used to parse a control file incrementally:
30//! ```
31//! # use debcontrol::{Paragraph, Field, Streaming, parse_streaming, parse_finish};
32//! # fn main() -> Result<(), debcontrol::SyntaxError<'static>> {
33//! let result = parse_streaming("field: value")?;
34//! assert_eq!(result, Streaming::Incomplete);
35//!
36//! let result = parse_streaming("field: value\n\n")?;
37//! assert_eq!(result, Streaming::Item(("", Paragraph {
38//! fields: vec![
39//! Field { name: "field", value: String::from("value") }
40//! ]
41//! })));
42//!
43//! let result = parse_finish("remaining: input")?;
44//! assert_eq!(result, Some(Paragraph {
45//! fields: vec![
46//! Field { name: "remaining", value: String::from("input") }
47//! ]
48//! }));
49//! # Ok(())
50//! # }
51//! ```
52
53#![cfg_attr(not(feature = "std"), no_std)]
54
55extern crate alloc;
56
57use alloc::{string::String, vec::Vec};
58use core::fmt;
59
60mod buf_parse;
61mod parser;
62pub use buf_parse::*;
63#[cfg(test)]
64mod tests;
65
66/// A single field in a control file.
67///
68/// All types of fields [(simple, folded, multiline)] are treated the same: all individual value
69/// lines (the part after the colon as well as any continuation lines) are trimmed and concatenated
70/// together using a single newline character. This means that field values never begin or end with
71/// a newline character, but internal newlines are preserved (and may be transformed or ignored when
72/// dealing with folded fields). Leading whitespace and trailing whitespace is always removed,
73/// including in continuation lines.
74///
75/// [(simple, folded, multiline)]: https://www.debian.org/doc/debian-policy/ch-controlfields.html#syntax-of-control-files
76#[derive(Debug, PartialEq, Eq, Hash, Clone)]
77pub struct Field<'a> {
78 pub name: &'a str,
79 pub value: String,
80}
81
82/// A paragraph in a control file.
83#[derive(Debug, PartialEq, Eq, Hash, Clone)]
84pub struct Paragraph<'a> {
85 pub fields: Vec<Field<'a>>,
86}
87
88impl Paragraph<'_> {
89 /// Create a new `Paragraph` from the given fields.
90 fn new(fields: Vec<Field>) -> Paragraph {
91 Paragraph { fields }
92 }
93}
94
95#[cfg(not(feature = "verbose-errors"))]
96type ErrorType<'a> = (&'a str, nom::error::ErrorKind);
97#[cfg(feature = "verbose-errors")]
98type ErrorType<'a> = nom::error::VerboseError<&'a str>;
99
100/// A parsing syntax error.
101///
102/// This is an opaque error type that wraps an underlying syntax error. The format and level of
103/// detail of the error output depends on the `verbose-errors` feature.
104#[derive(Debug)]
105pub struct SyntaxError<'a> {
106 /// The parser input that caused the error.
107 pub input: &'a str,
108 /// The underlying nom error.
109 pub underlying: ErrorType<'a>,
110}
111
112impl<'a> fmt::Display for SyntaxError<'a> {
113 #[cfg(not(feature = "verbose-errors"))]
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(
116 f,
117 "{} at '{}'",
118 self.underlying.1.description(),
119 self.underlying.0
120 )
121 }
122
123 #[cfg(feature = "verbose-errors")]
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(
126 f,
127 "{}",
128 nom::error::convert_error(self.input, self.underlying.clone())
129 )
130 }
131}
132
133#[cfg(feature = "std")]
134impl<'a> std::error::Error for SyntaxError<'a> {}
135
136/// A return value from the streaming parser.
137#[derive(Debug, PartialEq, Eq, Clone, Copy)]
138pub enum Streaming<T> {
139 /// An item returned from the parser.
140 Item(T),
141 /// More input is needed to make a parsing decision.
142 Incomplete,
143}
144
145/// Attempt to parse a paragraph from the given input.
146///
147/// This function returns a paragraph and any remaining input if a paragraph can be unambiguously
148/// parsed. If there's no complete paragraph in the input,
149/// [`Streaming::Incomplete`](enum.Streaming.html#variant.Incomplete) is returned. In that case,
150/// you need to either:
151///
152/// * read more data from the source and try again or
153/// * if there's no more data in the source, call [`parse_finish`](fn.parse_finish.html) with all
154/// remaining input.
155pub fn parse_streaming(input: &str) -> Result<Streaming<(&str, Paragraph)>, SyntaxError> {
156 match parser::streaming::paragraph::<ErrorType>(input) {
157 Ok((remaining, Some(item))) => Ok(Streaming::Item((remaining, item))),
158 Ok((_, None)) => Ok(Streaming::Incomplete),
159 Err(nom::Err::Incomplete(_)) => Ok(Streaming::Incomplete),
160 Err(nom::Err::Error(underlying)) => Err(SyntaxError { input, underlying }),
161 Err(nom::Err::Failure(underlying)) => Err(SyntaxError { input, underlying }),
162 }
163}
164
165/// Finish parsing the streaming input and return the final remaining paragraph, if any.
166///
167/// This is the companion function to [`parse_streaming`](fn.parse_streaming.html). Once all input
168/// has been read and `parse_streaming` returns
169/// [`Incomplete`](enum.Streaming.html#variant.Incomplete), call this function with any remaining
170/// input to parse the final remaining paragraph. If the remaining input is only whitespace and
171/// comments, `None` is returned.
172pub fn parse_finish(input: &str) -> Result<Option<Paragraph>, SyntaxError> {
173 match parser::complete::paragraph::<ErrorType>(input) {
174 Ok((_, item)) => Ok(item),
175 Err(nom::Err::Error(underlying)) => Err(SyntaxError { input, underlying }),
176 Err(nom::Err::Failure(underlying)) => Err(SyntaxError { input, underlying }),
177 Err(nom::Err::Incomplete(_)) => unimplemented!(),
178 }
179}
180
181/// Parse the given complete control file into paragraphs.
182///
183/// This function does not work for partial input. The entire control file must be passed in at
184/// once.
185pub fn parse_str(input: &str) -> Result<Vec<Paragraph>, SyntaxError> {
186 let mut paragraphs = Vec::new();
187
188 let mut input = input;
189 while let Streaming::Item((remaining, item)) = parse_streaming(input)? {
190 paragraphs.push(item);
191 input = remaining;
192 }
193 if let Some(paragraph) = parse_finish(input)? {
194 paragraphs.push(paragraph);
195 }
196
197 Ok(paragraphs)
198}