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}