diff --git a/CHANGELOG.md b/CHANGELOG.md index 8845965..3b6aa49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## `Unreleased` +### Added + +- Allow `x.y` versioning scheme in the config file ([#86]) + ### Changed - Modified the `rokit install` command to check if Rokit is in the user's PATH after installation, and provide appropriate instructions based on the user's operating system and shell ([#92]) diff --git a/lib/discovery/foreman.rs b/lib/discovery/foreman.rs index 8f638d3..96e1056 100644 --- a/lib/discovery/foreman.rs +++ b/lib/discovery/foreman.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, str::FromStr}; use semver::Version; use toml_edit::{DocumentMut, InlineTable, Table}; -use crate::tool::{ToolAlias, ToolId, ToolSpec}; +use crate::tool::{util::to_xyz_version, ToolAlias, ToolId, ToolSpec}; use super::Manifest; @@ -71,7 +71,8 @@ fn parse_foreman_tool_definition(map: SpecType) -> Option { let version = map.get("version").and_then(|t| t.as_str()).and_then(|v| { // TODO: Support real version requirements instead of just exact/min versions let without_prefix = v.trim_start_matches('=').trim_start_matches('^'); - without_prefix.parse::().ok() + let version_str = to_xyz_version(without_prefix); + version_str.parse::().ok() })?; // TODO: Support gitlab tool ids let github_tool_id = map diff --git a/lib/sources/github/mod.rs b/lib/sources/github/mod.rs index 56e37f3..14a9e3d 100644 --- a/lib/sources/github/mod.rs +++ b/lib/sources/github/mod.rs @@ -8,7 +8,7 @@ use reqwest::{ StatusCode, }; -use crate::tool::{ToolId, ToolSpec}; +use crate::tool::{util::to_xyz_version, ToolId, ToolSpec}; use super::{client::create_client, Artifact, ArtifactProvider, Release}; @@ -144,9 +144,9 @@ impl GithubProvider { Ok(r) => r, }; - let version = release - .tag_name - .trim_start_matches('v') + let version_str = release.tag_name.trim_start_matches('v'); + let version_str_xyz = to_xyz_version(version_str); + let version = version_str_xyz .parse::() .map_err(|e| GithubError::Other(e.to_string()))?; @@ -163,36 +163,44 @@ impl GithubProvider { #[instrument(skip(self), fields(%tool_spec), level = "debug")] pub async fn get_specific_release(&self, tool_spec: &ToolSpec) -> GithubResult { debug!(spec = %tool_spec, "fetching release for tool"); - - let url_with_prefix = format!( - "{BASE_URL}/repos/{owner}/{repo}/releases/tags/v{tag}", - owner = tool_spec.author(), - repo = tool_spec.name(), - tag = tool_spec.version(), - ); - let url_without_prefix = format!( - "{BASE_URL}/repos/{owner}/{repo}/releases/tags/{tag}", - owner = tool_spec.author(), - repo = tool_spec.name(), - tag = tool_spec.version(), - ); - - let release: GithubRelease = match self.get_json(&url_with_prefix).await { - Err(e) if is_404(&e) => match self.get_json(&url_without_prefix).await { - Err(e) if is_404(&e) => { - return Err(GithubError::ReleaseNotFound(tool_spec.clone().into())); + let mut tags_to_try = vec![tool_spec.version().to_string()]; + let version = tool_spec.version(); + if version.patch == 0 && version.pre.is_empty() && version.build.is_empty() { + tags_to_try.push(format!("{}.{}", version.major, version.minor)); + } + for tag in tags_to_try { + let url_with_prefix = format!( + "{BASE_URL}/repos/{owner}/{repo}/releases/tags/v{tag}", + owner = tool_spec.author(), + repo = tool_spec.name(), + ); + let url_without_prefix = format!( + "{BASE_URL}/repos/{owner}/{repo}/releases/tags/{tag}", + owner = tool_spec.author(), + repo = tool_spec.name(), + ); + match self.get_json::(&url_with_prefix).await { + Ok(release) => { + return Ok(Release { + changelog: release.changelog.clone(), + artifacts: artifacts_from_release(&release, tool_spec), + }); } - Err(e) => return Err(e), - Ok(r) => r, - }, - Err(e) => return Err(e), - Ok(r) => r, - }; - - Ok(Release { - changelog: release.changelog.clone(), - artifacts: artifacts_from_release(&release, tool_spec), - }) + Err(e) if !is_404(&e) => return Err(e), + _ => {} + } + match self.get_json::(&url_without_prefix).await { + Ok(release) => { + return Ok(Release { + changelog: release.changelog.clone(), + artifacts: artifacts_from_release(&release, tool_spec), + }); + } + Err(e) if !is_404(&e) => return Err(e), + _ => {} + } + } + Err(GithubError::ReleaseNotFound(tool_spec.clone().into())) } /** diff --git a/lib/tool/mod.rs b/lib/tool/mod.rs index b0601fe..32eb2ee 100644 --- a/lib/tool/mod.rs +++ b/lib/tool/mod.rs @@ -1,7 +1,7 @@ mod alias; mod id; -mod spec; -mod util; +pub(crate) mod spec; +pub(crate) mod util; pub use self::alias::{ToolAlias, ToolAliasParseError}; pub use self::id::{ToolId, ToolIdParseError}; diff --git a/lib/tool/spec.rs b/lib/tool/spec.rs index a03b23c..e56ff4a 100644 --- a/lib/tool/spec.rs +++ b/lib/tool/spec.rs @@ -6,7 +6,7 @@ use thiserror::Error; use crate::sources::ArtifactProvider; -use super::{util::is_invalid_identifier, ToolId, ToolIdParseError}; +use super::{util::is_invalid_identifier, util::to_xyz_version, ToolId, ToolIdParseError}; /** Error type representing the possible errors that can occur when parsing a `ToolSpec`. @@ -97,7 +97,8 @@ impl FromStr for ToolSpec { return Err(ToolSpecParseError::InvalidVersion(after.to_string())); } - let version = match after.parse::() { + let version_str = to_xyz_version(after); + let version = match version_str.parse::() { Ok(version) => version, Err(e) => { return match after.parse::() { @@ -147,6 +148,7 @@ mod tests { // Basic strings should parse ok assert!("a/b@0.0.0".parse::().is_ok()); assert!("author/name@1.2.3".parse::().is_ok()); + assert!("author/name@6.9".parse::().is_ok()); assert!("123abc456/78de90@11.22.33".parse::().is_ok()); // The parsed ToolSpec should match the input assert_eq!( @@ -157,6 +159,10 @@ mod tests { "author/name@1.2.3".parse::().unwrap(), new_spec("author", "name", "1.2.3"), ); + assert_eq!( + "author/name@6.9".parse::().unwrap(), + new_spec("author", "name", "6.9.0"), + ); assert_eq!( "123abc456/78de90@11.22.33".parse::().unwrap(), new_spec("123abc456", "78de90", "11.22.33"), diff --git a/lib/tool/util.rs b/lib/tool/util.rs index c846851..286e9d5 100644 --- a/lib/tool/util.rs +++ b/lib/tool/util.rs @@ -1,3 +1,20 @@ +use std::borrow::Cow; + +pub(crate) fn to_xyz_version(v_str: &str) -> Cow<'_, str> { + let (version_num_part, rest) = v_str + .find(['-', '+']) + .map_or((v_str, ""), |i| v_str.split_at(i)); + + let num_dots = version_num_part.matches('.').count(); + + if num_dots == 1 { + // x.y + return Cow::Owned(format!("{version_num_part}.0{rest}")); + } + + Cow::Borrowed(v_str) +} + pub fn is_invalid_identifier(s: &str) -> bool { s.is_empty() // Must not be empty || s.chars().all(char::is_whitespace) // Must contain some information