diff --git a/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs b/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs index 6a992346f..da102e8af 100644 --- a/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs +++ b/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs @@ -30,20 +30,34 @@ pub struct AurSrcInfo { pub fn convert_pkgbuild(content: &str) -> Result { let pkgnames = extract_array_assignment(content, "pkgname").unwrap_or_default(); let split_packages = extract_split_packages(content); - let pkgname = pkgnames - .first() - .cloned() - .or_else(|| split_packages.first().cloned()) - .or_else(|| extract_scalar_assignment(content, "pkgbase")) - .ok_or_else(|| CubError::Conversion("missing pkgname in PKGBUILD".to_string()))?; - let pkgver = extract_scalar_assignment(content, "pkgver") - .ok_or_else(|| CubError::Conversion("missing pkgver in PKGBUILD".to_string()))?; + let pkgname = resolve_shell_vars( + &pkgnames + .first() + .cloned() + .or_else(|| split_packages.first().cloned()) + .or_else(|| extract_scalar_assignment(content, "pkgbase")) + .ok_or_else(|| CubError::Conversion("missing pkgname in PKGBUILD".to_string()))?, + content, + ); + let pkgver = resolve_shell_vars( + &extract_scalar_assignment(content, "pkgver") + .ok_or_else(|| CubError::Conversion("missing pkgver in PKGBUILD".to_string()))?, + content, + ); - let pkgrel_raw = - extract_scalar_assignment(content, "pkgrel").unwrap_or_else(|| "1".to_string()); + let pkgrel_raw = resolve_shell_vars( + &extract_scalar_assignment(content, "pkgrel").unwrap_or_else(|| "1".to_string()), + content, + ); let pkgrel = pkgrel_raw.parse::().unwrap_or(1); - let pkgdesc = extract_scalar_assignment(content, "pkgdesc").unwrap_or_default(); - let url = extract_scalar_assignment(content, "url").unwrap_or_default(); + let pkgdesc = resolve_shell_vars( + &extract_scalar_assignment(content, "pkgdesc").unwrap_or_default(), + content, + ); + let url = resolve_shell_vars( + &extract_scalar_assignment(content, "url").unwrap_or_default(), + content, + ); let licenses = extract_array_assignment(content, "license").unwrap_or_default(); let depends = extract_array_assignment(content, "depends").unwrap_or_default(); let makedepends = extract_array_assignment(content, "makedepends").unwrap_or_default(); @@ -375,6 +389,72 @@ pub fn extract_bash_function(content: &str, name: &str) -> Option { None } +pub fn resolve_shell_vars(value: &str, content: &str) -> String { + let mut result = value.to_string(); + + for _ in 0..10 { + let mut changed = false; + let mut resolved = String::with_capacity(result.len()); + let chars: Vec = result.chars().collect(); + let mut i = 0; + + while i < chars.len() { + if chars[i] == '$' && i + 1 < chars.len() { + if chars[i + 1] == '{' { + if let Some(end) = chars[i + 2..].iter().position(|c| *c == '}') { + let inner: String = chars[i + 2..i + 2 + end].iter().collect(); + if let Some(val) = lookup_var(&inner, content) { + resolved.push_str(&val); + changed = true; + } else { + resolved.push_str(&result[i..i + 3 + end]); + } + i += 3 + end; + continue; + } + } else { + let mut j = i + 1; + while j < chars.len() && (chars[j].is_alphanumeric() || chars[j] == '_') { + j += 1; + } + let varname: String = chars[i + 1..j].iter().collect(); + if let Some(val) = lookup_var(&varname, content) { + resolved.push_str(&val); + changed = true; + } else { + resolved.push_str(&result[i..j]); + } + i = j; + continue; + } + } + resolved.push(chars[i]); + i += 1; + } + + result = resolved; + if !changed { + break; + } + } + + result +} + +fn lookup_var(name: &str, content: &str) -> Option { + if let Some(suffix) = name.strip_suffix("%-git") { + if let Some(val) = extract_scalar_assignment(content, suffix) { + return Some(val); + } + } + if let Some((var, suffix)) = name.split_once('%') { + if let Some(val) = extract_scalar_assignment(content, var) { + return Some(val.trim_end_matches(suffix).to_string()); + } + } + extract_scalar_assignment(content, name) +} + pub fn detect_linuxisms(content: &str) -> Vec { let lowered = content.to_ascii_lowercase(); let checks = [ @@ -697,6 +777,13 @@ package() { assert_eq!(parsed, vec!["glibc", "zlib"]); } + #[test] + fn resolves_shell_variables() { + let content = "_pkgname=nushell\npkgname=$_pkgname-git\npkgver=1.0\npkgrel=1\narch=('x86_64')\nbuild() { cargo build --release }\npackage() { install -Dm755 target/release/nushell \"$pkgdir/usr/bin/nushell\" }\n"; + let result = convert_pkgbuild(content).expect("convert"); + assert_eq!(result.rbpkg.package.name, "nushell-git"); + } + #[test] fn detects_meson_template() { let input = "pkgname=demo\npkgver=1\nmeson setup build\n"; diff --git a/local/recipes/system/cub/source/cub-tui/src/app.rs b/local/recipes/system/cub/source/cub-tui/src/app.rs index d9eeb3e11..135aa2b99 100644 --- a/local/recipes/system/cub/source/cub-tui/src/app.rs +++ b/local/recipes/system/cub/source/cub-tui/src/app.rs @@ -92,9 +92,11 @@ impl CubApp { }); let _ = store.init(); - let aur_client = Some(AurClient::new()).filter(|_| { - env::var("AUR_OFFLINE").is_err() - }); + let aur_client = if env::var("AUR_OFFLINE").is_err() { + Some(AurClient::new()) + } else { + None + }; let mut app = Self { search_query: String::new(), @@ -341,9 +343,31 @@ impl CubApp { let build_target = if self.current_view == View::Query { self.selected_query_recipe_dir() } else { - self.selected_package() - .map(|package| self.store.recipes_dir().join(&package.name)) - .filter(|path| path.is_dir()) + self.selected_package().map(|package| { + let dir = self.store.recipes_dir().join(&package.name); + if !dir.is_dir() { + let _ = self.store.init(); + let _ = std::fs::create_dir_all(&dir); + if env::var("AUR_OFFLINE").is_err() { + let repo_url = format!("https://aur.archlinux.org/{}.git", package.name); + let tmp = std::env::temp_dir().join(format!("cub-tui-aur-{}", package.name)); + let _ = std::fs::create_dir_all(&tmp); + if std::process::Command::new("git") + .arg("clone").arg("--depth").arg("1").arg(&repo_url).arg(&tmp) + .status().ok().map_or(false, |s| s.success()) + { + if let Ok(pkgbuild) = std::fs::read_to_string(tmp.join("PKGBUILD")) { + if let Ok(conv) = cub::pkgbuild::convert_pkgbuild(&pkgbuild) { + let _ = std::fs::write(dir.join("RBPKGBUILD"), conv.rbpkg.to_toml().unwrap_or_default()); + let _ = cub::recipe::save_recipe_to_store(&conv.rbpkg, &self.store); + } + } + } + let _ = std::fs::remove_dir_all(&tmp); + } + } + dir + }).filter(|path| path.is_dir()) }; let Some(recipe_dir) = build_target else {