Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 134 additions & 31 deletions src/semver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,32 @@ pub struct VersionMap<T> {
impl<T> VersionMap<T> {
/// Creates a new empty `VersionMap`.
pub fn new() -> Self {
Self {
versions: BTreeMap::new(),
alternates: HashMap::new(),
}
Self::default()
}

/// Returns the number of versions stored in the map.
pub fn len(&self) -> usize {
self.versions.len()
}

/// Returns `true` if the map contains no versions.
pub fn is_empty(&self) -> bool {
self.versions.is_empty()
}

/// Returns `true` if the map contains the specified version.
pub fn contains_version(&self, version: &Version) -> bool {
self.versions.contains_key(version)
}

/// Returns an iterator over all versions in the map in sorted order.
pub fn versions(&self) -> impl Iterator<Item = &Version> {
self.versions.keys()
}

/// Returns an iterator over all version-value pairs in sorted order.
pub fn iter(&self) -> impl Iterator<Item = (&Version, &T)> {
self.versions.iter()
}

/// Attempts to insert a version-value pair, returning an error if the version already exists.
Expand All @@ -60,30 +82,38 @@ impl<T> VersionMap<T> {
return Err((version, value));
}

if let Some(alternate) = version_alternate(&version) {
self.alternates
.entry(alternate)
.or_default()
.insert(version.clone());
}

self.versions.insert(version, value);

self.insert_internal(version, value);
Ok(())
}

/// Inserts a version-value pair, returning the previous value if the version existed.
///
/// Updates the alternates mapping appropriately.
pub fn insert(&mut self, version: Version, value: T) -> Option<T> {
if let Some(alternate) = version_alternate(&version) {
let old_value = self.versions.insert(version.clone(), value);

// Only update alternates if this is a new version
if old_value.is_none() {
self.update_alternates(&version);
}

old_value
}

/// Internal helper for inserting without checking for existing keys.
fn insert_internal(&mut self, version: Version, value: T) {
self.update_alternates(&version);
self.versions.insert(version, value);
}

/// Updates the alternates mapping for a given version.
fn update_alternates(&mut self, version: &Version) {
if let Some(alternate) = version_alternate(version) {
self.alternates
.entry(alternate)
.or_default()
.insert(version.clone());
}

self.versions.insert(version, value)
}

/// Gets a value by version, using alternate lookup if exact match is not found.
Expand All @@ -107,19 +137,23 @@ impl<T> VersionMap<T> {
/// // Get latest major
/// assert_eq!(map.get(&Version::new(1, 0, 0)), Some(&"v1.2.1"));
pub fn get(&self, version: &Version) -> Option<&T> {
// Try exact match first
if let Some(value) = self.get_exact(version) {
return Some(value);
}

// Only attempt alternate lookup for versions without build metadata
if version.build.is_empty() {
let maybe_value = version_alternate(version)
.as_ref()
.and_then(|alternate| self.alternates.get(alternate))
.and_then(|version_set| version_set.last())
.and_then(|version| self.versions.get(version));

if maybe_value.is_some() {
return maybe_value;
if let Some(alternate) = version_alternate(version) {
if let Some(version_set) = self.alternates.get(&alternate) {
if let Some(latest_version) = version_set.last() {
return self.versions.get(latest_version);
}
}
}
}

self.get_exact(version)
None
}

/// Gets a value by version or returns the latest version if no specific version is provided.
Expand Down Expand Up @@ -167,17 +201,41 @@ impl<T> VersionMap<T> {
self.versions.get(version)
}

/// Gets a mutable reference to a value by exact version match.
pub fn get_mut(&mut self, version: &Version) -> Option<&mut T> {
self.versions.get_mut(version)
}

/// Removes a version and its associated value from the map.
///
/// Returns the removed value if the version existed.
pub fn remove(&mut self, version: &Version) -> Option<T> {
if let Some(alternate) = version_alternate(version) {
if let Some(set) = self.alternates.get_mut(&alternate) {
set.remove(version);
if set.is_empty() {
self.alternates.remove(&alternate);
let result = self.versions.remove(version);

if result.is_some() {
// Clean up alternates mapping
if let Some(alternate) = version_alternate(version) {
if let Some(set) = self.alternates.get_mut(&alternate) {
set.remove(version);
if set.is_empty() {
self.alternates.remove(&alternate);
}
}
}
}

self.versions.remove(version)
result
}

/// Clears all versions from the map.
pub fn clear(&mut self) {
self.versions.clear();
self.alternates.clear();
}

/// Returns the first (lowest) version and its value.
pub fn get_first(&self) -> Option<(&Version, &T)> {
self.versions.first_key_value()
}
}

Expand Down Expand Up @@ -210,6 +268,9 @@ mod tests {
fn test_version_map_basic_operations() {
let mut map = VersionMap::new();

assert!(map.is_empty());
assert_eq!(map.len(), 0);

let version0 = Version::new(0, 4, 2);
let version1 = Version::new(1, 0, 0);
let version2 = Version::new(1, 0, 1);
Expand All @@ -221,8 +282,15 @@ mod tests {
assert!(map.try_insert(version2.clone(), "value2").is_ok());
assert!(map.try_insert(version3.clone(), "value3").is_ok());

assert_eq!(map.len(), 4);
assert!(!map.is_empty());

// Test duplicate insertion
assert!(map.try_insert(version1.clone(), "duplicate").is_err());

// Test contains
assert!(map.contains_version(&version1));
assert!(!map.contains_version(&Version::new(5, 0, 0)));
}

#[test]
Expand Down Expand Up @@ -271,6 +339,7 @@ mod tests {
map.insert(Version::new(0, 1, 0), "v0.1.0");

assert_eq!(map.get_latest(), Some((&Version::new(2, 0, 0), &"v2.0.0")));
assert_eq!(map.get_first(), Some((&Version::new(0, 1, 0), &"v0.1.0")));
assert_eq!(map.get_or_latest(None), Some(&"v2.0.0"));
assert_eq!(
map.get_or_latest(Some(&Version::new(1, 0, 0))),
Expand All @@ -288,8 +357,13 @@ mod tests {
map.insert(v1.clone(), "v1");
map.insert(v2.clone(), "v2");

assert_eq!(map.len(), 2);
assert_eq!(map.remove(&v1), Some("v1"));
assert_eq!(map.len(), 1);
assert_eq!(map.remove(&v1), None); // Already removed

map.clear();
assert!(map.is_empty());
}

#[test]
Expand Down Expand Up @@ -328,4 +402,33 @@ mod tests {
Some(Version::new(0, 0, 5))
);
}

#[test]
fn test_version_map_iterators() {
let mut map = VersionMap::new();

map.insert(Version::new(2, 0, 0), "v2");
map.insert(Version::new(1, 0, 0), "v1");
map.insert(Version::new(3, 0, 0), "v3");

let versions: Vec<_> = map.versions().cloned().collect();
assert_eq!(
versions,
vec![
Version::new(1, 0, 0),
Version::new(2, 0, 0),
Version::new(3, 0, 0),
]
);

let pairs: Vec<_> = map.iter().map(|(v, val)| (v.clone(), *val)).collect();
assert_eq!(
pairs,
vec![
(Version::new(1, 0, 0), "v1"),
(Version::new(2, 0, 0), "v2"),
(Version::new(3, 0, 0), "v3"),
]
);
}
}
Loading