Skip to main content

debian_control/
relations.rs

1//! Parsing of Debian relations strings.
2use std::iter::Peekable;
3use std::str::Chars;
4
5/// Build profile for a package.
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub enum BuildProfile {
8    /// A build profile that is enabled.
9    Enabled(String),
10
11    /// A build profile that is disabled.
12    Disabled(String),
13}
14
15impl std::fmt::Display for BuildProfile {
16    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
17        match self {
18            BuildProfile::Enabled(s) => f.write_str(s),
19            BuildProfile::Disabled(s) => write!(f, "!{}", s),
20        }
21    }
22}
23
24impl std::str::FromStr for BuildProfile {
25    type Err = String;
26
27    fn from_str(s: &str) -> Result<Self, Self::Err> {
28        if let Some(s) = s.strip_prefix('!') {
29            Ok(BuildProfile::Disabled(s.to_string()))
30        } else {
31            Ok(BuildProfile::Enabled(s.to_string()))
32        }
33    }
34}
35
36/// Constraint on a Debian package version.
37#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub enum VersionConstraint {
39    /// <<
40    LessThan, // <<
41    /// <=
42    LessThanEqual, // <=
43    /// =
44    Equal, // =
45    /// >>
46    GreaterThan, // >>
47    /// >=
48    GreaterThanEqual, // >=
49}
50
51impl std::str::FromStr for VersionConstraint {
52    type Err = String;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        match s {
56            ">=" => Ok(VersionConstraint::GreaterThanEqual),
57            "<=" => Ok(VersionConstraint::LessThanEqual),
58            "=" => Ok(VersionConstraint::Equal),
59            ">>" => Ok(VersionConstraint::GreaterThan),
60            "<<" => Ok(VersionConstraint::LessThan),
61            _ => Err(format!("Invalid version constraint: {}", s)),
62        }
63    }
64}
65
66impl std::fmt::Display for VersionConstraint {
67    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68        match self {
69            VersionConstraint::GreaterThanEqual => f.write_str(">="),
70            VersionConstraint::LessThanEqual => f.write_str("<="),
71            VersionConstraint::Equal => f.write_str("="),
72            VersionConstraint::GreaterThan => f.write_str(">>"),
73            VersionConstraint::LessThan => f.write_str("<<"),
74        }
75    }
76}
77
78#[cfg(feature = "serde")]
79impl serde::Serialize for VersionConstraint {
80    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81        serializer.serialize_str(&self.to_string())
82    }
83}
84
85#[cfg(feature = "serde")]
86impl<'de> serde::Deserialize<'de> for VersionConstraint {
87    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
88        let s = String::deserialize(deserializer)?;
89        s.parse().map_err(serde::de::Error::custom)
90    }
91}
92
93#[cfg(all(test, feature = "serde"))]
94mod version_constraint_serde_tests {
95    use super::VersionConstraint;
96
97    #[test]
98    fn test_serialize() {
99        assert_eq!(
100            serde_json::to_string(&VersionConstraint::GreaterThanEqual).unwrap(),
101            "\">=\"",
102        );
103        assert_eq!(
104            serde_json::to_string(&VersionConstraint::LessThanEqual).unwrap(),
105            "\"<=\"",
106        );
107        assert_eq!(
108            serde_json::to_string(&VersionConstraint::Equal).unwrap(),
109            "\"=\"",
110        );
111        assert_eq!(
112            serde_json::to_string(&VersionConstraint::GreaterThan).unwrap(),
113            "\">>\"",
114        );
115        assert_eq!(
116            serde_json::to_string(&VersionConstraint::LessThan).unwrap(),
117            "\"<<\"",
118        );
119    }
120
121    #[test]
122    fn test_deserialize() {
123        assert_eq!(
124            serde_json::from_str::<VersionConstraint>("\">=\"").unwrap(),
125            VersionConstraint::GreaterThanEqual,
126        );
127        assert_eq!(
128            serde_json::from_str::<VersionConstraint>("\"<=\"").unwrap(),
129            VersionConstraint::LessThanEqual,
130        );
131        assert_eq!(
132            serde_json::from_str::<VersionConstraint>("\"=\"").unwrap(),
133            VersionConstraint::Equal,
134        );
135        assert_eq!(
136            serde_json::from_str::<VersionConstraint>("\">>\"").unwrap(),
137            VersionConstraint::GreaterThan,
138        );
139        assert_eq!(
140            serde_json::from_str::<VersionConstraint>("\"<<\"").unwrap(),
141            VersionConstraint::LessThan,
142        );
143    }
144
145    #[test]
146    fn test_deserialize_invalid() {
147        assert!(serde_json::from_str::<VersionConstraint>("\"!!\"").is_err());
148    }
149}
150
151/// Let's start with defining all kinds of tokens and
152/// composite nodes.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
154#[allow(non_camel_case_types)]
155#[repr(u16)]
156#[allow(missing_docs)]
157pub enum SyntaxKind {
158    IDENT = 0, // package name
159    COLON,     // :
160    PIPE,
161    COMMA,      // ,
162    L_PARENS,   // (
163    R_PARENS,   // )
164    L_BRACKET,  // [
165    R_BRACKET,  // ]
166    NOT,        // !
167    L_ANGLE,    // <
168    R_ANGLE,    // >
169    EQUAL,      // =
170    WHITESPACE, // whitespace
171    NEWLINE,    // newline
172    DOLLAR,     // $
173    L_CURLY,
174    R_CURLY,
175    COMMENT, // comment line starting with #
176    ERROR,   // as well as errors
177
178    // composite nodes
179    ROOT,       // The entire file
180    ENTRY,      // A single entry
181    RELATION,   // An alternative in a dependency
182    ARCHQUAL,   // An architecture qualifier
183    VERSION,    // A version constraint
184    CONSTRAINT, // (">=", "<=", "=", ">>", "<<")
185    ARCHITECTURES,
186    PROFILES,
187    SUBSTVAR,
188}
189
190/// Convert our `SyntaxKind` into the rowan `SyntaxKind`.
191#[cfg(feature = "lossless")]
192impl From<SyntaxKind> for rowan::SyntaxKind {
193    fn from(kind: SyntaxKind) -> Self {
194        Self(kind as u16)
195    }
196}
197
198/// A lexer for relations strings.
199pub struct Lexer<'a> {
200    input: Peekable<Chars<'a>>,
201}
202
203impl<'a> Lexer<'a> {
204    /// Create a new lexer for the given input.
205    pub fn new(input: &'a str) -> Self {
206        Lexer {
207            input: input.chars().peekable(),
208        }
209    }
210
211    fn is_whitespace(c: char) -> bool {
212        c == ' ' || c == '\t' || c == '\r'
213    }
214
215    fn is_valid_ident_char(c: char) -> bool {
216        c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '+' || c == '~'
217    }
218
219    fn read_while<F>(&mut self, predicate: F) -> String
220    where
221        F: Fn(char) -> bool,
222    {
223        let mut result = String::new();
224        while let Some(&c) = self.input.peek() {
225            if predicate(c) {
226                result.push(c);
227                self.input.next();
228            } else {
229                break;
230            }
231        }
232        result
233    }
234
235    fn next_token(&mut self) -> Option<(SyntaxKind, String)> {
236        if let Some(&c) = self.input.peek() {
237            match c {
238                ':' => {
239                    self.input.next();
240                    Some((SyntaxKind::COLON, c.to_string()))
241                }
242                '|' => {
243                    self.input.next();
244                    Some((SyntaxKind::PIPE, c.to_string()))
245                }
246                ',' => {
247                    self.input.next();
248                    Some((SyntaxKind::COMMA, c.to_string()))
249                }
250                '(' => {
251                    self.input.next();
252                    Some((SyntaxKind::L_PARENS, c.to_string()))
253                }
254                ')' => {
255                    self.input.next();
256                    Some((SyntaxKind::R_PARENS, c.to_string()))
257                }
258                '[' => {
259                    self.input.next();
260                    Some((SyntaxKind::L_BRACKET, c.to_string()))
261                }
262                ']' => {
263                    self.input.next();
264                    Some((SyntaxKind::R_BRACKET, c.to_string()))
265                }
266                '!' => {
267                    self.input.next();
268                    Some((SyntaxKind::NOT, c.to_string()))
269                }
270                '$' => {
271                    self.input.next();
272                    Some((SyntaxKind::DOLLAR, c.to_string()))
273                }
274                '{' => {
275                    self.input.next();
276                    Some((SyntaxKind::L_CURLY, c.to_string()))
277                }
278                '}' => {
279                    self.input.next();
280                    Some((SyntaxKind::R_CURLY, c.to_string()))
281                }
282                '<' => {
283                    self.input.next();
284                    Some((SyntaxKind::L_ANGLE, c.to_string()))
285                }
286                '>' => {
287                    self.input.next();
288                    Some((SyntaxKind::R_ANGLE, c.to_string()))
289                }
290                '=' => {
291                    self.input.next();
292                    Some((SyntaxKind::EQUAL, c.to_string()))
293                }
294                '\n' => {
295                    self.input.next();
296                    Some((SyntaxKind::NEWLINE, c.to_string()))
297                }
298                '#' => {
299                    let comment = self.read_while(|c| c != '\n');
300                    Some((SyntaxKind::COMMENT, comment))
301                }
302                _ if Self::is_whitespace(c) => {
303                    let whitespace = self.read_while(Self::is_whitespace);
304                    Some((SyntaxKind::WHITESPACE, whitespace))
305                }
306                // TODO: separate handling for package names and versions?
307                _ if Self::is_valid_ident_char(c) => {
308                    let key = self.read_while(Self::is_valid_ident_char);
309                    Some((SyntaxKind::IDENT, key))
310                }
311                _ => {
312                    self.input.next();
313                    Some((SyntaxKind::ERROR, c.to_string()))
314                }
315            }
316        } else {
317            None
318        }
319    }
320}
321
322impl Iterator for Lexer<'_> {
323    type Item = (SyntaxKind, String);
324
325    fn next(&mut self) -> Option<Self::Item> {
326        self.next_token()
327    }
328}
329
330pub(crate) fn lex(input: &str) -> Vec<(SyntaxKind, String)> {
331    let mut lexer = Lexer::new(input);
332    lexer.by_ref().collect::<Vec<_>>()
333}