eight_deep_parser/
lib.rs

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
29/// Parse a single package:
30///
31/// ```rust
32/// use std::process::Command;
33/// use eight_deep_parser::{parse_multi, parse_one, Item};
34///
35/// let command = Command::new("dpkg")
36///     .arg("-s")
37///     .arg("plasma-workspace")
38///     .output()
39///     .unwrap();
40///
41/// let stdout = command.stdout;
42///
43/// let r = parse_one(std::str::from_utf8(&stdout).unwrap()).unwrap();
44///
45/// assert_eq!(
46///     r.get("Package").unwrap(),
47///     &Item::OneLine("plasma-workspace".to_string())
48/// );
49///```
50pub 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
58/// Parse multi package:
59/// (e.g: /var/lib/dpkg/status)
60///
61/// ```rust
62/// use std::{fs, io::Read, process::Command};
63/// use eight_deep_parser::{parse_multi, Item};
64///
65/// let dir = fs::read_dir("/var/lib/apt/lists").unwrap();
66///
67/// for i in dir.flatten() {
68///     if !i.file_name().to_str().unwrap().ends_with("_Packages") {
69///         continue;
70///     }
71///
72///     let mut f = std::fs::File::open(i.path()).unwrap();
73///     let mut buf = Vec::new();
74///     f.read_to_end(&mut buf).unwrap();
75///
76///     let r = parse_multi(std::str::from_utf8(&buf).unwrap());
77///
78///     assert!(r.is_ok())
79/// }
80/// ```
81pub 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
117/// Parse back:
118/// 
119/// ```rust
120/// use indexmap::IndexMap;
121/// use eight_deep_parser::{parse_back, Item};
122/// 
123/// fn test_parse_back() {
124///     let mut map = vec![];
125///
126///     let mut item1 = IndexMap::new();
127///     item1.insert("a".to_string(), Item::OneLine("b".to_string()));
128///     item1.insert(
129///         "c".to_string(),
130///         Item::MultiLine(vec!["a".to_string(), "b".to_string()]),
131///     );
132///     item1.insert("d".to_string(), Item::OneLine("e".to_string()));
133///     map.push(item1);
134///
135///     let mut item2 = IndexMap::new();
136///     item2.insert("a".to_string(), Item::OneLine("b".to_string()));
137///     map.push(item2);
138///
139///     let s = parse_back(&map);
140///
141///     assert_eq!(
142///         s,
143///         r#"a: b
144/// c:
145///   a
146///   b
147/// d: e
148///
149/// a: b
150///
151/// "#
152///     )
153/// }
154
155/// ```
156
157pub 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}