1use crate::fields::Priority;
2use crate::lossy::relations::Relations;
3use deb822_fast::{FromDeb822, ToDeb822};
4use deb822_fast::{FromDeb822Paragraph, ToDeb822Paragraph};
5
6fn deserialize_yesno(s: &str) -> Result<bool, String> {
7 match s {
8 "yes" => Ok(true),
9 "no" => Ok(false),
10 _ => Err(format!("invalid value for yesno: {}", s)),
11 }
12}
13
14fn serialize_yesno(b: &bool) -> String {
15 if *b {
16 "yes".to_string()
17 } else {
18 "no".to_string()
19 }
20}
21
22#[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)]
24pub struct Source {
25 #[deb822(field = "Source")]
26 pub name: String,
28 #[deb822(field = "Build-Depends")]
29 pub build_depends: Option<Relations>,
31 #[deb822(field = "Build-Depends-Indep")]
32 pub build_depends_indep: Option<Relations>,
34 #[deb822(field = "Build-Depends-Arch")]
35 pub build_depends_arch: Option<Relations>,
37 #[deb822(field = "Build-Conflicts")]
38 pub build_conflicts: Option<Relations>,
40 #[deb822(field = "Build-Conflicts-Indep")]
41 pub build_conflicts_indep: Option<Relations>,
43 #[deb822(field = "Build-Conflicts-Arch")]
44 pub build_conflicts_arch: Option<Relations>,
46 #[deb822(field = "Standards-Version")]
47 pub standards_version: Option<String>,
49 #[deb822(field = "Homepage")]
50 pub homepage: Option<url::Url>,
52 #[deb822(field = "Section")]
53 pub section: Option<String>,
55 #[deb822(field = "Priority")]
56 pub priority: Option<Priority>,
58 #[deb822(field = "Maintainer")]
59 pub maintainer: Option<String>,
61 #[deb822(field = "Uploaders")]
62 pub uploaders: Option<String>,
64 #[deb822(field = "Architecture")]
65 pub architecture: Option<String>,
67 #[deb822(field = "Rules-Requires-Root", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
68 pub rules_requires_root: Option<bool>,
70 #[deb822(field = "Testsuite")]
71 pub testsuite: Option<String>,
73 #[deb822(field = "Vcs-Git")]
74 pub vcs_git: Option<crate::vcs::ParsedVcs>,
76 #[deb822(field = "Vcs-Browser")]
77 pub vcs_browser: Option<url::Url>,
79}
80
81impl std::fmt::Display for Source {
82 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
83 let para: deb822_fast::Paragraph = self.to_paragraph();
84 write!(f, "{}", para)?;
85 Ok(())
86 }
87}
88
89impl Source {
90 pub fn is_qa_package(&self) -> bool {
95 self.maintainer
96 .as_deref()
97 .and_then(|m| crate::parse_identity(m).ok())
98 .map(|(_, email)| email.eq_ignore_ascii_case("packages@qa.debian.org"))
99 .unwrap_or(false)
100 }
101}
102
103#[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)]
105pub struct Binary {
106 #[deb822(field = "Package")]
107 pub name: String,
109 #[deb822(field = "Depends")]
110 pub depends: Option<Relations>,
112 #[deb822(field = "Recommends")]
113 pub recommends: Option<Relations>,
115 #[deb822(field = "Suggests")]
116 pub suggests: Option<Relations>,
118 #[deb822(field = "Enhances")]
119 pub enhances: Option<Relations>,
121 #[deb822(field = "Pre-Depends")]
122 pub pre_depends: Option<Relations>,
124 #[deb822(field = "Breaks")]
125 pub breaks: Option<Relations>,
127 #[deb822(field = "Conflicts")]
128 pub conflicts: Option<Relations>,
130 #[deb822(field = "Replaces")]
131 pub replaces: Option<Relations>,
133 #[deb822(field = "Provides")]
134 pub provides: Option<Relations>,
136 #[deb822(field = "Built-Using")]
137 pub built_using: Option<Relations>,
139 #[deb822(field = "Static-Built-Using")]
140 pub static_built_using: Option<Relations>,
142 #[deb822(field = "Architecture")]
143 pub architecture: Option<String>,
145 #[deb822(field = "Section")]
146 pub section: Option<String>,
148 #[deb822(field = "Priority")]
149 pub priority: Option<Priority>,
151 #[deb822(field = "Multi-Arch")]
152 pub multi_arch: Option<crate::fields::MultiArch>,
154 #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
155 pub essential: Option<bool>,
157 #[deb822(field = "Description")]
158 pub description: Option<String>,
160}
161
162impl std::fmt::Display for Binary {
163 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
164 let para: deb822_fast::Paragraph = self.to_paragraph();
165 write!(f, "{}", para)?;
166 Ok(())
167 }
168}
169
170#[derive(Clone, PartialEq)]
172pub struct Control {
173 pub source: Source,
175 pub binaries: Vec<Binary>,
177}
178
179impl std::fmt::Display for Control {
180 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
181 write!(f, "{}", self.source)?;
182 for binary in &self.binaries {
183 f.write_str("\n")?;
184 write!(f, "{}", binary)?;
185 }
186 Ok(())
187 }
188}
189
190impl std::str::FromStr for Control {
191 type Err = String;
192
193 fn from_str(s: &str) -> Result<Self, Self::Err> {
194 let deb822: deb822_fast::Deb822 = s.parse().map_err(|e| format!("parse error: {}", e))?;
195
196 let mut source: Option<Source> = None;
197 let mut binaries: Vec<Binary> = Vec::new();
198
199 for para in deb822.iter() {
200 if para.get("Package").is_some() {
201 let binary: Binary = Binary::from_paragraph(para)?;
202 binaries.push(binary);
203 } else if para.get("Source").is_some() {
204 if source.is_some() {
205 return Err("more than one source paragraph".to_string());
206 }
207 source = Some(Source::from_paragraph(para)?);
208 } else {
209 return Err("paragraph without Source or Package field".to_string());
210 }
211 }
212
213 Ok(Control {
214 source: source.ok_or_else(|| "no source paragraph".to_string())?,
215 binaries,
216 })
217 }
218}
219
220impl Default for Control {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226impl Control {
227 pub fn new() -> Self {
229 Self {
230 source: Source::default(),
231 binaries: Vec::new(),
232 }
233 }
234
235 pub fn add_binary(&mut self, name: &str) -> &mut Binary {
237 let binary = Binary {
238 name: name.to_string(),
239 ..Default::default()
240 };
241 self.binaries.push(binary);
242 self.binaries.last_mut().unwrap()
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::relations::VersionConstraint;
250 #[test]
251 fn test_parse() {
252 let control: Control = r#"Source: foo
253Section: libs
254Priority: optional
255Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
256Homepage: https://example.com
257
258"#
259 .parse()
260 .unwrap();
261 let source = &control.source;
262
263 assert_eq!(source.name, "foo".to_owned());
264 assert_eq!(source.section, Some("libs".to_owned()));
265 assert_eq!(source.priority, Some(super::Priority::Optional));
266 assert_eq!(
267 source.homepage,
268 Some("https://example.com".parse().unwrap())
269 );
270 let bd = source.build_depends.as_ref().unwrap();
271 let mut entries = bd.iter().collect::<Vec<_>>();
272 assert_eq!(entries.len(), 2);
273 let rel = entries[0].pop().unwrap();
274 assert_eq!(rel.name, "bar");
275 assert_eq!(
276 rel.version,
277 Some((
278 VersionConstraint::GreaterThanEqual,
279 "1.0.0".parse().unwrap()
280 ))
281 );
282 let rel = entries[1].pop().unwrap();
283 assert_eq!(rel.name, "baz");
284 assert_eq!(
285 rel.version,
286 Some((
287 VersionConstraint::GreaterThanEqual,
288 "1.0.0".parse().unwrap()
289 ))
290 );
291 }
292
293 #[test]
294 fn test_description() {
295 let control: Control = r#"Source: foo
296
297Package: foo
298Description: this is the short description
299 And the longer one
300 .
301 is on the next lines
302"#
303 .parse()
304 .unwrap();
305 let binary = &control.binaries[0];
306 assert_eq!(
307 binary.description,
308 Some(
309 "this is the short description\nAnd the longer one\n.\nis on the next lines"
310 .to_owned()
311 )
312 );
313 }
314
315 #[test]
316 fn test_is_qa_package() {
317 let control: Control = r#"Source: foo
318Maintainer: Debian QA Group <packages@qa.debian.org>
319"#
320 .parse()
321 .unwrap();
322 assert!(control.source.is_qa_package());
323
324 let control: Control = r#"Source: foo
325Maintainer: Jane Packager <jane@example.com>
326"#
327 .parse()
328 .unwrap();
329 assert!(!control.source.is_qa_package());
330
331 let control: Control = r#"Source: foo
332"#
333 .parse()
334 .unwrap();
335 assert!(!control.source.is_qa_package());
336 }
337}