1use guppy::{graph::PackageMetadata, PackageId};
5use serde::{ser::SerializeStruct, Serialize, Serializer};
6use std::{collections::HashMap, ops::Deref};
7
8#[derive(Debug, Default)]
9pub struct DiffOptions;
10
11impl DiffOptions {
12 pub fn diff<'a>(
13 &self,
14 old_packages: &'a [PackageMetadata<'a>],
15 new_packages: &'a [PackageMetadata<'a>],
16 ) -> Diff<'a> {
17 let mut new: HashMap<&PackageId, Package> =
18 new_packages.iter().map(|p| (p.id(), Package(p))).collect();
19
20 let mut removed = old_packages
21 .iter()
22 .filter_map(|package| {
23 if new.remove(package.id()).is_none() {
24 Some(Package(package))
25 } else {
26 None
27 }
28 })
29 .map(|removed_package| {
30 let remaining_packages = new_packages
31 .iter()
32 .filter_map(|package| {
33 if (package.id() != removed_package.id())
34 && (package.name() == removed_package.name())
35 {
36 Some(Package(package))
37 } else {
38 None
39 }
40 })
41 .collect::<Vec<_>>();
42
43 if remaining_packages.is_empty() {
44 (removed_package.id().clone(), (removed_package, None))
45 } else {
46 (
47 removed_package.id().clone(),
48 (removed_package, Some(remaining_packages)),
49 )
50 }
51 })
52 .collect::<HashMap<_, _>>();
53
54 let mut added = new
55 .into_iter()
56 .map(|(added_package_id, added_package)| {
57 let existing_packages = new_packages
58 .iter()
59 .filter_map(|package| {
60 if (package.id() != added_package_id)
61 && (package.name() == added_package.name())
62 {
63 Some(Package(package))
64 } else {
65 None
66 }
67 })
68 .collect::<Vec<_>>();
69
70 if existing_packages.is_empty() {
71 (added_package_id, (added_package, None))
72 } else {
73 (added_package_id, (added_package, Some(existing_packages)))
74 }
75 })
76 .collect::<HashMap<_, _>>();
77
78 let mut updated = removed
79 .iter()
80 .filter_map(|(_, (removed_package, _remaining_packages))| {
81 if let Some((_updated_package_id, (updated_package, _))) =
82 added
83 .iter()
84 .find(|(_added_package_id, (added_package, _))| {
85 removed_package.name() == added_package.name()
86 })
87 {
88 Some((removed_package.clone(), updated_package.clone()))
89 } else {
90 None
91 }
92 })
93 .collect::<Vec<_>>();
94 updated.sort_by(|a, b| a.1.name().cmp(b.1.name()));
95
96 for (removed_pkg, added_pkg) in &updated {
98 removed.remove(removed_pkg.id());
99 added.remove(added_pkg.id());
100 }
101
102 let mut removed = removed.into_iter().map(|x| x.1).collect::<Vec<_>>();
103 removed.sort_by(|(a, _), (b, _)| a.name().cmp(b.name()));
104 let mut added = added.into_iter().map(|x| x.1).collect::<Vec<_>>();
105 added.sort_by(|(a, _), (b, _)| a.name().cmp(b.name()));
106
107 Diff {
108 updated,
109 removed,
110 added,
111 }
112 }
113}
114
115#[derive(Clone, Debug)]
116struct Package<'a>(pub &'a PackageMetadata<'a>);
117
118impl<'a> Deref for Package<'a> {
119 type Target = PackageMetadata<'a>;
120
121 fn deref(&self) -> &Self::Target {
122 self.0
123 }
124}
125
126impl Serialize for Package<'_> {
127 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128 where
129 S: Serializer,
130 {
131 let mut state = serializer.serialize_struct("Package", 3)?;
132 state.serialize_field("id", self.0.id().repr())?;
133 state.serialize_field("name", self.0.name())?;
134 state.serialize_field("version", self.0.version())?;
135 state.end()
136 }
137}
138
139#[derive(Debug, Serialize)]
140pub struct Diff<'a> {
141 updated: Vec<(Package<'a>, Package<'a>)>,
142 removed: Vec<(Package<'a>, Option<Vec<Package<'a>>>)>,
143 added: Vec<(Package<'a>, Option<Vec<Package<'a>>>)>,
144}
145
146impl ::std::fmt::Display for Diff<'_> {
147 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
148 fn write_dups(
149 f: &mut ::std::fmt::Formatter<'_>,
150 dups: &Option<Vec<Package>>,
151 ) -> ::std::fmt::Result {
152 if let Some(dups) = dups {
153 write!(f, " ({}", dups[0].version())?;
154 for p in &dups[1..] {
155 write!(f, ", {}", p.version())?;
156 }
157 write!(f, ")")?;
158 }
159
160 Ok(())
161 }
162
163 if !self.added.is_empty() {
164 writeln!(f, "Added Packages (Duplicate versions in '()'):")?;
165 for (added, dups) in &self.added {
166 write!(f, "\t{} {}", added.name(), added.version(),)?;
167
168 write_dups(f, dups)?;
169 writeln!(f)?;
170 }
171 writeln!(f)?;
172 }
173
174 if !self.removed.is_empty() {
175 writeln!(f, "Removed Packages (Remaining versions in '()'):")?;
176 for (removed, dups) in &self.removed {
177 write!(f, "\t{} {}", removed.name(), removed.version(),)?;
178
179 write_dups(f, dups)?;
180 writeln!(f)?;
181 }
182 writeln!(f)?;
183 }
184
185 if !self.updated.is_empty() {
186 writeln!(f, "Updated Packages:")?;
187 for (removed, added) in &self.updated {
188 writeln!(
189 f,
190 "\t{}: {} -> {}",
191 removed.name(),
192 removed.version(),
193 added.version(),
194 )?;
195 }
196 writeln!(f)?;
197 }
198
199 Ok(())
200 }
201}