Skip to content

Commit 5a04fe9

Browse files
feat(lib): allow x.y versioning scheme for tools (#95)
1 parent 5cd84c9 commit 5a04fe9

6 files changed

Lines changed: 75 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
## `Unreleased`
1212

13+
### Added
14+
15+
- Allow `x.y` versioning scheme in the config file ([#86])
16+
1317
### Changed
1418

1519
- 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])

lib/discovery/foreman.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{collections::HashMap, str::FromStr};
33
use semver::Version;
44
use toml_edit::{DocumentMut, InlineTable, Table};
55

6-
use crate::tool::{ToolAlias, ToolId, ToolSpec};
6+
use crate::tool::{util::to_xyz_version, ToolAlias, ToolId, ToolSpec};
77

88
use super::Manifest;
99

@@ -71,7 +71,8 @@ fn parse_foreman_tool_definition(map: SpecType) -> Option<ToolSpec> {
7171
let version = map.get("version").and_then(|t| t.as_str()).and_then(|v| {
7272
// TODO: Support real version requirements instead of just exact/min versions
7373
let without_prefix = v.trim_start_matches('=').trim_start_matches('^');
74-
without_prefix.parse::<Version>().ok()
74+
let version_str = to_xyz_version(without_prefix);
75+
version_str.parse::<Version>().ok()
7576
})?;
7677
// TODO: Support gitlab tool ids
7778
let github_tool_id = map

lib/sources/github/mod.rs

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use reqwest::{
88
StatusCode,
99
};
1010

11-
use crate::tool::{ToolId, ToolSpec};
11+
use crate::tool::{util::to_xyz_version, ToolId, ToolSpec};
1212

1313
use super::{client::create_client, Artifact, ArtifactProvider, Release};
1414

@@ -144,9 +144,9 @@ impl GithubProvider {
144144
Ok(r) => r,
145145
};
146146

147-
let version = release
148-
.tag_name
149-
.trim_start_matches('v')
147+
let version_str = release.tag_name.trim_start_matches('v');
148+
let version_str_xyz = to_xyz_version(version_str);
149+
let version = version_str_xyz
150150
.parse::<Version>()
151151
.map_err(|e| GithubError::Other(e.to_string()))?;
152152

@@ -163,36 +163,44 @@ impl GithubProvider {
163163
#[instrument(skip(self), fields(%tool_spec), level = "debug")]
164164
pub async fn get_specific_release(&self, tool_spec: &ToolSpec) -> GithubResult<Release> {
165165
debug!(spec = %tool_spec, "fetching release for tool");
166-
167-
let url_with_prefix = format!(
168-
"{BASE_URL}/repos/{owner}/{repo}/releases/tags/v{tag}",
169-
owner = tool_spec.author(),
170-
repo = tool_spec.name(),
171-
tag = tool_spec.version(),
172-
);
173-
let url_without_prefix = format!(
174-
"{BASE_URL}/repos/{owner}/{repo}/releases/tags/{tag}",
175-
owner = tool_spec.author(),
176-
repo = tool_spec.name(),
177-
tag = tool_spec.version(),
178-
);
179-
180-
let release: GithubRelease = match self.get_json(&url_with_prefix).await {
181-
Err(e) if is_404(&e) => match self.get_json(&url_without_prefix).await {
182-
Err(e) if is_404(&e) => {
183-
return Err(GithubError::ReleaseNotFound(tool_spec.clone().into()));
166+
let mut tags_to_try = vec![tool_spec.version().to_string()];
167+
let version = tool_spec.version();
168+
if version.patch == 0 && version.pre.is_empty() && version.build.is_empty() {
169+
tags_to_try.push(format!("{}.{}", version.major, version.minor));
170+
}
171+
for tag in tags_to_try {
172+
let url_with_prefix = format!(
173+
"{BASE_URL}/repos/{owner}/{repo}/releases/tags/v{tag}",
174+
owner = tool_spec.author(),
175+
repo = tool_spec.name(),
176+
);
177+
let url_without_prefix = format!(
178+
"{BASE_URL}/repos/{owner}/{repo}/releases/tags/{tag}",
179+
owner = tool_spec.author(),
180+
repo = tool_spec.name(),
181+
);
182+
match self.get_json::<GithubRelease>(&url_with_prefix).await {
183+
Ok(release) => {
184+
return Ok(Release {
185+
changelog: release.changelog.clone(),
186+
artifacts: artifacts_from_release(&release, tool_spec),
187+
});
184188
}
185-
Err(e) => return Err(e),
186-
Ok(r) => r,
187-
},
188-
Err(e) => return Err(e),
189-
Ok(r) => r,
190-
};
191-
192-
Ok(Release {
193-
changelog: release.changelog.clone(),
194-
artifacts: artifacts_from_release(&release, tool_spec),
195-
})
189+
Err(e) if !is_404(&e) => return Err(e),
190+
_ => {}
191+
}
192+
match self.get_json::<GithubRelease>(&url_without_prefix).await {
193+
Ok(release) => {
194+
return Ok(Release {
195+
changelog: release.changelog.clone(),
196+
artifacts: artifacts_from_release(&release, tool_spec),
197+
});
198+
}
199+
Err(e) if !is_404(&e) => return Err(e),
200+
_ => {}
201+
}
202+
}
203+
Err(GithubError::ReleaseNotFound(tool_spec.clone().into()))
196204
}
197205

198206
/**

lib/tool/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod alias;
22
mod id;
3-
mod spec;
4-
mod util;
3+
pub(crate) mod spec;
4+
pub(crate) mod util;
55

66
pub use self::alias::{ToolAlias, ToolAliasParseError};
77
pub use self::id::{ToolId, ToolIdParseError};

lib/tool/spec.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use thiserror::Error;
66

77
use crate::sources::ArtifactProvider;
88

9-
use super::{util::is_invalid_identifier, ToolId, ToolIdParseError};
9+
use super::{util::is_invalid_identifier, util::to_xyz_version, ToolId, ToolIdParseError};
1010

1111
/**
1212
Error type representing the possible errors that can occur when parsing a `ToolSpec`.
@@ -97,7 +97,8 @@ impl FromStr for ToolSpec {
9797
return Err(ToolSpecParseError::InvalidVersion(after.to_string()));
9898
}
9999

100-
let version = match after.parse::<Version>() {
100+
let version_str = to_xyz_version(after);
101+
let version = match version_str.parse::<Version>() {
101102
Ok(version) => version,
102103
Err(e) => {
103104
return match after.parse::<VersionReq>() {
@@ -147,6 +148,7 @@ mod tests {
147148
// Basic strings should parse ok
148149
assert!("a/b@0.0.0".parse::<ToolSpec>().is_ok());
149150
assert!("author/name@1.2.3".parse::<ToolSpec>().is_ok());
151+
assert!("author/name@6.9".parse::<ToolSpec>().is_ok());
150152
assert!("123abc456/78de90@11.22.33".parse::<ToolSpec>().is_ok());
151153
// The parsed ToolSpec should match the input
152154
assert_eq!(
@@ -157,6 +159,10 @@ mod tests {
157159
"author/name@1.2.3".parse::<ToolSpec>().unwrap(),
158160
new_spec("author", "name", "1.2.3"),
159161
);
162+
assert_eq!(
163+
"author/name@6.9".parse::<ToolSpec>().unwrap(),
164+
new_spec("author", "name", "6.9.0"),
165+
);
160166
assert_eq!(
161167
"123abc456/78de90@11.22.33".parse::<ToolSpec>().unwrap(),
162168
new_spec("123abc456", "78de90", "11.22.33"),

lib/tool/util.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
use std::borrow::Cow;
2+
3+
pub(crate) fn to_xyz_version(v_str: &str) -> Cow<'_, str> {
4+
let (version_num_part, rest) = v_str
5+
.find(['-', '+'])
6+
.map_or((v_str, ""), |i| v_str.split_at(i));
7+
8+
let num_dots = version_num_part.matches('.').count();
9+
10+
if num_dots == 1 {
11+
// x.y
12+
return Cow::Owned(format!("{version_num_part}.0{rest}"));
13+
}
14+
15+
Cow::Borrowed(v_str)
16+
}
17+
118
pub fn is_invalid_identifier(s: &str) -> bool {
219
s.is_empty() // Must not be empty
320
|| s.chars().all(char::is_whitespace) // Must contain some information

0 commit comments

Comments
 (0)