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/// Let's start with defining all kinds of tokens and
79/// composite nodes.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81#[allow(non_camel_case_types)]
82#[repr(u16)]
83#[allow(missing_docs)]
84pub enum SyntaxKind {
85    IDENT = 0, // package name
86    COLON,     // :
87    PIPE,
88    COMMA,      // ,
89    L_PARENS,   // (
90    R_PARENS,   // )
91    L_BRACKET,  // [
92    R_BRACKET,  // ]
93    NOT,        // !
94    L_ANGLE,    // <
95    R_ANGLE,    // >
96    EQUAL,      // =
97    WHITESPACE, // whitespace
98    NEWLINE,    // newline
99    DOLLAR,     // $
100    L_CURLY,
101    R_CURLY,
102    ERROR, // as well as errors
103
104    // composite nodes
105    ROOT,       // The entire file
106    ENTRY,      // A single entry
107    RELATION,   // An alternative in a dependency
108    ARCHQUAL,   // An architecture qualifier
109    VERSION,    // A version constraint
110    CONSTRAINT, // (">=", "<=", "=", ">>", "<<")
111    ARCHITECTURES,
112    PROFILES,
113    SUBSTVAR,
114}
115
116/// Convert our `SyntaxKind` into the rowan `SyntaxKind`.
117#[cfg(feature = "lossless")]
118impl From<SyntaxKind> for rowan::SyntaxKind {
119    fn from(kind: SyntaxKind) -> Self {
120        Self(kind as u16)
121    }
122}
123
124/// A lexer for relations strings.
125pub struct Lexer<'a> {
126    input: Peekable<Chars<'a>>,
127}
128
129impl<'a> Lexer<'a> {
130    /// Create a new lexer for the given input.
131    pub fn new(input: &'a str) -> Self {
132        Lexer {
133            input: input.chars().peekable(),
134        }
135    }
136
137    fn is_whitespace(c: char) -> bool {
138        c == ' ' || c == '\t' || c == '\r'
139    }
140
141    fn is_valid_ident_char(c: char) -> bool {
142        c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '+' || c == '~'
143    }
144
145    fn read_while<F>(&mut self, predicate: F) -> String
146    where
147        F: Fn(char) -> bool,
148    {
149        let mut result = String::new();
150        while let Some(&c) = self.input.peek() {
151            if predicate(c) {
152                result.push(c);
153                self.input.next();
154            } else {
155                break;
156            }
157        }
158        result
159    }
160
161    fn next_token(&mut self) -> Option<(SyntaxKind, String)> {
162        if let Some(&c) = self.input.peek() {
163            match c {
164                ':' => {
165                    self.input.next();
166                    Some((SyntaxKind::COLON, c.to_string()))
167                }
168                '|' => {
169                    self.input.next();
170                    Some((SyntaxKind::PIPE, c.to_string()))
171                }
172                ',' => {
173                    self.input.next();
174                    Some((SyntaxKind::COMMA, c.to_string()))
175                }
176                '(' => {
177                    self.input.next();
178                    Some((SyntaxKind::L_PARENS, c.to_string()))
179                }
180                ')' => {
181                    self.input.next();
182                    Some((SyntaxKind::R_PARENS, c.to_string()))
183                }
184                '[' => {
185                    self.input.next();
186                    Some((SyntaxKind::L_BRACKET, c.to_string()))
187                }
188                ']' => {
189                    self.input.next();
190                    Some((SyntaxKind::R_BRACKET, c.to_string()))
191                }
192                '!' => {
193                    self.input.next();
194                    Some((SyntaxKind::NOT, c.to_string()))
195                }
196                '$' => {
197                    self.input.next();
198                    Some((SyntaxKind::DOLLAR, c.to_string()))
199                }
200                '{' => {
201                    self.input.next();
202                    Some((SyntaxKind::L_CURLY, c.to_string()))
203                }
204                '}' => {
205                    self.input.next();
206                    Some((SyntaxKind::R_CURLY, c.to_string()))
207                }
208                '<' => {
209                    self.input.next();
210                    Some((SyntaxKind::L_ANGLE, c.to_string()))
211                }
212                '>' => {
213                    self.input.next();
214                    Some((SyntaxKind::R_ANGLE, c.to_string()))
215                }
216                '=' => {
217                    self.input.next();
218                    Some((SyntaxKind::EQUAL, c.to_string()))
219                }
220                '\n' => {
221                    self.input.next();
222                    Some((SyntaxKind::NEWLINE, c.to_string()))
223                }
224                _ if Self::is_whitespace(c) => {
225                    let whitespace = self.read_while(Self::is_whitespace);
226                    Some((SyntaxKind::WHITESPACE, whitespace))
227                }
228                // TODO: separate handling for package names and versions?
229                _ if Self::is_valid_ident_char(c) => {
230                    let key = self.read_while(Self::is_valid_ident_char);
231                    Some((SyntaxKind::IDENT, key))
232                }
233                _ => {
234                    self.input.next();
235                    Some((SyntaxKind::ERROR, c.to_string()))
236                }
237            }
238        } else {
239            None
240        }
241    }
242}
243
244impl Iterator for Lexer<'_> {
245    type Item = (SyntaxKind, String);
246
247    fn next(&mut self) -> Option<Self::Item> {
248        self.next_token()
249    }
250}
251
252pub(crate) fn lex(input: &str) -> Vec<(SyntaxKind, String)> {
253    let mut lexer = Lexer::new(input);
254    lexer.by_ref().collect::<Vec<_>>()
255}