debian_control/lossless/
changes.rs1pub struct Changes(deb822_lossless::Paragraph);
5
6#[derive(Debug)]
8pub enum ParseError {
9 Deb822(deb822_lossless::Error),
11
12 NoParagraphs,
14
15 MultipleParagraphs,
17}
18
19impl std::fmt::Display for ParseError {
20 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
21 match self {
22 Self::Deb822(e) => write!(f, "{}", e),
23 Self::NoParagraphs => write!(f, "no paragraphs found"),
24 Self::MultipleParagraphs => write!(f, "multiple paragraphs found"),
25 }
26 }
27}
28
29impl std::error::Error for ParseError {}
30
31impl From<deb822_lossless::Error> for ParseError {
32 fn from(e: deb822_lossless::Error) -> Self {
33 Self::Deb822(e)
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
39pub struct File {
40 pub md5sum: String,
42 pub size: usize,
44 pub section: String,
46 pub priority: crate::Priority,
48 pub filename: String,
50}
51
52impl std::fmt::Display for File {
53 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54 write!(
55 f,
56 "{} {} {} {} {}",
57 self.md5sum, self.size, self.section, self.priority, self.filename
58 )
59 }
60}
61
62impl std::str::FromStr for File {
63 type Err = ();
64
65 fn from_str(s: &str) -> Result<Self, Self::Err> {
66 let mut parts = s.split_whitespace();
67 let md5sum = parts.next().ok_or(())?;
68 let size = parts.next().ok_or(())?.parse().map_err(|_| ())?;
69 let section = parts.next().ok_or(())?.to_string();
70 let priority = parts.next().ok_or(())?.parse().map_err(|_| ())?;
71 let filename = parts.next().ok_or(())?.to_string();
72 Ok(Self {
73 md5sum: md5sum.to_string(),
74 size,
75 section,
76 priority,
77 filename,
78 })
79 }
80}
81
82impl Changes {
83 pub fn format(&self) -> Option<String> {
85 self.0.get("Format").map(|s| s.to_string())
86 }
87
88 pub fn set_format(&mut self, value: &str) {
90 self.0.set("Format", value);
91 }
92
93 pub fn source(&self) -> Option<String> {
95 self.0.get("Source").map(|s| s.to_string())
96 }
97
98 pub fn binary(&self) -> Option<Vec<String>> {
100 self.0
101 .get("Binary")
102 .map(|s| s.split_whitespace().map(|s| s.to_string()).collect())
103 }
104
105 pub fn architecture(&self) -> Option<Vec<String>> {
107 self.0
108 .get("Architecture")
109 .map(|s| s.split_whitespace().map(|s| s.to_string()).collect())
110 }
111
112 pub fn version(&self) -> Option<debversion::Version> {
114 self.0.get("Version").map(|s| s.parse().unwrap())
115 }
116
117 pub fn distribution(&self) -> Option<String> {
119 self.0.get("Distribution").map(|s| s.to_string())
120 }
121
122 pub fn urgency(&self) -> Option<crate::fields::Urgency> {
124 self.0.get("Urgency").map(|s| s.parse().unwrap())
125 }
126
127 pub fn maintainer(&self) -> Option<String> {
129 self.0.get("Maintainer").map(|s| s.to_string())
130 }
131
132 pub fn changed_by(&self) -> Option<String> {
134 self.0.get("Changed-By").map(|s| s.to_string())
135 }
136
137 pub fn description(&self) -> Option<String> {
139 self.0.get("Description").map(|s| s.to_string())
140 }
141
142 pub fn checksums_sha1(&self) -> Option<Vec<crate::fields::Sha1Checksum>> {
144 self.0
145 .get("Checksums-Sha1")
146 .map(|s| s.lines().map(|line| line.parse().unwrap()).collect())
147 }
148
149 pub fn checksums_sha256(&self) -> Option<Vec<crate::fields::Sha256Checksum>> {
151 self.0
152 .get("Checksums-Sha256")
153 .map(|s| s.lines().map(|line| line.parse().unwrap()).collect())
154 }
155
156 pub fn files(&self) -> Option<Vec<File>> {
158 self.0
159 .get("Files")
160 .map(|s| s.lines().map(|line| line.parse().unwrap()).collect())
161 }
162
163 pub fn get_pool_path(&self) -> Option<String> {
165 let files = self.files()?;
166
167 let section = &files.first().unwrap().section;
168
169 let section = if let Some((section, _subsection)) = section.split_once('/') {
170 section
171 } else {
172 "main"
173 };
174
175 let source = self.source()?;
176
177 let subdir = if source.starts_with("lib") {
178 "lib".to_string()
179 } else {
180 source[..1].to_lowercase()
181 };
182
183 Some(format!("pool/{}/{}/{}", section, subdir, source))
184 }
185
186 pub fn new() -> Self {
188 let mut slf = Self(deb822_lossless::Paragraph::new());
189 slf.set_format("1.8");
190 slf
191 }
192
193 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, ParseError> {
195 let deb822 = deb822_lossless::Deb822::from_file(path)?;
196 let mut paras = deb822.paragraphs();
197 let para = match paras.next() {
198 Some(para) => para,
199 None => return Err(ParseError::NoParagraphs),
200 };
201 if paras.next().is_some() {
202 return Err(ParseError::MultipleParagraphs);
203 }
204 Ok(Self(para))
205 }
206
207 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
209 path: P,
210 ) -> Result<(Self, Vec<String>), std::io::Error> {
211 let (mut deb822, mut errors) = deb822_lossless::Deb822::from_file_relaxed(path)?;
212 let mut paras = deb822.paragraphs();
213 let para = match paras.next() {
214 Some(para) => para,
215 None => deb822.add_paragraph(),
216 };
217 if paras.next().is_some() {
218 errors.push("multiple paragraphs found".to_string());
219 }
220 Ok((Self(para), errors))
221 }
222
223 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, ParseError> {
225 let deb822 = deb822_lossless::Deb822::read(&mut r)?;
226 let mut paras = deb822.paragraphs();
227 let para = match paras.next() {
228 Some(para) => para,
229 None => return Err(ParseError::NoParagraphs),
230 };
231 if paras.next().is_some() {
232 return Err(ParseError::MultipleParagraphs);
233 }
234 Ok(Self(para))
235 }
236
237 pub fn read_relaxed<R: std::io::Read>(
239 mut r: R,
240 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
241 let (mut deb822, mut errors) = deb822_lossless::Deb822::read_relaxed(&mut r)?;
242 let mut paras = deb822.paragraphs();
243 let para = match paras.next() {
244 Some(para) => para,
245 None => deb822.add_paragraph(),
246 };
247 if paras.next().is_some() {
248 errors.push("multiple paragraphs found".to_string());
249 }
250 Ok((Self(para), errors))
251 }
252}
253
254impl Default for Changes {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260#[cfg(feature = "python-debian")]
261impl pyo3::ToPyObject for Changes {
262 fn to_object(&self, py: pyo3::Python) -> pyo3::PyObject {
263 self.0.to_object(py)
264 }
265}
266
267#[cfg(feature = "python-debian")]
268impl pyo3::FromPyObject<'_> for Changes {
269 fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
270 use pyo3::prelude::*;
271 Ok(Changes(ob.extract()?))
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 #[test]
278 fn test_new() {
279 let changes = super::Changes::new();
280 assert_eq!(changes.format(), Some("1.8".to_string()));
281 }
282
283 #[test]
284 fn test_parse() {
285 let changes = r#"Format: 1.8
286Date: Fri, 08 Sep 2023 18:23:59 +0100
287Source: buildlog-consultant
288Binary: python3-buildlog-consultant
289Architecture: all
290Version: 0.0.34-1
291Distribution: unstable
292Urgency: medium
293Maintainer: Jelmer Vernooij <jelmer@debian.org>
294Changed-By: Jelmer Vernooij <jelmer@debian.org>
295Description:
296 python3-buildlog-consultant - build log parser and analyser
297Changes:
298 buildlog-consultant (0.0.34-1) UNRELEASED; urgency=medium
299 .
300 * New upstream release.
301 * Update standards version to 4.6.2, no changes needed.
302Checksums-Sha1:
303 f1657e628254428ad74542e82c253a181894e8d0 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo
304 b44493c05d014bcd59180942d0125b20ddf45d03 2550812 python3-buildlog-consultant_0.0.34-1_all.deb
305Checksums-Sha256:
306 342a5782bf6a4f282d9002f726d2cac9c689c7e0fa7f61a1b0ecbf4da7916bdb 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo
307 7f7e5df81ee23fbbe89015edb37e04f4bb40672fa6e9b1afd4fd698e57db78fd 2550812 python3-buildlog-consultant_0.0.34-1_all.deb
308Files:
309 aa83112b0f8774a573bcf0b7b5cc12cc 17153 python optional buildlog-consultant_0.0.34-1_amd64.buildinfo
310 a55858b90fe0ca728c89c1a1132b45c5 2550812 python optional python3-buildlog-consultant_0.0.34-1_all.deb
311"#;
312 let changes = super::Changes::read(changes.as_bytes()).unwrap();
313 assert_eq!(changes.format(), Some("1.8".to_string()));
314 assert_eq!(changes.source(), Some("buildlog-consultant".to_string()));
315 assert_eq!(
316 changes.binary(),
317 Some(vec!["python3-buildlog-consultant".to_string()])
318 );
319 assert_eq!(changes.architecture(), Some(vec!["all".to_string()]));
320 assert_eq!(changes.version(), Some("0.0.34-1".parse().unwrap()));
321 assert_eq!(changes.distribution(), Some("unstable".to_string()));
322 assert_eq!(changes.urgency(), Some(crate::fields::Urgency::Medium));
323 assert_eq!(
324 changes.maintainer(),
325 Some("Jelmer Vernooij <jelmer@debian.org>".to_string())
326 );
327 assert_eq!(
328 changes.changed_by(),
329 Some("Jelmer Vernooij <jelmer@debian.org>".to_string())
330 );
331 assert_eq!(
332 changes.description(),
333 Some("python3-buildlog-consultant - build log parser and analyser".to_string())
334 );
335 assert_eq!(
336 changes.checksums_sha1(),
337 Some(vec![
338 "f1657e628254428ad74542e82c253a181894e8d0 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo".parse().unwrap(),
339 "b44493c05d014bcd59180942d0125b20ddf45d03 2550812 python3-buildlog-consultant_0.0.34-1_all.deb".parse().unwrap()
340 ])
341 );
342 assert_eq!(
343 changes.checksums_sha256(),
344 Some(vec![
345 "342a5782bf6a4f282d9002f726d2cac9c689c7e0fa7f61a1b0ecbf4da7916bdb 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo"
346 .parse()
347 .unwrap(),
348 "7f7e5df81ee23fbbe89015edb37e04f4bb40672fa6e9b1afd4fd698e57db78fd 2550812 python3-buildlog-consultant_0.0.34-1_all.deb"
349 .parse()
350 .unwrap()
351 ])
352 );
353 assert_eq!(
354 changes.files(),
355 Some(vec![
356 "aa83112b0f8774a573bcf0b7b5cc12cc 17153 python optional buildlog-consultant_0.0.34-1_amd64.buildinfo".parse().unwrap(),
357 "a55858b90fe0ca728c89c1a1132b45c5 2550812 python optional python3-buildlog-consultant_0.0.34-1_all.deb".parse().unwrap()
358 ])
359 );
360
361 assert_eq!(
362 changes.get_pool_path(),
363 Some("pool/main/b/buildlog-consultant".to_string())
364 );
365 }
366}