@@ -1,10 +1,13 @@
11use std::collections::{BTreeMap, BTreeSet};
2+use std::error::Error;
3+use std::fmt;
24use std::fs::File;
35use std::io::{BufRead, BufReader, LineWriter, Lines, Write};
46use std::path::Path;
57use std::process::Command;
68
79use lazy_regex::regex;
10+use serde::{Deserialize, Deserializer};
811
912pub fn clear() {
1013 handle(true, |loc, _lines| match loc {
@@ -86,11 +89,9 @@ pub fn outdated() {
8689 let latest = latest_version(&package);
8790 if version.version() != latest {
8891 println!(
89− "{} mismatch, {}: {}, hackage: {}",
90− package,
91− version.tag(),
92− version.version(),
93− latest
92+ "{package} mismatch, {tag}: {version}, hackage: {latest}",
93+ tag = version.tag(),
94+ version = version.version(),
9495 );
9596 }
9697 }
@@ -111,18 +112,17 @@ pub fn outdated() {
111112 let dependents = dependents
112113 .into_iter()
113114 .take(max)
114− .map(|(p, v)| format!("{}-{}", p, v))
115+ .map(|(p, v)| format!("{p}-{v}"))
115116 .collect::<Vec<String>>()
116117 .join(", ");
117118 let dependents = if dependents_stripped > 0 {
118− format!("{} and {} more", dependents, dependents_stripped)
119+ format!("{dependents} and {dependents_stripped} more")
119120 } else {
120121 dependents
121122 };
122123
123124 println!(
124− "{} mismatch, snapshot: {}, hackage: {}, dependents: {}",
125− package, version, latest, dependents,
125+ "{package} mismatch, snapshot: {version}, hackage: {latest}, dependents: {dependents}"
126126 );
127127 }
128128 }
@@ -295,3 +295,124 @@ where
295295 let file = File::open(filename).unwrap();
296296 BufReader::new(file).lines()
297297}
298+
299+#[derive(Deserialize)]
300+struct SnapshotYaml {
301+ // flags: BTreeMap<PackageName, BTreeMap<PackageFlag, bool>>,
302+ // publish_time
303+ packages: Vec<SnapshotPackage>,
304+ // hidden
305+ // resolver
306+}
307+
308+#[derive(Deserialize)]
309+struct SnapshotPackage {
310+ hackage: PackageWithVersionAndSha,
311+ // pantry-tree
312+}
313+
314+#[derive(PartialOrd, Ord, PartialEq, Eq, Clone)]
315+struct PackageName(String);
316+
317+impl fmt::Display for PackageName {
318+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
319+ self.0.fmt(f)
320+ }
321+}
322+
323+#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)]
324+struct Version(String);
325+
326+impl fmt::Display for Version {
327+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
328+ self.0.fmt(f)
329+ }
330+}
331+
332+// zstd-0.1.3.0@sha256:4c0a372251068eb6086b8c3a0a9f347488f08b570a7705844ffeb2c720c97223,3723
333+struct PackageWithVersionAndSha {
334+ name: PackageName,
335+ version: Version,
336+}
337+
338+impl<'de> serde::Deserialize<'de> for PackageWithVersionAndSha {
339+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
340+ where
341+ D: Deserializer<'de>,
342+ {
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 })
349+ } else {
350+ Err(serde::de::Error::invalid_value(
351+ serde::de::Unexpected::Other(&s),
352+ &"Invalid PackageVersionWithSha",
353+ ))
354+ }
355+ }
356+}
357+
358+fn yaml_from_file<A, P: AsRef<Path>>(path: P) -> Result<A, Box<dyn Error>>
359+where
360+ A: for<'de> Deserialize<'de>,
361+{
362+ let file = File::open(path)?;
363+ let reader = BufReader::new(file);
364+ let u = serde_yaml::from_reader(reader)?;
365+ Ok(u)
366+}
367+
368+struct Snapshot {
369+ packages: BTreeMap<PackageName, Diff<Version>>,
370+}
371+
372+#[derive(Clone, Copy)]
373+enum Diff<A> {
374+ Left(A),
375+ Right(A),
376+ Both(A, A),
377+}
378+
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));
384+ }
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) {
390+ match a {
391+ Diff::Left(a) => {
392+ if a == version {
393+ packages.remove(&name);
394+ } else {
395+ packages.insert(name, Diff::Both(a, version));
396+ }
397+ }
398+ _ => unreachable!(),
399+ }
400+ } else {
401+ packages.insert(name, Diff::Right(version));
402+ }
403+ }
404+
405+ Snapshot { packages }
406+}
407+
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}"),
415+ };
416+ println!("{s}");
417+ }
418+}