apt_parser/
control.rs

1use crate::{
2	case_map::CaseMap,
3	errors::{APTError, MissingKeyError, ParseError},
4	make_array, parse_kv,
5};
6
7pub struct Control {
8	pub(crate) map: CaseMap,
9	pub package: String,
10	pub source: Option<String>,
11	pub version: String,
12	pub section: Option<String>,
13	pub priority: Option<String>,
14	pub architecture: String,
15	pub is_essential: Option<bool>,
16	pub depends: Option<Vec<String>>,
17	pub pre_depends: Option<Vec<String>>,
18	pub recommends: Option<Vec<String>>,
19	pub suggests: Option<Vec<String>>,
20	pub replaces: Option<Vec<String>>,
21	pub enhances: Option<Vec<String>>,
22	pub breaks: Option<Vec<String>>,
23	pub conflicts: Option<Vec<String>>,
24	pub installed_size: Option<i64>,
25	pub maintainer: Option<String>,
26	pub description: Option<String>,
27	pub homepage: Option<String>,
28	pub built_using: Option<String>,
29	pub package_type: Option<String>,
30	pub tags: Option<Vec<String>>,
31}
32
33impl Control {
34	pub fn from(data: &str) -> Result<Control, APTError> {
35		let map = match parse_kv(data) {
36			Ok(map) => map,
37			Err(err) => return Err(APTError::KVError(err)),
38		};
39
40		let package = match map.get("Package") {
41			Some(package) => package,
42			None => {
43				return Err(APTError::MissingKeyError(MissingKeyError::new(
44					"Package", data,
45				)))
46			}
47		};
48
49		let version = match map.get("Version") {
50			Some(version) => version,
51			None => {
52				return Err(APTError::MissingKeyError(MissingKeyError::new(
53					"Version", data,
54				)))
55			}
56		};
57
58		let architecture = match map.get("Architecture") {
59			Some(architecture) => architecture,
60			None => {
61				return Err(APTError::MissingKeyError(MissingKeyError::new(
62					"Architecture",
63					data,
64				)))
65			}
66		};
67
68		let installed_size = match map.get("Installed-Size") {
69			Some(size) => Some(match size.parse::<i64>() {
70				Ok(size) => size,
71				Err(_) => return Err(APTError::ParseError(ParseError)),
72			}),
73			None => None,
74		};
75
76		Ok(Control {
77			map: map.clone(),
78			package: package.to_string(),
79			source: map.get("Source").cloned(),
80			version: version.to_string(),
81			section: map.get("Section").cloned(),
82			priority: map.get("Priority").cloned(),
83			architecture: architecture.to_string(),
84			is_essential: map.get("Essential").map(|x| x == "yes"),
85			depends: make_array(map.get("Depends")),
86			pre_depends: make_array(map.get("Pre-Depends")),
87			recommends: make_array(map.get("Recommends")),
88			suggests: make_array(map.get("Suggests")),
89			replaces: make_array(map.get("Replaces")),
90			enhances: make_array(map.get("Enhances")),
91			breaks: make_array(map.get("Breaks")),
92			conflicts: make_array(map.get("Conflicts")),
93			installed_size,
94			maintainer: map.get("Maintainer").cloned(),
95			description: map.get("Description").cloned(),
96			homepage: map.get("Homepage").cloned(),
97			built_using: map.get("Built-Using").cloned(),
98			package_type: map.get("Package-Type").cloned(),
99			tags: make_array(map.get("Tag")),
100		})
101	}
102
103	pub fn get(&self, key: &str) -> Option<&str> {
104		self.map.get(key).map(|x| &**x)
105	}
106}
107
108#[cfg(test)]
109mod tests {
110	use super::Control;
111	use std::fs::read_to_string;
112
113	#[test]
114	fn control_clang() {
115		let file = "./test/clang.control";
116		let data = match read_to_string(file) {
117			Ok(data) => data,
118			Err(err) => panic!("Failed to read file: {}", err),
119		};
120
121		let control = match Control::from(&data) {
122			Ok(control) => control,
123			Err(err) => panic!("Failed to parse control: {}", err),
124		};
125
126		assert_eq!(control.package, "clang");
127		assert_eq!(control.source, Some("llvm-defaults (0.54)".to_owned()));
128		assert_eq!(control.version, "1:13.0-54");
129		assert_eq!(control.section, Some("devel".to_owned()));
130		assert_eq!(control.priority, Some("optional".to_owned()));
131		assert_eq!(control.architecture, "amd64");
132		assert_eq!(control.is_essential, None);
133
134		assert_eq!(control.depends, Some(vec!["clang-13 (>= 13~)".to_owned()]));
135		assert_eq!(control.pre_depends, None);
136		assert_eq!(control.recommends, None);
137		assert_eq!(control.suggests, None);
138		assert_eq!(
139			control.replaces,
140			Some(vec![
141				"clang (<< 3.2-1~exp2)".to_owned(),
142				"clang-3.2".to_owned(),
143				"clang-3.3".to_owned(),
144				"clang-3.4 (<< 1:3.4.2-7~exp1)".to_owned(),
145				"clang-3.5 (<< 1:3.5~+rc1-3~exp1)".to_owned(),
146			])
147		);
148		assert_eq!(control.enhances, None);
149		assert_eq!(
150			control.breaks,
151			Some(vec![
152				"clang-3.2".to_owned(),
153				"clang-3.3".to_owned(),
154				"clang-3.4 (<< 1:3.4.2-7~exp1)".to_owned(),
155				"clang-3.5 (<< 1:3.5~+rc1-3~exp1)".to_owned(),
156			])
157		);
158		assert_eq!(control.conflicts, None);
159
160		assert_eq!(control.installed_size, Some(24));
161		assert_eq!(
162			control.maintainer,
163			Some("Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>".to_owned())
164		);
165		assert_eq!(control.description, Some("C, C++ and Objective-C compiler (LLVM based), clang binary\nClang project is a C, C++, Objective C and Objective C++ front-end for the LLVM compiler. Its goal is to offer a replacement to the GNU Compiler Collection (GCC).\n\nClang implements all of the ISO C++ 1998, 11 and 14 standards and also provides most of the support of C++17.\n\nThis is a dependency package providing the default clang compiler.".to_owned()));
166		assert_eq!(control.homepage, None);
167		assert_eq!(control.built_using, None);
168		assert_eq!(control.package_type, None);
169		assert_eq!(control.tags, None);
170
171		assert_eq!(
172			control.get("Original-Maintainer"),
173			Some("LLVM Packaging Team <pkg-llvm-team@lists.alioth.debian.org>")
174		);
175	}
176
177	#[test]
178	fn control_com_amywhile_signalreborn() {
179		let file = "./test/com.amywhile.signalreborn.control";
180		let data = match read_to_string(file) {
181			Ok(data) => data,
182			Err(err) => panic!("Failed to read file: {}", err),
183		};
184
185		let control = match Control::from(&data) {
186			Ok(control) => control,
187			Err(err) => panic!("Failed to parse control: {}", err),
188		};
189
190		assert_eq!(control.package, "com.amywhile.signalreborn");
191		assert_eq!(control.source, None);
192		assert_eq!(control.version, "2.2.1-2");
193		assert_eq!(control.section, Some("Applications".to_owned()));
194		assert_eq!(control.priority, None);
195		assert_eq!(control.architecture, "iphoneos-arm");
196		assert_eq!(control.is_essential, None);
197
198		assert_eq!(
199			control.depends,
200			Some(vec!["firmware (>= 12.2) | org.swift.libswift".to_owned()])
201		);
202		assert_eq!(control.pre_depends, None);
203		assert_eq!(control.recommends, None);
204		assert_eq!(control.suggests, None);
205		assert_eq!(
206			control.replaces,
207			Some(vec!["com.charliewhile.signalreborn".to_owned()])
208		);
209		assert_eq!(control.enhances, None);
210		assert_eq!(
211			control.breaks,
212			Some(vec!["com.charliewhile.signalreborn".to_owned()])
213		);
214		assert_eq!(
215			control.conflicts,
216			Some(vec!["com.charliewhile.signalreborn".to_owned()])
217		);
218
219		assert_eq!(control.installed_size, Some(1536));
220		assert_eq!(
221			control.maintainer,
222			Some("Amy While <support@anamy.gay>".to_owned())
223		);
224		assert_eq!(
225			control.description,
226			Some("Visualise your nearby cell towers".to_owned())
227		);
228		assert_eq!(control.homepage, None);
229		assert_eq!(control.built_using, None);
230		assert_eq!(control.package_type, None);
231		assert_eq!(
232			control.tags,
233			Some(vec!["compatible_min::ios11.0".to_owned()])
234		);
235
236		assert_eq!(control.get("Name"), Some("SignalReborn"));
237		assert_eq!(control.get("Author"), Some("Amy While <support@anamy.gay>"));
238		assert_eq!(
239			control.get("Icon"),
240			Some("https://img.chariz.cloud/icon/signal/icon@3x.png")
241		);
242		assert_eq!(
243			control.get("Depiction"),
244			Some("https://chariz.com/get/signal")
245		);
246	}
247}