Skip to content

Commit

Permalink
feat(download): add ergonomic flags for github asset matching
Browse files Browse the repository at this point in the history
  • Loading branch information
QaidVoid committed Nov 12, 2024
1 parent e0b9a58 commit e47083d
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 46 deletions.
5 changes: 4 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ fn main() {
.expect("Failed to get git commit SHA")
.stdout;

let commit_sha = String::from_utf8(commit_sha).expect("Invalid UTF-8 output").trim().to_string();
let commit_sha = String::from_utf8(commit_sha)
.expect("Invalid UTF-8 output")
.trim()
.to_string();

println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=CARGO_PKG_VERSION=nightly-{}", commit_sha);
Expand Down
12 changes: 10 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,16 @@ pub enum Commands {
output: Option<String>,

/// Regex to select the asset. Only works for github downloads
#[arg(required = false, short, long)]
regex: Option<String>,
#[arg(required = false, short = 'r', long = "regex")]
regex_patterns: Option<Vec<String>>,

/// Check if the asset contains given string
#[arg(required = false, short, long = "match")]
match_keywords: Option<Vec<String>>,

/// Check if the asset contains given string
#[arg(required = false, short, long = "exclude")]
exclude_keywords: Option<Vec<String>>,
},

/// Health check
Expand Down
15 changes: 13 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,20 @@ async fn handle_cli() -> Result<()> {
links,
yes,
output,
regex,
regex_patterns,
match_keywords,
exclude_keywords,
} => {
download_and_save(registry.await?, links.as_ref(), yes, output, regex).await?;
download_and_save(
registry.await?,
links.as_ref(),
yes,
output,
regex_patterns.as_deref(),
match_keywords.as_deref(),
exclude_keywords.as_deref(),
)
.await?;
}
Commands::Health => {
check_health().await;
Expand Down
117 changes: 76 additions & 41 deletions src/misc/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
registry::{select_single_package, PackageRegistry},
};

#[derive(Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
struct GithubAsset {
name: String,
size: u64,
Expand Down Expand Up @@ -168,16 +168,48 @@ async fn fetch_github_releases(user_repo: &str) -> Result<Vec<GithubRelease>> {
Ok(releases)
}

fn select_asset_idx(assets: &[&GithubAsset], max: usize) -> Result<usize> {
for (i, asset) in assets.iter().enumerate() {
info!(
" [{}] {:#?} ({})",
i + 1,
asset.name,
format_bytes(asset.size),
);
}
let selection = loop {
let response = interactive_ask(
&format!("Select an asset (1-{}): ", assets.len()),
AskType::Normal,
)?;

match response.parse::<usize>() {
Ok(n) if n > 0 && n <= max => break n - 1,
_ => error!("Invalid selection, please try again."),
}
};
Ok(selection)
}

pub async fn download_and_save(
registry: PackageRegistry,
links: &[String],
yes: bool,
output: Option<String>,
regex: Option<String>,
regex_patterns: Option<&[String]>,
match_keywords: Option<&[String]>,
exclude_keywords: Option<&[String]>,
) -> Result<()> {
let re = Regex::new(GITHUB_URL_REGEX).unwrap();
let asset_re = regex.as_deref();
let asset_re = Regex::new(asset_re.unwrap_or_default())?;
let asset_regexes = regex_patterns
.map(|patterns| {
patterns
.iter()
.map(|pattern| Regex::new(pattern))
.collect::<Result<Vec<Regex>, regex::Error>>()
})
.transpose()?
.unwrap_or_default();

for link in links {
if re.is_match(link) {
Expand Down Expand Up @@ -219,47 +251,50 @@ pub async fn download_and_save(
continue;
}

let selected_file = if regex.is_some() {
let Some(asset) = assets.iter().find(|asset| asset_re.is_match(&asset.name))
else {
continue;
};
asset
} else if assets.len() == 1 || yes {
&assets[0]
} else {
info!(
"Showing assets for {}{}",
release.tag_name,
if release.prerelease {
" [prerelease]".color(Color::BrightRed)
} else {
" [stable]".color(Color::BrightCyan)
let selected_asset = {
let assets: Vec<&GithubAsset> = assets
.iter()
.filter(|asset| {
asset_regexes
.iter()
.all(|regex| regex.is_match(&asset.name))
&& match_keywords.map_or(true, |keywords| {
keywords.iter().all(|keyword| asset.name.contains(keyword))
})
&& exclude_keywords.map_or(true, |keywords| {
keywords.iter().all(|keyword| !asset.name.contains(keyword))
})
})
.collect();

match assets.len() {
0 => {
error!("No assets matched the provided criteria.");
continue;
}
);
for (i, asset) in assets.iter().enumerate() {
info!(
" [{}] {:#?} ({})",
i + 1,
asset.name,
format_bytes(asset.size),
);
}
let selection = loop {
let response = interactive_ask(
&format!("Select a file (1-{}): ", assets.len()),
AskType::Normal,
)?;

match response.parse::<usize>() {
Ok(n) if n > 0 && n <= releases.len() => break n - 1,
_ => error!("Invalid selection, please try again."),
1 => assets[0],
_ => {
if yes {
assets[0]
} else {
info!(
"Multiple matching assets found for {}{}",
release.tag_name,
if release.prerelease {
" [prerelease]".color(Color::BrightRed)
} else {
" [stable]".color(Color::BrightCyan)
}
);

let asset_idx = select_asset_idx(&assets, releases.len())?;
assets[asset_idx]
}
}
};
&assets[selection]
}
};

let download_url = &selected_file.browser_download_url;
let download_url = &selected_asset.browser_download_url;
download(download_url, output.clone()).await?;
}
} else if let Ok(url) = Url::parse(link) {
Expand Down

0 comments on commit e47083d

Please sign in to comment.