fix: PKGBUILD shell variable resolver, TUI auto-fetch before build, AurClient Result fix
- resolve_shell_vars(): handles ${var}, $var, ${var%suffix} patterns
- nushell-git ($_pkgname-git) now resolves correctly to nushell-git
- TUI start_build_selected() auto-fetches AUR PKGBUILD if not cached
- Fix AurClient::new() Result handling in app.rs
- 70 tests pass (1 new: resolves_shell_variables)
This commit is contained in:
@@ -30,20 +30,34 @@ pub struct AurSrcInfo {
|
||||
pub fn convert_pkgbuild(content: &str) -> Result<ConversionResult, CubError> {
|
||||
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::<u32>().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<String> {
|
||||
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<char> = 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<String> {
|
||||
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<String> {
|
||||
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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user