diff --git a/Cargo.lock b/Cargo.lock index 1b336b6..213edee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,7 +228,7 @@ dependencies = [ "time", "tokio", "unic-langid", - "winnow 0.4.11", + "winnow", "zbus", ] @@ -2354,7 +2354,7 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", "toml_datetime", - "winnow 0.5.15", + "winnow", ] [[package]] @@ -2367,7 +2367,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.5.15", + "winnow", ] [[package]] @@ -2718,15 +2718,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" -[[package]] -name = "winnow" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656953b22bcbfb1ec8179d60734981d1904494ecc91f8a3f0ee5c7389bb8eb4b" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.5.15" diff --git a/Cargo.toml b/Cargo.toml index eaad69b..5af5852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" lazy_static = "1.4" reqwest = { version = "0.11", features = ["json"] } -winnow = "0.4" +winnow = "0.5" nix = { version = "0.27", features = ["user"] } tempfile = "3.5" sha2 = "0.10" diff --git a/src/parser.rs b/src/parser.rs index ddea979..fbbaf83 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,69 +1,109 @@ -use anyhow::{anyhow, Result}; -use std::collections::HashSet; +use std::fmt::{Debug, Display}; +use std::{collections::HashSet, error::Error}; +use winnow::error::ErrorKind; use winnow::{ ascii::space0, - bytes::{one_of, tag, take_until0}, - combinator::repeat, - sequence::{separated_pair, terminated}, - IResult, Parser, + combinator::{repeat, separated_pair, terminated}, + error::ParserError, + token::{one_of, tag, take_until0}, + PResult, Parser, }; #[inline] -fn key_name(input: &[u8]) -> IResult<&[u8], &[u8]> { +fn key_name<'a, E: ParserError<&'a [u8]>>(input: &mut &'a [u8]) -> PResult<&'a [u8], E> { take_until0(":") .verify(|input: &[u8]| !input.is_empty() && input[0] != b'\n') .parse_next(input) } #[inline] -fn separator(input: &[u8]) -> IResult<&[u8], ()> { +fn separator<'a, E: ParserError<&'a [u8]>>(input: &mut &'a [u8]) -> PResult<(), E> { (one_of(':'), space0).void().parse_next(input) } #[inline] -fn single_line(input: &[u8]) -> IResult<&[u8], &[u8]> { +fn single_line<'a, E: ParserError<&'a [u8]>>(input: &mut &'a [u8]) -> PResult<&'a [u8], E> { take_until0("\n").parse_next(input) } #[inline] -fn key_value(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { +fn key_value<'a, E: ParserError<&'a [u8]>>( + input: &mut &'a [u8], +) -> PResult<(&'a [u8], &'a [u8]), E> { separated_pair(key_name, separator, single_line).parse_next(input) } #[inline] -fn single_package(input: &[u8]) -> IResult<&[u8], Vec<(&[u8], &[u8])>> { +fn single_package<'a, E: ParserError<&'a [u8]>>( + input: &mut &'a [u8], +) -> PResult, E> { repeat(1.., terminated(key_value, tag("\n"))).parse_next(input) } #[inline] -fn extract_name(input: &[u8]) -> IResult<&[u8], &[u8]> { +fn extract_name<'a, E: ParserError<&'a [u8]>>(input: &mut &'a [u8]) -> PResult<&'a [u8], E> { let info = single_package(input)?; let mut found: Option<&[u8]> = None; - for i in info.1 { + for i in info { if i.0 == &b"Package"[..] { found = Some(i.1); } if i.0 == &b"Status"[..] && i.1.len() > 8 && i.1[..8] == b"install "[..] && found.is_some() { - return Ok((info.0, found.unwrap())); + return Ok(found.unwrap()); } } - Ok((info.0, &[])) + Ok(&[]) } #[inline] -pub fn extract_all_names(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { +pub fn extract_all_names<'a, E: ParserError<&'a [u8]>>( + input: &mut &'a [u8], +) -> PResult, E> { repeat(1.., terminated(extract_name, tag("\n"))).parse_next(input) } -pub fn list_installed(input: &[u8]) -> Result> { - let names = extract_all_names(input); +#[derive(Debug)] +#[allow(dead_code)] +pub struct AtmParseError { + input: String, + kind: winnow::error::ErrorKind, +} + +impl ParserError<&[u8]> for AtmParseError { + fn from_error_kind(input: &&[u8], kind: ErrorKind) -> Self { + Self { + input: String::from_utf8_lossy(input).to_string(), + kind, + } + } + + fn append(self, _: &&[u8], _: ErrorKind) -> Self { + self + } +} + +impl Display for AtmParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{self:?}") + } +} + +impl Error for AtmParseError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } + + fn cause(&self) -> Option<&dyn Error> { + self.source() + } +} + +pub fn list_installed<'a>(input: &mut &'a [u8]) -> PResult, AtmParseError> { + let names: PResult, AtmParseError> = extract_all_names(input); let mut result: HashSet = HashSet::new(); - for name in names - .map_err(|_| anyhow!("Failed to parse dpkg status file"))? - .1 - { + for name in names? { if name.is_empty() { continue; } @@ -76,75 +116,67 @@ pub fn list_installed(input: &[u8]) -> Result> { // tests #[test] fn test_key_name() { - let test = &b"name: value"[..]; - assert_eq!(key_name(test), Ok((&b": value"[..], &b"name"[..]))); + let test = &mut &b"name: value"[..]; + assert_eq!(key_name::<()>(test), Ok(&b"name"[..])); } #[test] fn test_seperator() { - let test = &b": value"[..]; - let test_2 = &b": \tvalue"[..]; - assert_eq!(separator(test), Ok((&b"value"[..], ()))); - assert_eq!(separator(test_2), Ok((&b"value"[..], ()))); + let test = &mut &b": value"[..]; + let test_2 = &mut &b": \tvalue"[..]; + assert_eq!(separator::<()>(test), Ok(())); + assert_eq!(separator::<()>(test_2), Ok(())); } #[test] fn test_single_line() { - let test = &b"value\n"[..]; - let test_2 = &b"value\t\r\n"[..]; - let test_3 = &b"value \x23\xff\n"[..]; - assert_eq!(single_line(test), Ok((&b"\n"[..], &b"value"[..]))); - assert_eq!(single_line(test_2), Ok((&b"\n"[..], &b"value\t\r"[..]))); - assert_eq!( - single_line(test_3), - Ok((&b"\n"[..], &b"value \x23\xff"[..])) - ); + let test = &mut &b"value\n"[..]; + let test_2 = &mut &b"value\t\r\n"[..]; + let test_3 = &mut &b"value \x23\xff\n"[..]; + assert_eq!(single_line::<()>(test), Ok(&b"value"[..])); + assert_eq!(single_line::<()>(test_2), Ok(&b"value\t\r"[..])); + assert_eq!(single_line::<()>(test_3), Ok(&b"value \x23\xff"[..])); } #[test] fn test_key_value() { - let test = &b"name1: value\n"[..]; - let test_2 = &b"name2: value\t\r\n"[..]; - let test_3 = &b"name3: value \x23\xff\n"[..]; - assert_eq!( - key_value(test), - Ok((&b"\n"[..], (&b"name1"[..], &b"value"[..]))) - ); + let test = &mut &b"name1: value\n"[..]; + let test_2 = &mut &b"name2: value\t\r\n"[..]; + let test_3 = &mut &b"name3: value \x23\xff\n"[..]; + assert_eq!(key_value::<()>(test), Ok((&b"name1"[..], &b"value"[..]))); assert_eq!( - key_value(test_2), - Ok((&b"\n"[..], (&b"name2"[..], &b"value\t\r"[..]))) + key_value::<()>(test_2), + Ok((&b"name2"[..], &b"value\t\r"[..])) ); assert_eq!( - key_value(test_3), - Ok((&b"\n"[..], (&b"name3"[..], &b"value \x23\xff"[..]))) + key_value::<()>(test_3), + Ok((&b"name3"[..], &b"value \x23\xff"[..])) ); } #[test] fn test_package() { - let test = &b"Package: zsync\nVersion: 0.6.2-1\nStatus: install ok installed\nArchitecture: amd64\nInstalled-Size: 256\n\n"[..]; + let test = &mut &b"Package: zsync\nVersion: 0.6.2-1\nStatus: install ok installed\nArchitecture: amd64\nInstalled-Size: 256\n\n"[..]; assert_eq!( - single_package(test), - Ok(( - &b"\n"[..], - vec![ - (&b"Package"[..], &b"zsync"[..]), - (&b"Version"[..], &b"0.6.2-1"[..]), - (&b"Status"[..], &b"install ok installed"[..]), - (&b"Architecture"[..], &b"amd64"[..]), - (&b"Installed-Size"[..], &b"256"[..]) - ] - )) + single_package::<()>(test), + Ok(vec![ + (&b"Package"[..], &b"zsync"[..]), + (&b"Version"[..], &b"0.6.2-1"[..]), + (&b"Status"[..], &b"install ok installed"[..]), + (&b"Architecture"[..], &b"amd64"[..]), + (&b"Installed-Size"[..], &b"256"[..]) + ]) ); - assert_eq!(extract_name(test), Ok((&b"\n"[..], (&b"zsync"[..])))); + let test = &mut &b"Package: zsync\nVersion: 0.6.2-1\nStatus: install ok installed\nArchitecture: amd64\nInstalled-Size: 256\n\n"[..]; + assert_eq!(extract_name::<()>(test), Ok(&b"zsync"[..])); } #[test] fn test_multi_package() { let test = - &b"Package: zsync\nStatus: b\n\nPackage: rsync\nStatus: install ok installed\n\n"[..]; + &mut &b"Package: zsync\nStatus: b\n\nPackage: rsync\nStatus: install ok installed\n\n"[..]; assert_eq!( - extract_all_names(test), - Ok((&b""[..], vec![&b""[..], &b"rsync"[..]])) + extract_all_names::<()>(test), + Ok(vec![&b""[..], &b"rsync"[..]]) ); } diff --git a/src/pm.rs b/src/pm.rs index 92004f4..58dfe99 100644 --- a/src/pm.rs +++ b/src/pm.rs @@ -12,7 +12,7 @@ use crate::pk::{ create_transaction, find_stable_version_of, get_updated_packages, refresh_cache, PackageKitProxy, }; -use anyhow::Result; +use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use serde_json::{from_reader, to_string}; @@ -36,7 +36,10 @@ type PreviousTopics = Vec; /// Returns the packages need to be reinstalled pub fn close_topics(topics: &[TopicManifest]) -> Result> { let state_file = fs::read(DPKG_STATE)?; - let installed = list_installed(&state_file)?; + let state_file_slice = &mut state_file.as_slice(); + let installed = list_installed(state_file_slice) + .map_err(|e| e.into_inner().unwrap()) + .context("Failed to parse dpkg status file")?; let mut remove = Vec::new(); for topic in topics {