use std::fmt::Display;
use error::Result;
pub use indexmap::IndexMap;
use thiserror::Error;
mod error;
mod parser;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Item {
OneLine(String),
MultiLine(Vec<String>),
}
#[derive(Debug, Error)]
pub struct NomErrorWrap {
source: nom::Err<nom::error::Error<Vec<u8>>>,
}
impl Display for NomErrorWrap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.source.fmt(f)
}
}
type NomParseItem<'a> = Vec<(&'a [u8], (&'a [u8], Vec<u8>))>;
pub fn parse_one(s: &str) -> Result<IndexMap<String, Item>> {
let (_, parse_v) = parser::single_package(s.as_bytes())?;
let result = to_map(parse_v)?;
Ok(result)
}
pub fn parse_multi(s: &str) -> Result<Vec<IndexMap<String, Item>>> {
if s.is_empty() {
return Ok(Vec::new());
}
let (_, parse_v) = parser::multi_package(s.as_bytes())?;
let mut result = vec![];
for i in parse_v {
result.push(to_map(i)?);
}
Ok(result)
}
fn to_map(parse_v: NomParseItem) -> Result<IndexMap<String, Item>> {
let mut result = IndexMap::new();
for (k, v) in parse_v {
let (one, multi) = v;
let k = std::str::from_utf8(k)?.to_string();
if one.is_empty() {
let multi = std::str::from_utf8(&multi)?;
let multi = multi.split('\n').map(|x| x.to_string()).collect();
result.insert(k, Item::MultiLine(multi));
continue;
}
result.insert(k, Item::OneLine(std::str::from_utf8(one)?.to_string()));
}
Ok(result)
}
pub fn parse_back(map: &[IndexMap<String, Item>]) -> String {
let mut s = String::new();
for i in map {
for (k, v) in i {
s += &format!("{}:", k);
match v {
Item::OneLine(v) => s += &format!(" {}\n", v),
Item::MultiLine(v) => {
s += "\n";
for i in v {
s += &format!(" {}\n", i);
}
}
}
}
s += "\n";
}
s
}
#[cfg(test)]
mod tests {
use std::{fs, io::Read, process::Command};
use indexmap::IndexMap;
use crate::{parse_back, parse_multi, parse_one, Item};
#[test]
fn parse_one_it_works() {
let command = Command::new("dpkg")
.arg("-s")
.arg("plasma-workspace")
.output()
.unwrap();
let stdout = command.stdout;
let r = parse_one(std::str::from_utf8(&stdout).unwrap()).unwrap();
assert_eq!(
r.get("Package").unwrap(),
&Item::OneLine("plasma-workspace".to_string())
);
let right = vec![
"/etc/pam.d/kde a33459447160292012baca99cb9820b3",
"/etc/xdg/autostart/gmenudbusmenuproxy.desktop 4bf33ab6a937c4991c0ec418bfff11a0",
"/etc/xdg/autostart/klipper.desktop cc58958cfa37d7f4001e24e3de34abbd",
"/etc/xdg/autostart/org.kde.plasmashell.desktop 9552c32cf4e0c3a56b2884f6b08d7c72",
"/etc/xdg/autostart/xembedsniproxy.desktop 76011e12682833a1b4b3a01c7faac001",
"/etc/xdg/plasmanotifyrc f9713a8fb2a4abb43e592f0c12f3fab5",
"/etc/xdg/taskmanagerrulesrc 9df6c5d4530892fac71c219f27892f5b",
];
let right = right.iter().map(|x| x.to_string()).collect::<Vec<_>>();
assert_eq!(r.get("Conffiles").unwrap(), &Item::MultiLine(right));
assert_eq!(
r.get("Description").unwrap(),
&Item::OneLine("The KDE Plasma Workspace, API and runtime libraries".to_string())
);
}
#[test]
fn parse_multi_it_works() {
let dir = fs::read_dir("/var/lib/apt/lists").unwrap();
for i in dir.flatten() {
if !i.file_name().to_str().unwrap().ends_with("_Packages") {
continue;
}
let mut f = std::fs::File::open(i.path()).unwrap();
let mut buf = Vec::new();
f.read_to_end(&mut buf).unwrap();
let r = parse_multi(std::str::from_utf8(&buf).unwrap());
assert!(r.is_ok())
}
}
#[test]
fn test_parse_back() {
let mut map = vec![];
let mut item1 = IndexMap::new();
item1.insert("a".to_string(), Item::OneLine("b".to_string()));
item1.insert(
"c".to_string(),
Item::MultiLine(vec!["a".to_string(), "b".to_string()]),
);
item1.insert("d".to_string(), Item::OneLine("e".to_string()));
map.push(item1);
let mut item2 = IndexMap::new();
item2.insert("a".to_string(), Item::OneLine("b".to_string()));
map.push(item2);
let s = parse_back(&map);
assert_eq!(
s,
r#"a: b
c:
a
b
d: e
a: b
"#
)
}
}