From a1ccf88d0c975c030888d1d286252724f20471a5 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Fri, 9 May 2025 15:52:19 +0300 Subject: [PATCH] feat: fix formatting, yield ignored chars in version component iter --- src/diff.rs | 159 ++++++++++++++++++++++++++++++++++--------------- src/main.rs | 25 -------- src/version.rs | 67 ++++++++++++++------- 3 files changed, 155 insertions(+), 96 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index 6132322..5227b81 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -1,12 +1,12 @@ -use std::{ - fmt::{ - self, - Write as _, - }, - sync, +use std::fmt::{ + self, + Write as _, }; -use itertools::EitherOrBoth; +use itertools::{ + EitherOrBoth, + Itertools, +}; use ref_cast::RefCast as _; use rustc_hash::{ FxBuildHasher, @@ -20,9 +20,7 @@ use crate::{ Version, }; -const HEADER_STYLE: yansi::Style = yansi::Style::new().bold().underline(); - -#[derive(Default)] +#[derive(Debug, Default)] struct Diff { old: T, new: T, @@ -110,9 +108,18 @@ pub fn diff<'a>( for (name, versions, status) in diffs { if last_status != Some(status) { last_status = Some(status); - HEADER_STYLE.fmt_prefix(writer)?; - writeln!(writer, "{status:?} packages:")?; - HEADER_STYLE.fmt_suffix(writer)?; + + writeln!( + writer, + "{nl}{status}", + nl = if last_status.is_some() { "\n" } else { "" }, + status = match status { + DiffStatus::Added => "ADDED", + DiffStatus::Removed => "REMOVED", + DiffStatus::Changed => "CHANGED", + } + .bold(), + )?; } write!( @@ -122,56 +129,112 @@ pub fn diff<'a>( )?; let mut oldacc = String::new(); + let mut oldwrote = false; let mut newacc = String::new(); + let mut newwrote = false; - for diff in itertools::Itertools::zip_longest( - versions.old.iter(), - versions.new.iter(), - ) { + for diff in Itertools::zip_longest(versions.old.iter(), versions.new.iter()) + { match diff { + EitherOrBoth::Right(old_version) => { + if oldwrote { + write!(oldacc, ", ")?; + } else { + write!(oldacc, " ")?; + oldwrote = true; + } + + for old_comp in old_version.unwrap_or(Version::ref_cast("")) { + match old_comp { + Ok(old_comp) => write!(oldacc, "{old}", old = old_comp.red())?, + Err(ignored) => write!(oldacc, "{ignored}")?, + } + } + }, + // I have no idea why itertools is returning `versions.new` in `Left`. - EitherOrBoth::Left(new) => { - let new = new.unwrap_or(Version::ref_cast("")); + EitherOrBoth::Left(new_version) => { + if newwrote { + write!(newacc, ", ")?; + } else { + write!(newacc, " ")?; + newwrote = true; + } - write!(newacc, " {new}", new = new.green())?; + for new_comp in new_version.unwrap_or(Version::ref_cast("")) { + match new_comp { + Ok(new_comp) => write!(newacc, "{new}", new = new_comp.green())?, + Err(ignored) => write!(newacc, "{ignored}")?, + } + } }, - EitherOrBoth::Right(old) => { - let old = old.unwrap_or(Version::ref_cast("")); + EitherOrBoth::Both(old_version, new_version) => { + if old_version == new_version { + continue; + } - write!(oldacc, " {old}", old = old.red())?; - }, + let old_version = old_version.unwrap_or(Version::ref_cast("")); + let new_version = new_version.unwrap_or(Version::ref_cast("")); - EitherOrBoth::Both(old, new) => { - static NAME_SUFFIX_REGEX: sync::LazyLock = - sync::LazyLock::new(|| { - regex::Regex::new("(-man|-lib|-doc|-dev|-out|-terminfo)") - .expect("failed to compile regex for Nix store path versions") - }); + if oldwrote { + write!(oldacc, ", ")?; + } else { + write!(oldacc, " ")?; + oldwrote = true; + } + if newwrote { + write!(newacc, ", ")?; + } else { + write!(newacc, " ")?; + newwrote = true; + } - let old = old.unwrap_or(Version::ref_cast("")); - let new = new.unwrap_or(Version::ref_cast("")); - - let suffix = NAME_SUFFIX_REGEX.captures(old).map_or("", |matches| { - matches.get(0).map_or("", |capture| capture.as_str()) - }); - - let old = old.strip_suffix(suffix).unwrap_or(old); - let new = new.strip_suffix(suffix).unwrap_or(new); - - for diff in diff::chars(old, new) { + for diff in Itertools::zip_longest( + old_version.into_iter(), + new_version.into_iter(), + ) { match diff { - diff::Result::Left(oldc) => { - write!(oldacc, "{oldc}", oldc = oldc.red()).unwrap(); + EitherOrBoth::Right(old_comp) => { + match old_comp { + Ok(old_comp) => { + write!(oldacc, "{old}", old = old_comp.red())?; + }, + Err(ignored) => { + write!(oldacc, "{ignored}")?; + }, + } }, - diff::Result::Right(newc) => { - write!(newacc, "{newc}", newc = newc.green()).unwrap(); + EitherOrBoth::Left(new_comp) => { + match new_comp { + Ok(new_comp) => { + write!(newacc, "{new}", new = new_comp.green())?; + }, + Err(ignored) => { + write!(newacc, "{ignored}")?; + }, + } }, - diff::Result::Both(oldc, newc) => { - write!(oldacc, "{oldc}", oldc = oldc.yellow()).unwrap(); - write!(newacc, "{newc}", newc = newc.yellow()).unwrap(); + EitherOrBoth::Both(old_comp, new_comp) => { + if let Err(ignored) = old_comp { + write!(oldacc, "{ignored}")?; + } + + if let Err(ignored) = new_comp { + write!(newacc, "{ignored}")?; + } + + if let (Ok(old_comp), Ok(new_comp)) = (old_comp, new_comp) { + if old_comp == new_comp { + write!(oldacc, "{old}", old = old_comp.yellow())?; + write!(newacc, "{new}", new = new_comp.yellow())?; + } else { + write!(oldacc, "{old}", old = old_comp.red())?; + write!(newacc, "{new}", new = new_comp.green())?; + } + } }, } } diff --git a/src/main.rs b/src/main.rs index 241dbaa..9c75010 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,31 +111,6 @@ fn real_main() -> Result<()> { paths_old.iter().map(|(_, path)| path), paths_new.iter().map(|(_, path)| path), )?; - // let PackageDiff { - // pkg_to_versions_pre: pre, - // pkg_to_versions_post: post, - // pre_keys: _, - // post_keys: _, - // added, - // removed, - // changed, - // } = PackageDiff::new(&packages_old, &packages_after); - - // log::debug!("Added packages: {}", added.len()); - // log::debug!("Removed packages: {}", removed.len()); - // log::debug!( - // "Changed packages: {}", - // changed - // .iter() - // .filter(|p| { - // !p.is_empty() - // && match (pre.get(*p), post.get(*p)) { - // (Some(ver_pre), Some(ver_post)) => ver_pre != ver_post, - // _ => false, - // } - // }) - // .count() - // ); // println!("Difference between the two generations:"); // println!(); diff --git a/src/version.rs b/src/version.rs index 8a40027..456ccb1 100644 --- a/src/version.rs +++ b/src/version.rs @@ -20,15 +20,26 @@ impl PartialOrd for Version { impl cmp::Ord for Version { fn cmp(&self, that: &Self) -> cmp::Ordering { - let this = VersionComponentIter::from(&**self); - let that = VersionComponentIter::from(&**that); + let this = VersionComponentIter::from(&**self).filter_map(Result::ok); + let that = VersionComponentIter::from(&**that).filter_map(Result::ok); this.cmp(that) } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum VersionComponent<'a> { +#[expect(clippy::into_iter_without_iter)] +impl<'a> IntoIterator for &'a Version { + type Item = Result, &'a str>; + + type IntoIter = VersionComponentIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + VersionComponentIter::from(&**self) + } +} + +#[derive(Display, Debug, Clone, Copy, Eq, PartialEq)] +pub enum VersionComponent<'a> { Number(u64), Text(&'a str), } @@ -47,12 +58,12 @@ impl cmp::Ord for VersionComponent<'_> { }; match (*self, *other) { - (Number(x), Number(y)) => x.cmp(&y), - (Text(x), Text(y)) => { - match (x, y) { + (Number(this), Number(that)) => this.cmp(&that), + (Text(this), Text(that)) => { + match (this, that) { ("pre", _) => cmp::Ordering::Less, (_, "pre") => cmp::Ordering::Greater, - _ => x.cmp(y), + _ => this.cmp(that), } }, (Text(_), Number(_)) => cmp::Ordering::Less, @@ -63,15 +74,16 @@ impl cmp::Ord for VersionComponent<'_> { /// Yields [`VertionComponent`] from a version string. #[derive(Deref, DerefMut, From)] -struct VersionComponentIter<'a>(&'a str); +pub struct VersionComponentIter<'a>(&'a str); impl<'a> Iterator for VersionComponentIter<'a> { - type Item = VersionComponent<'a>; + type Item = Result, &'a str>; fn next(&mut self) -> Option { - // Skip all '-' and '.'. - while self.starts_with(['.', '-']) { + if self.starts_with(['.', '-']) { + let ret = &self[..1]; **self = &self[1..]; + return Some(Err(ret)); } // Get the next character and decide if it is a digit. @@ -92,9 +104,13 @@ impl<'a> Iterator for VersionComponentIter<'a> { assert!(!component.is_empty()); if is_digit { - component.parse::().ok().map(VersionComponent::Number) + component + .parse::() + .ok() + .map(VersionComponent::Number) + .map(Ok) } else { - Some(VersionComponent::Text(component)) + Some(Ok(VersionComponent::Text(component))) } } } @@ -113,14 +129,19 @@ mod tests { fn version_component_iter() { let version = "132.1.2test234-1-man----.--.......---------..---"; - assert_eq!(VersionComponentIter::from(version).collect::>(), [ - Number(132), - Number(1), - Number(2), - Text("test"), - Number(234), - Number(1), - Text("man") - ]); + assert_eq!( + VersionComponentIter::from(version) + .filter_map(Result::ok) + .collect::>(), + [ + Number(132), + Number(1), + Number(2), + Text("test"), + Number(234), + Number(1), + Text("man") + ] + ); } }