@@ -1,10 +1,13 @@
1use std::collections::{BTreeMap, BTreeSet};
1use std::collections::{BTreeMap, BTreeSet};
2use std::fs::File;
4use std::fs::File;
3use std::io::{BufRead, BufReader, LineWriter, Lines, Write};
5use std::io::{BufRead, BufReader, LineWriter, Lines, Write};
4use std::path::Path;
6use std::path::Path;
5use std::process::Command;
7use std::process::Command;
7use lazy_regex::regex;
9use lazy_regex::regex;
10+use serde::{Deserialize, Deserializer};
9pub fn clear() {
12pub fn clear() {
10 handle(true, |loc, _lines| match loc {
13 handle(true, |loc, _lines| match loc {
@@ -86,11 +89,9 @@ pub fn outdated() {
86 let latest = latest_version(&package);
89 let latest = latest_version(&package);
87 if version.version() != latest {
90 if version.version() != latest {
89− "{} mismatch, {}: {}, hackage: {}",
92+ "{package} mismatch, {tag}: {version}, hackage: {latest}",
90− package,
93+ tag = version.tag(),
91− version.tag(),
94+ version = version.version(),
@@ -111,18 +112,17 @@ pub fn outdated() {
111 let dependents = dependents
112 let dependents = dependents
112 .into_iter()
113 .into_iter()
113 .take(max)
114 .take(max)
114− .map(|(p, v)| format!("{}-{}", p, v))
115+ .map(|(p, v)| format!("{p}-{v}"))
115 .collect::<Vec<String>>()
116 .collect::<Vec<String>>()
116 .join(", ");
117 .join(", ");
117 let dependents = if dependents_stripped > 0 {
118 let dependents = if dependents_stripped > 0 {
118− format!("{} and {} more", dependents, dependents_stripped)
119+ format!("{dependents} and {dependents_stripped} more")
120 dependents
121 dependents
123 println!(
124 println!(
124− "{} mismatch, snapshot: {}, hackage: {}, dependents: {}",
125+ "{package} mismatch, snapshot: {version}, hackage: {latest}, dependents: {dependents}"
125− package, version, latest, dependents,
@@ -295,3 +295,124 @@ where
295 let file = File::open(filename).unwrap();
295 let file = File::open(filename).unwrap();
296 BufReader::new(file).lines()
296 BufReader::new(file).lines()
299+#[derive(Deserialize)]
300+struct SnapshotYaml {
301+ // flags: BTreeMap<PackageName, BTreeMap<PackageFlag, bool>>,
303+ packages: Vec<SnapshotPackage>,
308+#[derive(Deserialize)]
309+struct SnapshotPackage {
310+ hackage: PackageWithVersionAndSha,
314+#[derive(PartialOrd, Ord, PartialEq, Eq, Clone)]
315+struct PackageName(String);
317+impl fmt::Display for PackageName {
318+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323+#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)]
324+struct Version(String);
326+impl fmt::Display for Version {
327+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
332+// zstd-0.1.3.0@sha256:4c0a372251068eb6086b8c3a0a9f347488f08b570a7705844ffeb2c720c97223,3723
333+struct PackageWithVersionAndSha {
338+impl<'de> serde::Deserialize<'de> for PackageWithVersionAndSha {
339+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341+ D: Deserializer<'de>,
343+ let s: String = String::deserialize(deserializer)?;
344+ let r = regex!(r#"^(.+?)-([.\d]+)@sha256:[\da-z]+,\d+$"#);
345+ if let Some(caps) = r.captures(&s) {
346+ let name = PackageName(caps.get(1).unwrap().as_str().to_owned());
347+ let version = Version(caps.get(2).unwrap().as_str().to_owned());
348+ Ok(Self { name, version })
350+ Err(serde::de::Error::invalid_value(
351+ serde::de::Unexpected::Other(&s),
352+ &"Invalid PackageVersionWithSha",
358+fn yaml_from_file<A, P: AsRef<Path>>(path: P) -> Result<A, Box<dyn Error>>
360+ A: for<'de> Deserialize<'de>,
362+ let file = File::open(path)?;
363+ let reader = BufReader::new(file);
364+ let u = serde_yaml::from_reader(reader)?;
369+ packages: BTreeMap<PackageName, Diff<Version>>,
372+#[derive(Clone, Copy)]
379+fn to_diff(a: SnapshotYaml, b: SnapshotYaml) -> Snapshot {
380+ let mut packages = BTreeMap::new();
381+ for s in a.packages {
382+ let package = s.hackage;
383+ packages.insert(package.name, Diff::Left(package.version));
385+ for s in b.packages {
386+ let package = s.hackage;
387+ let name = package.name;
388+ let version = package.version;
389+ if let Some(a) = packages.remove(&name) {
393+ packages.remove(&name);
395+ packages.insert(name, Diff::Both(a, version));
398+ _ => unreachable!(),
401+ packages.insert(name, Diff::Right(version));
405+ Snapshot { packages }
408+pub fn diff_snapshot(a: String, b: String) {
409+ let diff = to_diff(yaml_from_file(a).unwrap(), yaml_from_file(b).unwrap());
410+ for (name, diff) in diff.packages {
411+ let s = match diff {
412+ Diff::Left(a) => format!("- {name}-{a}"),
413+ Diff::Right(b) => format!("+ {name}-{b}"),
414+ Diff::Both(a, b) => format!("~ {name}-{a} -> {b}"),