debian_control/
lib.rs

1#![deny(missing_docs)]
2// Until we drop support for PyO3 0.22, allow use of deprecated functions.
3#![allow(deprecated)]
4
5//! Parser for Debian control files.
6//!
7//! This crate provides a parser for Debian control files.
8//!
9//! # Example
10//!
11//! ```rust
12//! use debian_control::lossy::Control;
13//! use debian_control::fields::Priority;
14//! use std::fs::File;
15//!
16//! let mut control = Control::new();
17//! let mut source = &mut control.source;
18//! source.name = "hello".to_string();
19//! source.section = Some("rust".to_string());
20//!
21//! let mut binary = control.add_binary("hello");
22//! binary.architecture = Some("amd64".to_string());
23//! binary.priority = Some(Priority::Optional);
24//! binary.description = Some("Hello, world!".to_string());
25//!
26//! assert_eq!(control.to_string(), r#"Source: hello
27//! Section: rust
28//!
29//! Package: hello
30//! Architecture: amd64
31//! Priority: optional
32//! Description: Hello, world!
33//! "#);
34//! ```
35//!
36//! See the ``lossless`` module for a parser that preserves all comments and formatting, and
37//! as well as allowing inline errors.
38pub mod lossy;
39#[cfg(feature = "lossless")]
40pub use lossless::control::{Binary, Control, Source};
41pub mod fields;
42pub use fields::*;
43#[cfg(feature = "lossless")]
44pub mod lossless;
45#[cfg(feature = "lossless")]
46pub use lossless::apt;
47#[cfg(feature = "lossless")]
48pub use lossless::changes;
49#[cfg(feature = "lossless")]
50pub use lossless::control;
51#[cfg(feature = "lossless")]
52pub mod pgp;
53pub mod relations;
54pub mod vcs;
55
56use std::borrow::Cow;
57
58/// Error type for parsing an identity string.
59#[derive(Debug, PartialEq)]
60pub enum ParseIdentityError {
61    /// No email address found.
62    NoEmail,
63}
64
65impl std::fmt::Display for ParseIdentityError {
66    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
67        match self {
68            ParseIdentityError::NoEmail => write!(f, "No email found"),
69        }
70    }
71}
72
73impl std::error::Error for ParseIdentityError {}
74
75/// Parse an identity string into a name and an email address.
76///
77/// The input string should be in the format `Name <email>`. If the email is missing, an error is
78/// returned.
79///
80/// # Example
81/// ```
82/// use debian_control::parse_identity;
83/// assert_eq!(parse_identity("Joe Example <joe@example.com>"), Ok(("Joe Example", "joe@example.com")));
84/// ```
85///
86/// # Arguments
87/// * `s` - The input string.
88///
89/// # Returns
90/// A tuple with the name and the email address.
91pub fn parse_identity(s: &str) -> Result<(&str, &str), ParseIdentityError> {
92    // Support Name <email> and email, but ensure email contains an "@".
93    if let Some((name, email)) = s.split_once('<') {
94        if let Some(email) = email.strip_suffix('>') {
95            Ok((name.trim(), email.trim()))
96        } else {
97            Err(ParseIdentityError::NoEmail)
98        }
99    } else if s.contains('@') {
100        Ok(("", s.trim()))
101    } else {
102        Err(ParseIdentityError::NoEmail)
103    }
104}
105
106/// A trait for looking up versions of packages.
107pub trait VersionLookup {
108    /// Look up the version of a package.
109    fn lookup_version<'a>(
110        &'a self,
111        package: &'_ str,
112    ) -> Option<std::borrow::Cow<'a, debversion::Version>>;
113}
114
115impl VersionLookup for std::collections::HashMap<String, debversion::Version> {
116    fn lookup_version<'a>(&'a self, package: &str) -> Option<Cow<'a, debversion::Version>> {
117        self.get(package).map(Cow::Borrowed)
118    }
119}
120
121impl<F> VersionLookup for F
122where
123    F: Fn(&str) -> Option<debversion::Version>,
124{
125    fn lookup_version<'a>(&'a self, name: &str) -> Option<Cow<'a, debversion::Version>> {
126        self(name).map(Cow::Owned)
127    }
128}
129
130impl VersionLookup for (String, debversion::Version) {
131    fn lookup_version<'a>(&'a self, name: &str) -> Option<Cow<'a, debversion::Version>> {
132        if name == self.0 {
133            Some(Cow::Borrowed(&self.1))
134        } else {
135            None
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_parse_identity() {
146        assert_eq!(
147            parse_identity("Joe Example <joe@example.com>"),
148            Ok(("Joe Example", "joe@example.com"))
149        );
150        assert_eq!(
151            parse_identity("joe@example.com"),
152            Ok(("", "joe@example.com"))
153        );
154        assert_eq!(parse_identity("somebody"), Err(ParseIdentityError::NoEmail));
155    }
156}