1use std::{collections::BTreeMap, fmt::Debug, str::FromStr};
2
3use tracing::debug;
4
5use super::{
6 prerelease_map::PrereleaseMap, Label, PreVersion, Prerelease, Rule, StableVersion, Version,
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!("Rule is MAJOR, but major component is 0. Bumping minor component from {version} to {new}");
200 new
201 }
202 (Stable::Minor, true) => {
203 let new = version.increment_patch();
204 debug!("Rule is MINOR, but major component is 0. Bumping patch component from {version} to {new}");
205 new
206 }
207 (Stable::Patch, _) => {
208 let new = version.increment_patch();
209 debug!("Using PATCH rule to bump from {version} to {new}");
210 new
211 }
212 }
213}
214
215#[derive(Debug, thiserror::Error)]
232#[error("No prerelease version found, but a Release rule was requested")]
233pub struct PreReleaseNotFound;
234
235impl From<StableVersion> for PackageVersions {
236 fn from(version: StableVersion) -> Self {
237 Self {
238 stable: version,
239 prereleases: BTreeMap::new(),
240 }
241 }
242}
243
244impl From<Version> for PackageVersions {
245 fn from(version: Version) -> Self {
246 let mut new = Self::default();
247 new.update_version(version);
248 new
249 }
250}
251
252#[cfg(test)]
253#[allow(clippy::unwrap_used)]
254mod test_from_tags {
255 use std::str::FromStr;
256
257 use pretty_assertions::assert_eq;
258
259 use crate::semver::{PackageVersions, Prerelease, StableVersion, Version};
260 #[test]
261 fn collect_all_newer_pre_releases() {
262 let tags = [
263 "v2.0.0-alpha.0",
264 "v1.3.0-beta.0",
265 "v1.3.0-alpha.1",
266 "v1.3.0-alpha.0",
267 "v1.2.4-rc.0",
268 "v1.2.3",
269 ]
270 .map(String::from);
271
272 let versions = PackageVersions::from_tags(None, &tags);
273
274 assert_eq!(
275 versions.stable(),
276 StableVersion {
277 major: 1,
278 minor: 2,
279 patch: 3
280 }
281 );
282
283 assert_eq!(
284 versions.clone().into_latest(),
285 Version::from_str("2.0.0-alpha.0").unwrap()
286 );
287 assert_eq!(
288 *versions
289 .prereleases
290 .get(&StableVersion {
291 major: 1,
292 minor: 3,
293 patch: 0
294 })
295 .unwrap()
296 .get(&"alpha".into())
297 .unwrap(),
298 Prerelease::new("alpha".into(), 1)
299 );
300
301 assert_eq!(
302 *versions
303 .prereleases
304 .get(&StableVersion {
305 major: 1,
306 minor: 3,
307 patch: 0
308 })
309 .unwrap()
310 .get(&"beta".into())
311 .unwrap(),
312 Prerelease::new("beta".into(), 0)
313 );
314 }
315}
316
317#[cfg(test)]
318#[allow(clippy::unwrap_used)]
319mod test_bump {
320 use std::str::FromStr;
321
322 use super::*;
323
324 #[test]
325 fn major() {
326 let mut versions: PackageVersions = Version::new(1, 2, 3, None).into();
327 versions.bump(Rule::Major).unwrap();
328
329 assert_eq!(versions.into_latest(), Version::new(2, 0, 0, None));
330 }
331
332 #[test]
333 fn major_0() {
334 let mut versions = PackageVersions::from(Version::new(0, 1, 2, None));
335 versions.bump(Rule::Major).unwrap();
336
337 assert_eq!(versions.into_latest(), Version::new(0, 2, 0, None));
338 }
339
340 #[test]
341 fn major_unset() {
342 let mut versions = PackageVersions::default();
343 versions.bump(Rule::Major).unwrap();
344
345 assert_eq!(versions.into_latest(), Version::new(0, 1, 0, None));
346 }
347
348 #[test]
349 fn major_after_pre() {
350 for pre_version in ["1.2.4-rc.0", "1.3.0-rc.0", "2.0.0-rc.0"] {
351 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
352 versions.update_version(Version::from_str(pre_version).unwrap());
353 versions.bump(Rule::Major).unwrap();
354
355 assert_eq!(versions.into_latest(), Version::new(2, 0, 0, None));
356 }
357 }
358
359 #[test]
360 fn minor() {
361 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
362 versions.bump(Rule::Minor).unwrap();
363
364 assert_eq!(versions.into_latest(), Version::new(1, 3, 0, None));
365 }
366
367 #[test]
368 fn minor_0() {
369 let mut versions = PackageVersions::from(Version::new(0, 1, 2, None));
370 versions.bump(Rule::Minor).unwrap();
371
372 assert_eq!(versions.into_latest(), Version::new(0, 1, 3, None));
373 }
374
375 #[test]
376 fn minor_unset() {
377 let mut versions = PackageVersions::default();
378 versions.bump(Rule::Minor).unwrap();
379
380 assert_eq!(versions.into_latest(), Version::new(0, 0, 1, None));
381 }
382
383 #[test]
384 fn minor_after_pre() {
385 for pre_version in ["1.2.4-rc.0", "1.3.0-rc.0"] {
386 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
387 versions.update_version(Version::from_str(pre_version).unwrap());
388 versions.bump(Rule::Minor).unwrap();
389
390 assert_eq!(versions.into_latest(), Version::new(1, 3, 0, None));
391 }
392 }
393
394 #[test]
395 fn patch() {
396 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
397 versions.bump(Rule::Patch).unwrap();
398
399 assert_eq!(versions.into_latest(), Version::new(1, 2, 4, None));
400 }
401
402 #[test]
403 fn patch_0() {
404 let mut versions = PackageVersions::from(Version::new(0, 1, 0, None));
405 versions.bump(Rule::Patch).unwrap();
406
407 assert_eq!(versions.into_latest(), Version::new(0, 1, 1, None));
408 }
409
410 #[test]
411 fn patch_unset() {
412 let mut versions = PackageVersions::default();
413 versions.bump(Rule::Patch).unwrap();
414
415 assert_eq!(versions.into_latest(), Version::new(0, 0, 1, None));
416 }
417
418 #[test]
419 fn patch_after_pre() {
420 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
421 versions.update_version(Version::from_str("1.2.4-rc.0").unwrap());
422 versions.bump(Rule::Patch).unwrap();
423
424 assert_eq!(versions.into_latest(), Version::new(1, 2, 4, None));
425 }
426
427 #[test]
428 fn pre() {
429 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
430 versions
431 .bump(Rule::Pre {
432 label: Label::from("rc"),
433 stable_rule: Stable::Minor,
434 })
435 .unwrap();
436
437 assert_eq!(
438 versions.into_latest(),
439 Version::from_str("1.3.0-rc.0").unwrap()
440 );
441 }
442
443 #[test]
444 fn pre_after_same_pre() {
445 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
446 versions.update_version(Version::from_str("1.3.0-rc.0").unwrap());
447 versions.update_version(Version::from_str("1.2.4-rc.1").unwrap());
448 versions.update_version(Version::from_str("2.0.0-rc.2").unwrap());
449 versions
450 .bump(Rule::Pre {
451 label: Label::from("rc"),
452 stable_rule: Stable::Minor,
453 })
454 .unwrap();
455
456 assert_eq!(
457 versions.into_latest(),
458 Version::from_str("1.3.0-rc.1").unwrap()
459 );
460 }
461
462 #[test]
463 fn pre_after_different_pre_version() {
464 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
465 versions.update_version(Version::from_str("1.2.4-beta.1").unwrap());
466 versions.update_version(Version::from_str("1.2.4-rc.0").unwrap());
467 versions
468 .bump(Rule::Pre {
469 label: Label::from("beta"),
470 stable_rule: Stable::Patch,
471 })
472 .unwrap();
473
474 assert_eq!(
475 versions.into_latest(),
476 Version::from_str("1.2.4-beta.2").unwrap()
477 );
478 }
479
480 #[test]
481 fn pre_after_different_pre_label() {
482 let mut versions = PackageVersions::from(Version::new(1, 2, 3, None));
483 versions.update_version(Version::from_str("1.3.0-beta.0").unwrap());
484 versions
485 .bump(Rule::Pre {
486 label: Label::from("rc"),
487 stable_rule: Stable::Minor,
488 })
489 .unwrap();
490
491 assert_eq!(
492 versions.into_latest(),
493 Version::from_str("1.3.0-rc.0").unwrap()
494 );
495 }
496
497 #[test]
498 fn release() {
499 let mut versions = PackageVersions::default();
500 versions.update_version(Version::from_str("1.2.3-rc.0").unwrap());
501 versions.update_version(Version::from_str("1.2.4-rc.1").unwrap());
502 versions.update_version(Version::from_str("2.0.0-rc.2").unwrap());
503
504 versions.bump(Rule::Release).unwrap();
505
506 assert_eq!(versions.into_latest(), Version::new(2, 0, 0, None));
507 }
508}