1use std::fmt::Display;
2
3use error::Result;
4pub use indexmap::IndexMap;
5use thiserror::Error;
6
7mod error;
8mod parser;
9
10#[derive(Debug, PartialEq, Eq, Clone)]
11pub enum Item {
12 OneLine(String),
13 MultiLine(Vec<String>),
14}
15
16#[derive(Debug, Error)]
17pub struct NomErrorWrap {
18 source: nom::Err<nom::error::Error<Vec<u8>>>,
19}
20
21impl Display for NomErrorWrap {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 self.source.fmt(f)
24 }
25}
26
27type NomParseItem<'a> = Vec<(&'a [u8], (&'a [u8], Vec<u8>))>;
28
29pub fn parse_one(s: &str) -> Result<IndexMap<String, Item>> {
51 let (_, parse_v) = parser::single_package(s.as_bytes())?;
52
53 let result = to_map(parse_v)?;
54
55 Ok(result)
56}
57
58pub fn parse_multi(s: &str) -> Result<Vec<IndexMap<String, Item>>> {
82 if s.is_empty() {
83 return Ok(Vec::new());
84 }
85
86 let (_, parse_v) = parser::multi_package(s.as_bytes())?;
87
88 let mut result = vec![];
89
90 for i in parse_v {
91 result.push(to_map(i)?);
92 }
93
94 Ok(result)
95}
96
97fn to_map(parse_v: NomParseItem) -> Result<IndexMap<String, Item>> {
98 let mut result = IndexMap::new();
99 for (k, v) in parse_v {
100 let (one, multi) = v;
101 let k = std::str::from_utf8(k)?.to_string();
102
103 if one.is_empty() {
104 let multi = std::str::from_utf8(&multi)?;
105 let multi = multi.split('\n').map(|x| x.to_string()).collect();
106
107 result.insert(k, Item::MultiLine(multi));
108 continue;
109 }
110
111 result.insert(k, Item::OneLine(std::str::from_utf8(one)?.to_string()));
112 }
113
114 Ok(result)
115}
116
117pub fn parse_back(map: &[IndexMap<String, Item>]) -> String {
158 let mut s = String::new();
159 for i in map {
160 for (k, v) in i {
161 s += &format!("{}:", k);
162
163 match v {
164 Item::OneLine(v) => s += &format!(" {}\n", v),
165 Item::MultiLine(v) => {
166 s += "\n";
167 for i in v {
168 s += &format!(" {}\n", i);
169 }
170 }
171 }
172 }
173
174 s += "\n";
175 }
176
177 s
178}
179
180#[cfg(test)]
181mod tests {
182 use std::{fs, io::Read, process::Command};
183
184 use indexmap::IndexMap;
185
186 use crate::{parse_back, parse_multi, parse_one, Item};
187
188 #[test]
189 fn parse_one_it_works() {
190 let command = Command::new("dpkg")
191 .arg("-s")
192 .arg("plasma-workspace")
193 .output()
194 .unwrap();
195 let stdout = command.stdout;
196
197 let r = parse_one(std::str::from_utf8(&stdout).unwrap()).unwrap();
198
199 assert_eq!(
200 r.get("Package").unwrap(),
201 &Item::OneLine("plasma-workspace".to_string())
202 );
203
204 let right = vec![
205 "/etc/pam.d/kde a33459447160292012baca99cb9820b3",
206 "/etc/xdg/autostart/gmenudbusmenuproxy.desktop 4bf33ab6a937c4991c0ec418bfff11a0",
207 "/etc/xdg/autostart/klipper.desktop cc58958cfa37d7f4001e24e3de34abbd",
208 "/etc/xdg/autostart/org.kde.plasmashell.desktop 9552c32cf4e0c3a56b2884f6b08d7c72",
209 "/etc/xdg/autostart/xembedsniproxy.desktop 76011e12682833a1b4b3a01c7faac001",
210 "/etc/xdg/plasmanotifyrc f9713a8fb2a4abb43e592f0c12f3fab5",
211 "/etc/xdg/taskmanagerrulesrc 9df6c5d4530892fac71c219f27892f5b",
212 ];
213
214 let right = right.iter().map(|x| x.to_string()).collect::<Vec<_>>();
215
216 assert_eq!(r.get("Conffiles").unwrap(), &Item::MultiLine(right));
217
218 assert_eq!(
219 r.get("Description").unwrap(),
220 &Item::OneLine("The KDE Plasma Workspace, API and runtime libraries".to_string())
221 );
222 }
223
224 #[test]
225 fn parse_multi_it_works() {
226 let dir = fs::read_dir("/var/lib/apt/lists").unwrap();
227
228 for i in dir.flatten() {
229 if !i.file_name().to_str().unwrap().ends_with("_Packages") {
230 continue;
231 }
232
233 let mut f = std::fs::File::open(i.path()).unwrap();
234 let mut buf = Vec::new();
235 f.read_to_end(&mut buf).unwrap();
236
237 let r = parse_multi(std::str::from_utf8(&buf).unwrap());
238
239 assert!(r.is_ok())
240 }
241 }
242
243 #[test]
244 fn test_parse_back() {
245 let mut map = vec![];
246
247 let mut item1 = IndexMap::new();
248 item1.insert("a".to_string(), Item::OneLine("b".to_string()));
249 item1.insert(
250 "c".to_string(),
251 Item::MultiLine(vec!["a".to_string(), "b".to_string()]),
252 );
253 item1.insert("d".to_string(), Item::OneLine("e".to_string()));
254 map.push(item1);
255
256 let mut item2 = IndexMap::new();
257 item2.insert("a".to_string(), Item::OneLine("b".to_string()));
258 map.push(item2);
259
260 let s = parse_back(&map);
261
262 assert_eq!(
263 s,
264 r#"a: b
265c:
266 a
267 b
268d: e
269
270a: b
271
272"#
273 )
274 }
275}