From 75207f37dca783261b69968adaf218ef52ab9f4e Mon Sep 17 00:00:00 2001 From: Risu <79110363+risu729@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:48:02 +1000 Subject: [PATCH 1/5] fix: fix version prefix detection --- src/aqua/aqua_registry.rs | 5 +- src/main.rs | 1 + src/semver.rs | 98 ++++++++++++++++++++++++++++++++++++ src/toolset/outdated_info.rs | 29 +---------- 4 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 src/semver.rs diff --git a/src/aqua/aqua_registry.rs b/src/aqua/aqua_registry.rs index ac4b3ceb18..a026ca4416 100644 --- a/src/aqua/aqua_registry.rs +++ b/src/aqua/aqua_registry.rs @@ -2,6 +2,7 @@ use crate::backend::aqua; use crate::backend::aqua::{arch, os}; use crate::duration::{DAILY, WEEKLY}; use crate::git::{CloneOptions, Git}; +use crate::semver::split_version_prefix; use crate::{aqua::aqua_template, config::Settings}; use crate::{dirs, file, hashmap, http}; use expr::{Context, Program, Value}; @@ -462,8 +463,8 @@ impl AquaPackage { } fn expr_parser(&self, v: &str) -> expr::Environment<'_> { - let prefix = Regex::new(r"^[^0-9.]+").unwrap(); - let ver = versions::Versioning::new(prefix.replace(v, "")); + let (_, v) = split_version_prefix(v); + let ver = versions::Versioning::new(v); let mut env = expr::Environment::new(); env.add_function("semver", move |c| { if c.args.len() != 1 { diff --git a/src/main.rs b/src/main.rs index 75272944ce..304839af59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,6 +69,7 @@ mod redactions; mod registry; pub(crate) mod result; mod runtime_symlinks; +mod semver; mod shell; mod shims; mod shorthands; diff --git a/src/semver.rs b/src/semver.rs new file mode 100644 index 0000000000..2633166d20 --- /dev/null +++ b/src/semver.rs @@ -0,0 +1,98 @@ +use regex::Regex; + +/// splits a version number into an optional prefix and the remaining version string +pub fn split_version_prefix(version: &str) -> (String, String) { + version + .char_indices() + .find_map(|(i, c)| { + if c.is_ascii_digit() { + if i == 0 { + return Some(i); + } + // If the previous char is a delimiter or 'v', we found a split point. + let prev_char = version.chars().nth(i - 1).unwrap(); + if ['-', '_', '/', '.', 'v', 'V'].contains(&prev_char) { + return Some(i); + } + } + None + }) + .map_or_else( + || ("".into(), version.into()), + |i| { + let (prefix, version) = version.split_at(i); + (prefix.into(), version.into()) + }, + ) +} + +/// split a version number into chunks +/// given v: "1.2-3a4" return ["1", ".2", "-3", "a4"] +pub fn chunkify_version(v: &str) -> Vec { + fn chunkify(m: &Mess, sep0: &str, chunks: &mut Vec) { + for (i, chunk) in m.chunks.iter().enumerate() { + let sep = if i == 0 { sep0 } else { "." }; + chunks.push(format!("{sep}{chunk}")); + } + if let Some((next_sep, next_mess)) = &m.next { + chunkify(next_mess, next_sep.to_string().as_ref(), chunks) + } + } + + let mut chunks = vec![]; + // don't parse "latest", otherwise bump from latest to any version would have one chunk only + if v != "latest" { + if let Some(v) = Versioning::new(v) { + let m = match v { + Versioning::Ideal(sem_ver) => sem_ver.to_mess(), + Versioning::General(version) => version.to_mess(), + Versioning::Complex(mess) => mess, + }; + chunkify(&m, "", &mut chunks); + } + } + chunks +} + +#[cfg(test)] +mod tests { + use super::{chunkify_version, split_version_prefix}; + + #[test] + fn test_split_version_prefix() { + assert_eq!(split_version_prefix("latest"), ("".into(), "latest".into())); + assert_eq!(split_version_prefix("v1.2.3"), ("v".into(), "1.2.3".into())); + assert_eq!( + split_version_prefix("mountpoint-s3-v1.2.3-5_beta.5"), + ("mountpoint-s3-v".into(), "1.2.3-5_beta.5".into()) + ); + assert_eq!( + split_version_prefix("cli/1.2.3"), + ("cli/".into(), "1.2.3".into()) + ); + assert_eq!( + split_version_prefix("temurin-17.0.7+7"), + ("temurin-".into(), "17.0.7+7".into()) + ); + assert_eq!(split_version_prefix("1.2"), ("".into(), "1.2".into())); + assert_eq!( + split_version_prefix("2:1.2.1"), + ("".into(), "2:1.2.1".into()) + ); + assert_eq!( + split_version_prefix("2025-05-17"), + ("".into(), "2025-05-17".into()) + ); + } + + #[test] + fn test_chunkify_version() { + assert_eq!(chunkify_version("1.2-3a4"), vec!["1", ".2", "-3", "a4"]); + assert_eq!(chunkify_version("latest"), Vec::::new()); + assert_eq!(chunkify_version("1.0.0"), vec!["1", ".0", ".0"]); + assert_eq!( + chunkify_version("2.3.4-beta"), + vec!["2", ".3", ".4", "-beta"] + ); + } +} diff --git a/src/toolset/outdated_info.rs b/src/toolset/outdated_info.rs index eeee934eb3..e3b565d6f5 100644 --- a/src/toolset/outdated_info.rs +++ b/src/toolset/outdated_info.rs @@ -1,3 +1,4 @@ +use crate::semver::chunkify_version; use crate::toolset; use crate::toolset::{ToolRequest, ToolSource, ToolVersion}; use crate::{Result, config::Config}; @@ -198,34 +199,6 @@ fn check_semver_bump(old: &str, new: &str) -> Option { } } -/// split a version number into chunks -/// given v: "1.2-3a4" return ["1", ".2", "-3", "a4"] -fn chunkify_version(v: &str) -> Vec { - fn chunkify(m: &Mess, sep0: &str, chunks: &mut Vec) { - for (i, chunk) in m.chunks.iter().enumerate() { - let sep = if i == 0 { sep0 } else { "." }; - chunks.push(format!("{sep}{chunk}")); - } - if let Some((next_sep, next_mess)) = &m.next { - chunkify(next_mess, next_sep.to_string().as_ref(), chunks) - } - } - - let mut chunks = vec![]; - // don't parse "latest", otherwise bump from latest to any version would have one chunk only - if v != "latest" { - if let Some(v) = Versioning::new(v) { - let m = match v { - Versioning::Ideal(sem_ver) => sem_ver.to_mess(), - Versioning::General(version) => version.to_mess(), - Versioning::Complex(mess) => mess, - }; - chunkify(&m, "", &mut chunks); - } - } - chunks -} - pub fn is_outdated_version(current: &str, latest: &str) -> bool { if let (Some(c), Some(l)) = (Version::new(current), Version::new(latest)) { c.lt(&l) From ab9b88ec46539f0a8a37a8423a5ee404099bfb86 Mon Sep 17 00:00:00 2001 From: Risu <79110363+risu729@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:11:02 +1000 Subject: [PATCH 2/5] fix: fix import --- src/semver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/semver.rs b/src/semver.rs index 2633166d20..90c5f2065f 100644 --- a/src/semver.rs +++ b/src/semver.rs @@ -1,4 +1,4 @@ -use regex::Regex; +use versions::{Mess, Versioning}; /// splits a version number into an optional prefix and the remaining version string pub fn split_version_prefix(version: &str) -> (String, String) { From 08cbc96af916f1e241046a53dd6394ae0d4c2713 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 09:17:49 +0000 Subject: [PATCH 3/5] [autofix.ci] apply automated fixes --- src/aqua/aqua_registry.rs | 1 - src/toolset/outdated_info.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aqua/aqua_registry.rs b/src/aqua/aqua_registry.rs index a026ca4416..c0c68d1c4d 100644 --- a/src/aqua/aqua_registry.rs +++ b/src/aqua/aqua_registry.rs @@ -9,7 +9,6 @@ use expr::{Context, Program, Value}; use eyre::{ContextCompat, Result, eyre}; use indexmap::IndexSet; use itertools::Itertools; -use regex::Regex; use serde_derive::Deserialize; use std::collections::HashMap; use std::path::PathBuf; diff --git a/src/toolset/outdated_info.rs b/src/toolset/outdated_info.rs index e3b565d6f5..39d58701da 100644 --- a/src/toolset/outdated_info.rs +++ b/src/toolset/outdated_info.rs @@ -8,7 +8,7 @@ use std::{ sync::Arc, }; use tabled::Tabled; -use versions::{Mess, Version, Versioning}; +use versions::Version; #[derive(Debug, Serialize, Clone, Tabled)] pub struct OutdatedInfo { From f2fa3e1590ecc257193fec456713c4a15182b4d7 Mon Sep 17 00:00:00 2001 From: Risu <79110363+risu729@users.noreply.github.com> Date: Wed, 13 Aug 2025 18:31:39 +1000 Subject: [PATCH 4/5] test: match to current behaviour --- src/semver.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/semver.rs b/src/semver.rs index 90c5f2065f..0f8b537402 100644 --- a/src/semver.rs +++ b/src/semver.rs @@ -27,7 +27,7 @@ pub fn split_version_prefix(version: &str) -> (String, String) { } /// split a version number into chunks -/// given v: "1.2-3a4" return ["1", ".2", "-3", "a4"] +/// given v: "1.2-3a4" return ["1", ".2", "-3a4"] pub fn chunkify_version(v: &str) -> Vec { fn chunkify(m: &Mess, sep0: &str, chunks: &mut Vec) { for (i, chunk) in m.chunks.iter().enumerate() { @@ -87,7 +87,7 @@ mod tests { #[test] fn test_chunkify_version() { - assert_eq!(chunkify_version("1.2-3a4"), vec!["1", ".2", "-3", "a4"]); + assert_eq!(chunkify_version("1.2-3a4"), vec!["1", ".2", "-3a4"]); assert_eq!(chunkify_version("latest"), Vec::::new()); assert_eq!(chunkify_version("1.0.0"), vec!["1", ".0", ".0"]); assert_eq!( From e7bfb98f47f7b5d4f71228e1f17e984fac6abb55 Mon Sep 17 00:00:00 2001 From: Risu <79110363+risu729@users.noreply.github.com> Date: Wed, 13 Aug 2025 18:55:52 +1000 Subject: [PATCH 5/5] refactor: use split_version_prefix functions --- src/runtime_symlinks.rs | 11 +++-------- src/toolset/outdated_info.rs | 10 ++++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/runtime_symlinks.rs b/src/runtime_symlinks.rs index 6067c183b1..7c56433bf2 100644 --- a/src/runtime_symlinks.rs +++ b/src/runtime_symlinks.rs @@ -5,12 +5,12 @@ use crate::backend::Backend; use crate::config::{Alias, Config}; use crate::file::make_symlink_or_file; use crate::plugins::VERSION_REGEX; +use crate::semver::split_version_prefix; use crate::{backend, file}; use eyre::Result; use indexmap::IndexMap; use itertools::Itertools; use versions::Versioning; -use xx::regex; pub async fn rebuild(config: &Config) -> Result<()> { for backend in backend::list() { @@ -39,14 +39,9 @@ fn list_symlinks(config: &Config, backend: Arc) -> IndexMap eyre::Result> { let t = tv.backend()?; // prefix is something like "temurin-" or "corretto-" - let prefix = xx::regex!(r"^[a-zA-Z-]+-") - .find(&tv.request.version()) - .map(|m| m.as_str().to_string()); + let (prefix, _) = split_version_prefix(&tv.request.version()); let latest_result = if bump { - t.latest_version(config, prefix.clone()).await + t.latest_version(config, Some(prefix.clone()).filter(|s| !s.is_empty())) + .await } else { tv.latest_version(config).await.map(Option::from) }; @@ -85,7 +84,6 @@ impl OutdatedInfo { return Ok(None); } if bump { - let prefix = prefix.unwrap_or_default(); let old = oi.tool_version.request.version(); let old = old.strip_prefix(&prefix).unwrap_or_default(); let new = oi.latest.strip_prefix(&prefix).unwrap_or_default();