1
Fork 0
mirror of https://github.com/RGBCube/dix synced 2025-07-28 12:17:45 +00:00

feat: fix formatting, yield ignored chars in version component iter

This commit is contained in:
RGBCube 2025-05-09 15:52:19 +03:00 committed by bloxx12
parent 4f0ee04e19
commit a1ccf88d0c
3 changed files with 155 additions and 96 deletions

View file

@ -1,12 +1,12 @@
use std::{ use std::fmt::{
fmt::{
self, self,
Write as _, Write as _,
},
sync,
}; };
use itertools::EitherOrBoth; use itertools::{
EitherOrBoth,
Itertools,
};
use ref_cast::RefCast as _; use ref_cast::RefCast as _;
use rustc_hash::{ use rustc_hash::{
FxBuildHasher, FxBuildHasher,
@ -20,9 +20,7 @@ use crate::{
Version, Version,
}; };
const HEADER_STYLE: yansi::Style = yansi::Style::new().bold().underline(); #[derive(Debug, Default)]
#[derive(Default)]
struct Diff<T> { struct Diff<T> {
old: T, old: T,
new: T, new: T,
@ -110,9 +108,18 @@ pub fn diff<'a>(
for (name, versions, status) in diffs { for (name, versions, status) in diffs {
if last_status != Some(status) { if last_status != Some(status) {
last_status = Some(status); last_status = Some(status);
HEADER_STYLE.fmt_prefix(writer)?;
writeln!(writer, "{status:?} packages:")?; writeln!(
HEADER_STYLE.fmt_suffix(writer)?; 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!( write!(
@ -122,56 +129,112 @@ pub fn diff<'a>(
)?; )?;
let mut oldacc = String::new(); let mut oldacc = String::new();
let mut oldwrote = false;
let mut newacc = String::new(); let mut newacc = String::new();
let mut newwrote = false;
for diff in itertools::Itertools::zip_longest( for diff in Itertools::zip_longest(versions.old.iter(), versions.new.iter())
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("<none>")) {
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_version) => {
if newwrote {
write!(newacc, ", ")?;
} else {
write!(newacc, " ")?;
newwrote = true;
}
for new_comp in new_version.unwrap_or(Version::ref_cast("<none>")) {
match new_comp {
Ok(new_comp) => write!(newacc, "{new}", new = new_comp.green())?,
Err(ignored) => write!(newacc, "{ignored}")?,
}
}
},
EitherOrBoth::Both(old_version, new_version) => {
if old_version == new_version {
continue;
}
let old_version = old_version.unwrap_or(Version::ref_cast("<none>"));
let new_version = new_version.unwrap_or(Version::ref_cast("<none>"));
if oldwrote {
write!(oldacc, ", ")?;
} else {
write!(oldacc, " ")?;
oldwrote = true;
}
if newwrote {
write!(newacc, ", ")?;
} else {
write!(newacc, " ")?;
newwrote = true;
}
for diff in Itertools::zip_longest(
old_version.into_iter(),
new_version.into_iter(),
) { ) {
match diff { match diff {
// I have no idea why itertools is returning `versions.new` in `Left`. EitherOrBoth::Right(old_comp) => {
EitherOrBoth::Left(new) => { match old_comp {
let new = new.unwrap_or(Version::ref_cast("<none>")); Ok(old_comp) => {
write!(oldacc, "{old}", old = old_comp.red())?;
write!(newacc, " {new}", new = new.green())?; },
Err(ignored) => {
write!(oldacc, "{ignored}")?;
},
}
}, },
EitherOrBoth::Right(old) => { EitherOrBoth::Left(new_comp) => {
let old = old.unwrap_or(Version::ref_cast("<none>")); match new_comp {
Ok(new_comp) => {
write!(oldacc, " {old}", old = old.red())?; write!(newacc, "{new}", new = new_comp.green())?;
},
Err(ignored) => {
write!(newacc, "{ignored}")?;
},
}
}, },
EitherOrBoth::Both(old, new) => { EitherOrBoth::Both(old_comp, new_comp) => {
static NAME_SUFFIX_REGEX: sync::LazyLock<regex::Regex> = if let Err(ignored) = old_comp {
sync::LazyLock::new(|| { write!(oldacc, "{ignored}")?;
regex::Regex::new("(-man|-lib|-doc|-dev|-out|-terminfo)") }
.expect("failed to compile regex for Nix store path versions")
});
let old = old.unwrap_or(Version::ref_cast("<none>")); if let Err(ignored) = new_comp {
let new = new.unwrap_or(Version::ref_cast("<none>")); write!(newacc, "{ignored}")?;
}
let suffix = NAME_SUFFIX_REGEX.captures(old).map_or("", |matches| { if let (Ok(old_comp), Ok(new_comp)) = (old_comp, new_comp) {
matches.get(0).map_or("", |capture| capture.as_str()) if old_comp == new_comp {
}); write!(oldacc, "{old}", old = old_comp.yellow())?;
write!(newacc, "{new}", new = new_comp.yellow())?;
let old = old.strip_suffix(suffix).unwrap_or(old); } else {
let new = new.strip_suffix(suffix).unwrap_or(new); write!(oldacc, "{old}", old = old_comp.red())?;
write!(newacc, "{new}", new = new_comp.green())?;
for diff in diff::chars(old, new) { }
match diff { }
diff::Result::Left(oldc) => {
write!(oldacc, "{oldc}", oldc = oldc.red()).unwrap();
},
diff::Result::Right(newc) => {
write!(newacc, "{newc}", newc = newc.green()).unwrap();
},
diff::Result::Both(oldc, newc) => {
write!(oldacc, "{oldc}", oldc = oldc.yellow()).unwrap();
write!(newacc, "{newc}", newc = newc.yellow()).unwrap();
}, },
} }
} }

View file

@ -111,31 +111,6 @@ fn real_main() -> Result<()> {
paths_old.iter().map(|(_, path)| path), paths_old.iter().map(|(_, path)| path),
paths_new.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!("Difference between the two generations:");
// println!(); // println!();

View file

@ -20,15 +20,26 @@ impl PartialOrd for Version {
impl cmp::Ord for Version { impl cmp::Ord for Version {
fn cmp(&self, that: &Self) -> cmp::Ordering { fn cmp(&self, that: &Self) -> cmp::Ordering {
let this = VersionComponentIter::from(&**self); let this = VersionComponentIter::from(&**self).filter_map(Result::ok);
let that = VersionComponentIter::from(&**that); let that = VersionComponentIter::from(&**that).filter_map(Result::ok);
this.cmp(that) this.cmp(that)
} }
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[expect(clippy::into_iter_without_iter)]
enum VersionComponent<'a> { impl<'a> IntoIterator for &'a Version {
type Item = Result<VersionComponent<'a>, &'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), Number(u64),
Text(&'a str), Text(&'a str),
} }
@ -47,12 +58,12 @@ impl cmp::Ord for VersionComponent<'_> {
}; };
match (*self, *other) { match (*self, *other) {
(Number(x), Number(y)) => x.cmp(&y), (Number(this), Number(that)) => this.cmp(&that),
(Text(x), Text(y)) => { (Text(this), Text(that)) => {
match (x, y) { match (this, that) {
("pre", _) => cmp::Ordering::Less, ("pre", _) => cmp::Ordering::Less,
(_, "pre") => cmp::Ordering::Greater, (_, "pre") => cmp::Ordering::Greater,
_ => x.cmp(y), _ => this.cmp(that),
} }
}, },
(Text(_), Number(_)) => cmp::Ordering::Less, (Text(_), Number(_)) => cmp::Ordering::Less,
@ -63,15 +74,16 @@ impl cmp::Ord for VersionComponent<'_> {
/// Yields [`VertionComponent`] from a version string. /// Yields [`VertionComponent`] from a version string.
#[derive(Deref, DerefMut, From)] #[derive(Deref, DerefMut, From)]
struct VersionComponentIter<'a>(&'a str); pub struct VersionComponentIter<'a>(&'a str);
impl<'a> Iterator for VersionComponentIter<'a> { impl<'a> Iterator for VersionComponentIter<'a> {
type Item = VersionComponent<'a>; type Item = Result<VersionComponent<'a>, &'a str>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
// Skip all '-' and '.'. if self.starts_with(['.', '-']) {
while self.starts_with(['.', '-']) { let ret = &self[..1];
**self = &self[1..]; **self = &self[1..];
return Some(Err(ret));
} }
// Get the next character and decide if it is a digit. // 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()); assert!(!component.is_empty());
if is_digit { if is_digit {
component.parse::<u64>().ok().map(VersionComponent::Number) component
.parse::<u64>()
.ok()
.map(VersionComponent::Number)
.map(Ok)
} else { } else {
Some(VersionComponent::Text(component)) Some(Ok(VersionComponent::Text(component)))
} }
} }
} }
@ -113,7 +129,11 @@ mod tests {
fn version_component_iter() { fn version_component_iter() {
let version = "132.1.2test234-1-man----.--.......---------..---"; let version = "132.1.2test234-1-man----.--.......---------..---";
assert_eq!(VersionComponentIter::from(version).collect::<Vec<_>>(), [ assert_eq!(
VersionComponentIter::from(version)
.filter_map(Result::ok)
.collect::<Vec<_>>(),
[
Number(132), Number(132),
Number(1), Number(1),
Number(2), Number(2),
@ -121,6 +141,7 @@ mod tests {
Number(234), Number(234),
Number(1), Number(1),
Text("man") Text("man")
]); ]
);
} }
} }