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
89#[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)]
91pub struct Binary {
92 #[deb822(field = "Package")]
93 pub name: String,
95 #[deb822(field = "Depends")]
96 pub depends: Option<Relations>,
98 #[deb822(field = "Recommends")]
99 pub recommends: Option<Relations>,
101 #[deb822(field = "Suggests")]
102 pub suggests: Option<Relations>,
104 #[deb822(field = "Enhances")]
105 pub enhances: Option<Relations>,
107 #[deb822(field = "Pre-Depends")]
108 pub pre_depends: Option<Relations>,
110 #[deb822(field = "Breaks")]
111 pub breaks: Option<Relations>,
113 #[deb822(field = "Conflicts")]
114 pub conflicts: Option<Relations>,
116 #[deb822(field = "Replaces")]
117 pub replaces: Option<Relations>,
119 #[deb822(field = "Provides")]
120 pub provides: Option<Relations>,
122 #[deb822(field = "Built-Using")]
123 pub built_using: Option<Relations>,
125 #[deb822(field = "Static-Built-Using")]
126 pub static_built_using: Option<Relations>,
128 #[deb822(field = "Architecture")]
129 pub architecture: Option<String>,
131 #[deb822(field = "Section")]
132 pub section: Option<String>,
134 #[deb822(field = "Priority")]
135 pub priority: Option<Priority>,
137 #[deb822(field = "Multi-Arch")]
138 pub multi_arch: Option<crate::fields::MultiArch>,
140 #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
141 pub essential: Option<bool>,
143 #[deb822(field = "Description")]
144 pub description: Option<String>,
146}
147
148impl std::fmt::Display for Binary {
149 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
150 let para: deb822_fast::Paragraph = self.to_paragraph();
151 write!(f, "{}", para)?;
152 Ok(())
153 }
154}
155
156#[derive(Clone, PartialEq)]
158pub struct Control {
159 pub source: Source,
161 pub binaries: Vec<Binary>,
163}
164
165impl std::fmt::Display for Control {
166 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
167 write!(f, "{}", self.source)?;
168 for binary in &self.binaries {
169 f.write_str("\n")?;
170 write!(f, "{}", binary)?;
171 }
172 Ok(())
173 }
174}
175
176impl std::str::FromStr for Control {
177 type Err = String;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
180 let deb822: deb822_fast::Deb822 = s.parse().map_err(|e| format!("parse error: {}", e))?;
181
182 let mut source: Option<Source> = None;
183 let mut binaries: Vec<Binary> = Vec::new();
184
185 for para in deb822.iter() {
186 if para.get("Package").is_some() {
187 let binary: Binary = Binary::from_paragraph(para)?;
188 binaries.push(binary);
189 } else if para.get("Source").is_some() {
190 if source.is_some() {
191 return Err("more than one source paragraph".to_string());
192 }
193 source = Some(Source::from_paragraph(para)?);
194 } else {
195 return Err("paragraph without Source or Package field".to_string());
196 }
197 }
198
199 Ok(Control {
200 source: source.ok_or_else(|| "no source paragraph".to_string())?,
201 binaries,
202 })
203 }
204}
205
206impl Default for Control {
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212impl Control {
213 pub fn new() -> Self {
215 Self {
216 source: Source::default(),
217 binaries: Vec::new(),
218 }
219 }
220
221 pub fn add_binary(&mut self, name: &str) -> &mut Binary {
223 let binary = Binary {
224 name: name.to_string(),
225 ..Default::default()
226 };
227 self.binaries.push(binary);
228 self.binaries.last_mut().unwrap()
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::relations::VersionConstraint;
236 #[test]
237 fn test_parse() {
238 let control: Control = r#"Source: foo
239Section: libs
240Priority: optional
241Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
242Homepage: https://example.com
243
244"#
245 .parse()
246 .unwrap();
247 let source = &control.source;
248
249 assert_eq!(source.name, "foo".to_owned());
250 assert_eq!(source.section, Some("libs".to_owned()));
251 assert_eq!(source.priority, Some(super::Priority::Optional));
252 assert_eq!(
253 source.homepage,
254 Some("https://example.com".parse().unwrap())
255 );
256 let bd = source.build_depends.as_ref().unwrap();
257 let mut entries = bd.iter().collect::<Vec<_>>();
258 assert_eq!(entries.len(), 2);
259 let rel = entries[0].pop().unwrap();
260 assert_eq!(rel.name, "bar");
261 assert_eq!(
262 rel.version,
263 Some((
264 VersionConstraint::GreaterThanEqual,
265 "1.0.0".parse().unwrap()
266 ))
267 );
268 let rel = entries[1].pop().unwrap();
269 assert_eq!(rel.name, "baz");
270 assert_eq!(
271 rel.version,
272 Some((
273 VersionConstraint::GreaterThanEqual,
274 "1.0.0".parse().unwrap()
275 ))
276 );
277 }
278
279 #[test]
280 fn test_description() {
281 let control: Control = r#"Source: foo
282
283Package: foo
284Description: this is the short description
285 And the longer one
286 .
287 is on the next lines
288"#
289 .parse()
290 .unwrap();
291 let binary = &control.binaries[0];
292 assert_eq!(
293 binary.description,
294 Some(
295 "this is the short description\nAnd the longer one\n.\nis on the next lines"
296 .to_owned()
297 )
298 );
299 }
300}