debian_control/lossless/
buildinfo.rs1use crate::fields::{Md5Checksum, Sha1Checksum, Sha256Checksum};
8use crate::lossless::relations::Relations;
9use rowan::ast::AstNode;
10
11pub struct Buildinfo(deb822_lossless::Paragraph);
13
14impl From<deb822_lossless::Paragraph> for Buildinfo {
15 fn from(paragraph: deb822_lossless::Paragraph) -> Self {
16 Self(paragraph)
17 }
18}
19
20impl Default for Buildinfo {
21 fn default() -> Self {
22 let mut para = deb822_lossless::Paragraph::new();
23 para.set("Format", "1.0");
24 Self(para)
25 }
26}
27
28impl Buildinfo {
29 pub fn new() -> Self {
31 Self(deb822_lossless::Paragraph::new())
32 }
33
34 pub fn parse(text: &str) -> deb822_lossless::Parse<Buildinfo> {
38 let deb822_parse = deb822_lossless::Deb822::parse(text);
39 let green = deb822_parse.green().clone();
40 let mut errors = deb822_parse.errors().to_vec();
41
42 if errors.is_empty() {
44 let deb822 = deb822_parse.tree();
45 let paragraph_count = deb822.paragraphs().count();
46 if paragraph_count == 0 {
47 errors.push("No paragraphs found".to_string());
48 } else if paragraph_count > 1 {
49 errors.push("Multiple paragraphs found, expected one".to_string());
50 }
51 }
52
53 deb822_lossless::Parse::new(green, errors)
54 }
55
56 pub fn source(&self) -> Option<String> {
58 self.0.get("Source").map(|s| s.to_string())
59 }
60
61 pub fn set_source(&mut self, package: &str) {
63 self.0.set("Source", package);
64 }
65
66 pub fn binaries(&self) -> Option<Vec<String>> {
68 self.0.get("Binary").map(|s| {
69 s.split(' ')
70 .map(|s| s.trim().to_string())
71 .collect::<Vec<String>>()
72 })
73 }
74
75 pub fn set_binaries(&mut self, binaries: Vec<String>) {
77 self.0.set("Binary", &binaries.join(" "));
78 }
79
80 pub fn version(&self) -> Option<debversion::Version> {
82 self.0.get("Version").map(|s| s.parse().unwrap())
83 }
84
85 pub fn set_version(&mut self, version: debversion::Version) {
87 self.0.set("Version", &version.to_string());
88 }
89
90 pub fn build_architecture(&self) -> Option<String> {
92 self.0.get("Build-Architecture").map(|s| s.to_string())
93 }
94
95 pub fn set_build_architecture(&mut self, arch: &str) {
97 self.0.set("Build-Architecture", arch);
98 }
99
100 pub fn architecture(&self) -> Option<String> {
102 self.0.get("Architecture").map(|s| s.to_string())
103 }
104
105 pub fn set_architecture(&mut self, arch: &str) {
107 self.0.set("Architecture", arch);
108 }
109
110 pub fn checksums_sha256(&self) -> Vec<Sha256Checksum> {
112 self.0
113 .get("Checksums-Sha256")
114 .map(|s| {
115 s.lines()
116 .map(|line| line.parse().unwrap())
117 .collect::<Vec<Sha256Checksum>>()
118 })
119 .unwrap_or_default()
120 }
121
122 pub fn set_checksums_sha256(&mut self, checksums: Vec<Sha256Checksum>) {
124 self.0.set(
125 "Checksums-Sha256",
126 &checksums
127 .iter()
128 .map(|c| c.to_string())
129 .collect::<Vec<String>>()
130 .join("\n"),
131 );
132 }
133
134 pub fn checksums_sha1(&self) -> Vec<Sha1Checksum> {
136 self.0
137 .get("Checksums-Sha1")
138 .map(|s| {
139 s.lines()
140 .map(|line| line.parse().unwrap())
141 .collect::<Vec<Sha1Checksum>>()
142 })
143 .unwrap_or_default()
144 }
145
146 pub fn set_checksums_sha1(&mut self, checksums: Vec<Sha1Checksum>) {
148 self.0.set(
149 "Checksums-Sha1",
150 &checksums
151 .iter()
152 .map(|c| c.to_string())
153 .collect::<Vec<String>>()
154 .join("\n"),
155 );
156 }
157
158 pub fn checksums_md5(&self) -> Vec<Md5Checksum> {
160 self.0
161 .get("Checksums-Md5")
162 .map(|s| {
163 s.lines()
164 .map(|line| line.parse().unwrap())
165 .collect::<Vec<Md5Checksum>>()
166 })
167 .unwrap_or_default()
168 }
169
170 pub fn set_checksums_md5(&mut self, checksums: Vec<Md5Checksum>) {
172 self.0.set(
173 "Checksums-Md5",
174 &checksums
175 .iter()
176 .map(|c| c.to_string())
177 .collect::<Vec<String>>()
178 .join("\n"),
179 );
180 }
181
182 pub fn build_origin(&self) -> Option<String> {
184 self.0.get("Build-Origin").map(|s| s.to_string())
185 }
186
187 pub fn set_build_origin(&mut self, origin: &str) {
189 self.0.set("Build-Origin", origin);
190 }
191
192 pub fn build_date(&self) -> Option<String> {
194 self.0.get("Build-Date").map(|s| s.to_string())
195 }
196
197 pub fn set_build_date(&mut self, date: &str) {
199 self.0.set("Build-Date", date);
200 }
201
202 pub fn build_tainted_by(&self) -> Option<Vec<String>> {
204 self.0
205 .get("Build-Tainted-By")
206 .map(|s| s.split(' ').map(|s| s.to_string()).collect())
207 }
208
209 pub fn set_build_tainted_by(&mut self, tainted_by: Vec<String>) {
211 self.0.set("Build-Tainted-By", &tainted_by.join(" "));
212 }
213
214 pub fn format(&self) -> Option<String> {
216 self.0.get("Format").map(|s| s.to_string())
217 }
218
219 pub fn set_format(&mut self, format: &str) {
221 self.0.set("Format", format);
222 }
223
224 pub fn build_path(&self) -> Option<String> {
226 self.0.get("Build-Path").map(|s| s.to_string())
227 }
228
229 pub fn set_build_path(&mut self, path: &str) {
231 self.0.set("Build-Path", path);
232 }
233
234 pub fn environment(&self) -> Option<std::collections::HashMap<String, String>> {
236 self.0.get("Environment").map(|s| {
237 s.lines()
238 .map(|line| {
239 let (key, value) = line.split_once('=').unwrap();
240 (key.to_string(), value.to_string())
241 })
242 .collect()
243 })
244 }
245
246 pub fn set_environment(&mut self, env: std::collections::HashMap<String, String>) {
248 let mut s = String::new();
249 for (key, value) in env {
250 if !s.is_empty() {
251 s.push('\n');
252 }
253 s.push_str(&format!("{}={}", key, value));
254 }
255 self.0.set("Environment", &s);
256 }
257
258 pub fn installed_build_depends(&self) -> Option<Relations> {
260 self.0
261 .get("Installed-Build-Depends")
262 .map(|s| s.parse().unwrap())
263 }
264
265 pub fn set_installed_build_depends(&mut self, depends: Relations) {
267 self.0.set("Installed-Build-Depends", &depends.to_string());
268 }
269}
270
271impl std::str::FromStr for Buildinfo {
272 type Err = deb822_lossless::ParseError;
273
274 fn from_str(s: &str) -> Result<Self, Self::Err> {
275 Buildinfo::parse(s).to_result()
276 }
277}
278
279impl AstNode for Buildinfo {
280 type Language = deb822_lossless::Lang;
281
282 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
283 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
284 }
285
286 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
287 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
288 Some(Buildinfo(para))
289 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
290 deb822.paragraphs().next().map(Buildinfo)
291 } else {
292 None
293 }
294 }
295
296 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
297 self.0.syntax()
298 }
299}
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_parse() {
306 let s = include_str!("../../testdata/ruff.buildinfo");
307 let buildinfo: Buildinfo = s.parse().unwrap();
308 assert_eq!(buildinfo.format(), Some("1.0".to_string()));
309 }
310
311 #[test]
312 fn test_set_environment() {
313 let s = include_str!("../../testdata/ruff.buildinfo");
314 let mut buildinfo: Buildinfo = s.parse().unwrap();
315
316 let mut env = std::collections::HashMap::new();
317 env.insert("TEST_VAR".to_string(), "test_value".to_string());
318 env.insert("ANOTHER_VAR".to_string(), "another_value".to_string());
319
320 buildinfo.set_environment(env);
321 let env_field = buildinfo.0.get("Environment").unwrap();
322 assert!(env_field.contains("TEST_VAR=test_value"));
323 assert!(env_field.contains("ANOTHER_VAR=another_value"));
324 }
325
326 #[test]
327 fn test_set_build_path() {
328 let s = include_str!("../../testdata/ruff.buildinfo");
329 let mut buildinfo: Buildinfo = s.parse().unwrap();
330
331 buildinfo.set_build_path("/tmp/build/path");
332 assert_eq!(buildinfo.build_path(), Some("/tmp/build/path".to_string()));
333 }
334
335 #[test]
336 fn test_build_origin() {
337 let s = include_str!("../../testdata/ruff.buildinfo");
338 let buildinfo: Buildinfo = s.parse().unwrap();
339 assert_eq!(buildinfo.build_origin(), Some("Debian".to_string()));
340 }
341}