1use std::{collections::BTreeMap, fmt::Debug, str::FromStr};
2
3use tracing::debug;
4
5use super::{
6 Label, PreVersion, Prerelease, Rule, StableVersion, Version, prerelease_map::PrereleaseMap,
7};
8use crate::semver::rule::Stable;
9
10#[derive(Clone, Debug, Default, Eq, PartialEq)]
16pub struct PackageVersions {
17 stable: StableVersion,
18 prereleases: Prereleases,
19}
20
21type Prereleases = BTreeMap<StableVersion, PrereleaseMap>;
22
23impl PackageVersions {
24 pub fn from_tags<S: AsRef<str> + Debug>(prefix: Option<&str>, all_tags: &[S]) -> Self {
32 let pattern = prefix
33 .as_ref()
34 .map_or_else(|| String::from("v"), |prefix| format!("{prefix}/v"));
35 let mut tags = all_tags
36 .iter()
37 .filter(|tag| tag.as_ref().starts_with(&pattern))
38 .peekable();
39
40 if tags.peek().is_none() {
41 debug!("No tags found matching pattern {pattern}");
42 }
43
44 let mut current_versions = Self::default();
45 for tag in tags {
46 let version_string = tag.as_ref().replace(&pattern, "");
47 if let Ok(version) = Version::from_str(version_string.as_str()) {
48 match version {
49 Version::Stable(stable) => {
50 current_versions.stable = stable;
51 break; }
53 Version::Pre(_) => {
54 current_versions.update_version(version);
55 }
56 }
57 }
58 }
59
60 current_versions
61 }
62
63 #[must_use]
65 pub fn into_latest(mut self) -> Version {
66 self.prereleases.pop_last().map_or(
67 Version::Stable(self.stable),
68 |(stable_component, pres)| {
69 let pre_component = pres.into_last();
70 Version::Pre(PreVersion {
71 stable_component,
72 pre_component,
73 })
74 },
75 )
76 }
77
78 pub fn update_version(&mut self, version: Version) {
85 match version {
86 Version::Stable(new) => {
87 if self.stable >= new {
88 return;
89 }
90 self.stable = new;
91 self.prereleases.clear();
92 }
93 Version::Pre(PreVersion {
94 stable_component,
95 pre_component,
96 }) => {
97 let recorded_pre = self
98 .prereleases
99 .get(&stable_component)
100 .and_then(|pres| pres.get(&pre_component.label));
101 if let Some(recorded_pre) = recorded_pre {
102 if recorded_pre >= &pre_component {
103 return;
104 }
105 }
106 if let Some(labels) = self.prereleases.get_mut(&stable_component) {
107 labels.insert(pre_component);
108 } else {
109 self.prereleases
110 .insert(stable_component, PrereleaseMap::new(pre_component));
111 }
112 }
113 }
114 }
115
116 pub fn bump(&mut self, rule: Rule) -> Result<(), PreReleaseNotFound> {
129 match rule {
130 Rule::Major => self.update_version(bump_stable(self.stable, Stable::Major).into()),
131 Rule::Minor => self.update_version(bump_stable(self.stable, Stable::Minor).into()),
132 Rule::Patch => self.update_version(bump_stable(self.stable, Stable::Patch).into()),
133 Rule::Release => {
134 let version = self
135 .prereleases
136 .pop_last()
137 .map(|(version, _pre)| version)
138 .ok_or(PreReleaseNotFound)?
139 .into();
140 self.update_version(version);
141 }
142 Rule::Pre { label, stable_rule } => self.bump_pre(label, stable_rule),
143 }
144 Ok(())
145 }
146
147 #[must_use]
148 pub fn stable(&self) -> StableVersion {
149 self.stable
150 }
151
152 fn bump_pre(&mut self, label: Label, stable_rule: Stable) {
158 debug!("Pre-release label {label} selected. Determining next stable version...");
159 let stable_component = bump_stable(self.stable, stable_rule);
160 let pre_version = self
161 .prereleases
162 .get(&stable_component)
163 .and_then(|pres| {
164 pres.get(&label).map(|pre| {
165 debug!("Found existing pre-release version {pre}");
166 pre.version + 1
167 })
168 })
169 .unwrap_or_default();
170 let pre = Prerelease::new(label, pre_version);
171 if pre_version == 0 {
172 debug!("No existing pre-release version found; creating {pre}");
173 }
174
175 self.prereleases.clear();
176
177 self.update_version(Version::Pre(PreVersion {
178 stable_component,
179 pre_component: pre,
180 }));
181 }
182}
183
184fn bump_stable(version: StableVersion, rule: Stable) -> StableVersion {
185 let is_0 = version.major == 0;
186 match (rule, is_0) {
187 (Stable::Major, false) => {
188 let new = version.increment_major();
189 debug!("Using MAJOR rule to bump from {version} to {new}");
190 new
191 }
192 (Stable::Minor, false) => {
193 let new = version.increment_minor();
194 debug!("Using MINOR rule to bump from {version} to {new}");
195 new
196 }
197 (Stable::Major, true) => {
198 let new = version.increment_minor();
199 debug!(
200 "Rule is MAJOR, but major component is 0. Bumping minor component from {version} to {new}"
201 );
202 new
203 }
204 (Stable::Minor, true) => {
205 let new = version.increment_patch();
206 debug!(
207 "Rule is MINOR, but major component is 0. Bumping patch component from {version} to {new}"
208 );
209 new
210 }
211 (Stable::Patch, _) => {
212 let new = version.increment_patch();
213 debug!("Using PATCH rule to bump from {version} to {new}");
214 new
215 }
216 }
217}
218
219#[derive(Debug, thiserror::Error)]
236#[error("No prerelease version found, but a Release rule was requested")]
237pub struct PreReleaseNotFound;
238
239impl From<StableVersion> for PackageVersions {
240 fn from(version: StableVersion) -> Self {
241 Self {
242 stable: version,
243 prereleases: BTreeMap::new(),
244 }
245 }
246}
247
248impl From<Version> for PackageVersions {
249 fn from(version: Version) -> Self {
250 let mut new = Self::default();
251 new.update_version(version);
252 new
253 }
254}
255
256#[cfg(test)]
257#[allow(clippy::unwrap_used)]
258mod test_from_tags {
259 use std::str::FromStr;
260
261 use pretty_assertions::assert_eq;
262
263 use crate::semver::{PackageVersions, Prerelease, StableVersion, Version};
264 #[test]
265 fn collect_all_newer_pre_releases() {
266 let tags = [
267 "v2.0.0-alpha.0",
268 "v1.3.0-beta.0",
269 "v1.3.0-alpha.1",
270 "v1.3.0-alpha.0",
271 "v1.2.4-rc.0",
272 "v1.2.3",
273 ]
274 .map(String::from);
275
276 let versions = PackageVersions::from_tags(None, &tags);
277
278 assert_eq!(
279 versions.stable(),
280 StableVersion {
281 major: 1,
282 minor: 2,
283 patch: 3
284 }
285 );
286
287 assert_eq!(
288 versions.clone().into_latest(),
289 Version::from_str("2.0.0-alpha.0").unwrap()
290 );
291 assert_eq!(
292 *versions
293 .prereleases
294 .get(&StableVersion {
295 major: 1,
296 minor: 3,
297 patch: 0
298 })
299 .unwrap()
300 .get(&"alpha".into())
301 .unwrap(),
302 Prerelease::new("alpha".into(), 1)
303 );
304
305 assert_eq!(
306 *versions
307 .prereleases
308 .get(&StableVersion {
309 major: 1,
310 minor: 3,
311 patch: 0
312 })
313 .unwrap()
314 .get(&"beta".into())
315 .unwrap(),
316 Prerelease::new("beta".into(), 0)
317 );
318 }
319}
320
321#[cfg(test)]
322#[allow(clippy::unwrap_used)]
323mod test_bump {
324 use std::str::FromStr;
325
326 use super::*;
327
328 #[test]
329 fn major() {
330 let mut versions: PackageVersions = Version::new(1, 2, 3, None).into();
331 versions.bump(Rule::Major).unwrap();
332
333 assert_eq!(versions.into_latest(), Version::new(2, 0, 0, None));
334 }
335
336 #[test]
337 fn major_0() {
338 let mut versions = PackageVersions::from(Version::new(0, 1, 2, None));
339 versions.bump(Rule::Major).unwrap();
340
341 assert_eq!(versions.into_latest(), Version::new(0, 2, 0, None));
342 }
343
344 #[test]
345 fn major_unset() {
346 let mut versions = PackageVersions::default();
347 versions.bump(Rule::Major).unwrap();
348
349 assert_eq!(versions.into_latest(), Version::new(0, 1, 0, None));
350 }
351
352 #[test]
353 fn major_after_pre() {
354 for pre_version in ["1.2.4-rc.0", "1.3.0-rc.0", "2.0.0-rc.0"] {
355 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
356 versions.update_version(Version::from_str(pre_version).unwrap());
357 versions.bump(Rule::Major).unwrap();
358
359 assert_eq!(versions.into_latest(), Version::new(2, 0, 0, None));
360 }
361 }
362
363 #[test]
364 fn minor() {
365 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
366 versions.bump(Rule::Minor).unwrap();
367
368 assert_eq!(versions.into_latest(), Version::new(1, 3, 0, None));
369 }
370
371 #[test]
372 fn minor_0() {
373 let mut versions = PackageVersions::from(Version::new(0, 1, 2, None));
374 versions.bump(Rule::Minor).unwrap();
375
376 assert_eq!(versions.into_latest(), Version::new(0, 1, 3, None));
377 }
378
379 #[test]
380 fn minor_unset() {
381 let mut versions = PackageVersions::default();
382 versions.bump(Rule::Minor).unwrap();
383
384 assert_eq!(versions.into_latest(), Version::new(0, 0, 1, None));
385 }
386
387 #[test]
388 fn minor_after_pre() {
389 for pre_version in ["1.2.4-rc.0", "1.3.0-rc.0"] {
390 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
391 versions.update_version(Version::from_str(pre_version).unwrap());
392 versions.bump(Rule::Minor).unwrap();
393
394 assert_eq!(versions.into_latest(), Version::new(1, 3, 0, None));
395 }
396 }
397
398 #[test]
399 fn patch() {
400 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
401 versions.bump(Rule::Patch).unwrap();
402
403 assert_eq!(versions.into_latest(), Version::new(1, 2, 4, None));
404 }
405
406 #[test]
407 fn patch_0() {
408 let mut versions = PackageVersions::from(Version::new(0, 1, 0, None));
409 versions.bump(Rule::Patch).unwrap();
410
411 assert_eq!(versions.into_latest(), Version::new(0, 1, 1, None));
412 }
413
414 #[test]
415 fn patch_unset() {
416 let mut versions = PackageVersions::default();
417 versions.bump(Rule::Patch).unwrap();
418
419 assert_eq!(versions.into_latest(), Version::new(0, 0, 1, None));
420 }
421
422 #[test]
423 fn patch_after_pre() {
424 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
425 versions.update_version(Version::from_str("1.2.4-rc.0").unwrap());
426 versions.bump(Rule::Patch).unwrap();
427
428 assert_eq!(versions.into_latest(), Version::new(1, 2, 4, None));
429 }
430
431 #[test]
432 fn pre() {
433 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
434 versions
435 .bump(Rule::Pre {
436 label: Label::from("rc"),
437 stable_rule: Stable::Minor,
438 })
439 .unwrap();
440
441 assert_eq!(
442 versions.into_latest(),
443 Version::from_str("1.3.0-rc.0").unwrap()
444 );
445 }
446
447 #[test]
448 fn pre_after_same_pre() {
449 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
450 versions.update_version(Version::from_str("1.3.0-rc.0").unwrap());
451 versions.update_version(Version::from_str("1.2.4-rc.1").unwrap());
452 versions.update_version(Version::from_str("2.0.0-rc.2").unwrap());
453 versions
454 .bump(Rule::Pre {
455 label: Label::from("rc"),
456 stable_rule: Stable::Minor,
457 })
458 .unwrap();
459
460 assert_eq!(
461 versions.into_latest(),
462 Version::from_str("1.3.0-rc.1").unwrap()
463 );
464 }
465
466 #[test]
467 fn pre_after_different_pre_version() {
468 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
469 versions.update_version(Version::from_str("1.2.4-beta.1").unwrap());
470 versions.update_version(Version::from_str("1.2.4-rc.0").unwrap());
471 versions
472 .bump(Rule::Pre {
473 label: Label::from("beta"),
474 stable_rule: Stable::Patch,
475 })
476 .unwrap();
477
478 assert_eq!(
479 versions.into_latest(),
480 Version::from_str("1.2.4-beta.2").unwrap()
481 );
482 }
483
484 #[test]
485 fn pre_after_different_pre_label() {
486 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
487 versions.update_version(Version::from_str("1.3.0-beta.0").unwrap());
488 versions
489 .bump(Rule::Pre {
490 label: Label::from("rc"),
491 stable_rule: Stable::Minor,
492 })
493 .unwrap();
494
495 assert_eq!(
496 versions.into_latest(),
497 Version::from_str("1.3.0-rc.0").unwrap()
498 );
499 }
500
501 #[test]
502 fn release() {
503 let mut versions = PackageVersions::default();
504 versions.update_version(Version::from_str("1.2.3-rc.0").unwrap());
505 versions.update_version(Version::from_str("1.2.4-rc.1").unwrap());
506 versions.update_version(Version::from_str("2.0.0-rc.2").unwrap());
507
508 versions.bump(Rule::Release).unwrap();
509
510 assert_eq!(versions.into_latest(), Version::new(2, 0, 0, None));
511 }
512}