Skip to content

Commit

Permalink
Fix bug with cargo search when finding remote version of a crate
Browse files Browse the repository at this point in the history
  • Loading branch information
syl20bnr committed Aug 28, 2024
1 parent 0a37df0 commit 1097b29
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 13 deletions.
27 changes: 14 additions & 13 deletions crates/tracel-xtask/src/commands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{env, process::Command, str};

use anyhow::{anyhow, Ok};

use crate::{endgroup, group, utils::process::run_process};
use crate::{endgroup, group, utils::{cargo::parse_cargo_search_output, process::run_process}};

// Crates.io API token
const CRATES_IO_API_TOKEN: &str = "CRATES_IO_API_TOKEN";
Expand Down Expand Up @@ -56,27 +56,28 @@ fn local_version(crate_name: &str) -> anyhow::Result<String> {
Ok(local_version.trim_end().to_string())
}

// Obtain remote crate version

// Obtain the crate version from crates.io
fn remote_version(crate_name: &str) -> anyhow::Result<Option<String>> {
// Obtain remote crate version contained in cargo search data
let cargo_search_output = Command::new("cargo")
.args(["search", crate_name, "--limit", "1"])
.output()
.map_err(|e| anyhow!("Failed to execute cargo search: {}", e))?;
// Cargo search returns an empty string in case of a crate not present on crates.io
if cargo_search_output.stdout.is_empty() {
Ok(None)
} else {
if !cargo_search_output.stdout.is_empty() {
let output_str = str::from_utf8(&cargo_search_output.stdout).unwrap();
// Convert cargo search output into a str
let remote_version_str = str::from_utf8(&cargo_search_output.stdout)
.expect("Failed to convert cargo search output into a str");

// Extract only the remote crate version from str
Ok(remote_version_str
.split_once('=')
.and_then(|(_, second)| second.trim_start().split_once(' '))
.map(|(s, _)| s.trim_matches('"').to_string()))
// as cargo search does not support exact match only we need to make sure that the
// result returned by cargo search is indeed the crate that we are looking for and not
// a crate whose name contains the name of the crate we are looking for.
if let Some((name, version)) = parse_cargo_search_output(output_str) {
if name == crate_name {
return Ok(Some(version.to_string()));
}
}
}
Ok(None)
}

fn publish(crate_name: String) -> anyhow::Result<()> {
Expand Down
32 changes: 32 additions & 0 deletions crates/tracel-xtask/src/utils/cargo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::process::Command;

use anyhow::Ok;
use regex::Regex;

use crate::{endgroup, group, utils::process::run_process};

Expand Down Expand Up @@ -47,3 +48,34 @@ pub fn is_cargo_crate_installed(crate_name: &str) -> bool {
let output_str = String::from_utf8_lossy(&output.stdout);
output_str.lines().any(|line| line.contains(crate_name))
}

pub fn parse_cargo_search_output(output: &str) -> Option<(&str, &str)> {
let re = Regex::new(r#"(?P<name>[a-zA-Z0-9_-]+)\s*=\s*"(?P<version>\d+\.\d+\.\d+)""#)
.expect("should compile regex");
if let Some(captures) = re.captures(output) {
let name = captures.name("name");
let version = captures.name("version");
if name.is_some() && version.is_some() {
return Some((name.unwrap().as_str(), version.unwrap().as_str()));
}
}
None
}

#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;

#[rstest]
#[case::valid_input("tracel-xtask-macros = \"1.0.1\"", Some(("tracel-xtask-macros", "1.0.1")))]
#[case::missing_version("tracel-xtask-macros =", None)]
#[case::invalid_format("tracel-xtask-macros: \"1.0.1\"", None)]
#[case::extra_whitespace(" tracel-xtask-macros = \"1.0.1\" ", Some(("tracel-xtask-macros", "1.0.1")))]
#[case::no_quotes("tracel-xtask-macros = 1.0.1", None)]
#[case::wrong_version_format("tracel-xtask-macros = \"1.0\"", None)]
fn test_parse_cargo_search_output(#[case] input: &str, #[case] expected: Option<(&str, &str)>) {
let result = parse_cargo_search_output(input);
assert_eq!(result, expected);
}
}

0 comments on commit 1097b29

Please sign in to comment.