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> {
|
pub fn convert_pkgbuild(content: &str) -> Result<ConversionResult, CubError> {
|
||||||
let pkgnames = extract_array_assignment(content, "pkgname").unwrap_or_default();
|
let pkgnames = extract_array_assignment(content, "pkgname").unwrap_or_default();
|
||||||
let split_packages = extract_split_packages(content);
|
let split_packages = extract_split_packages(content);
|
||||||
let pkgname = pkgnames
|
let pkgname = resolve_shell_vars(
|
||||||
.first()
|
&pkgnames
|
||||||
.cloned()
|
.first()
|
||||||
.or_else(|| split_packages.first().cloned())
|
.cloned()
|
||||||
.or_else(|| extract_scalar_assignment(content, "pkgbase"))
|
.or_else(|| split_packages.first().cloned())
|
||||||
.ok_or_else(|| CubError::Conversion("missing pkgname in PKGBUILD".to_string()))?;
|
.or_else(|| extract_scalar_assignment(content, "pkgbase"))
|
||||||
let pkgver = extract_scalar_assignment(content, "pkgver")
|
.ok_or_else(|| CubError::Conversion("missing pkgname in PKGBUILD".to_string()))?,
|
||||||
.ok_or_else(|| CubError::Conversion("missing pkgver 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 =
|
let pkgrel_raw = resolve_shell_vars(
|
||||||
extract_scalar_assignment(content, "pkgrel").unwrap_or_else(|| "1".to_string());
|
&extract_scalar_assignment(content, "pkgrel").unwrap_or_else(|| "1".to_string()),
|
||||||
|
content,
|
||||||
|
);
|
||||||
let pkgrel = pkgrel_raw.parse::<u32>().unwrap_or(1);
|
let pkgrel = pkgrel_raw.parse::<u32>().unwrap_or(1);
|
||||||
let pkgdesc = extract_scalar_assignment(content, "pkgdesc").unwrap_or_default();
|
let pkgdesc = resolve_shell_vars(
|
||||||
let url = extract_scalar_assignment(content, "url").unwrap_or_default();
|
&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 licenses = extract_array_assignment(content, "license").unwrap_or_default();
|
||||||
let depends = extract_array_assignment(content, "depends").unwrap_or_default();
|
let depends = extract_array_assignment(content, "depends").unwrap_or_default();
|
||||||
let makedepends = extract_array_assignment(content, "makedepends").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
|
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> {
|
pub fn detect_linuxisms(content: &str) -> Vec<String> {
|
||||||
let lowered = content.to_ascii_lowercase();
|
let lowered = content.to_ascii_lowercase();
|
||||||
let checks = [
|
let checks = [
|
||||||
@@ -697,6 +777,13 @@ package() {
|
|||||||
assert_eq!(parsed, vec!["glibc", "zlib"]);
|
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]
|
#[test]
|
||||||
fn detects_meson_template() {
|
fn detects_meson_template() {
|
||||||
let input = "pkgname=demo\npkgver=1\nmeson setup build\n";
|
let input = "pkgname=demo\npkgver=1\nmeson setup build\n";
|
||||||
|
|||||||
@@ -92,9 +92,11 @@ impl CubApp {
|
|||||||
});
|
});
|
||||||
let _ = store.init();
|
let _ = store.init();
|
||||||
|
|
||||||
let aur_client = Some(AurClient::new()).filter(|_| {
|
let aur_client = if env::var("AUR_OFFLINE").is_err() {
|
||||||
env::var("AUR_OFFLINE").is_err()
|
Some(AurClient::new())
|
||||||
});
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let mut app = Self {
|
let mut app = Self {
|
||||||
search_query: String::new(),
|
search_query: String::new(),
|
||||||
@@ -341,9 +343,31 @@ impl CubApp {
|
|||||||
let build_target = if self.current_view == View::Query {
|
let build_target = if self.current_view == View::Query {
|
||||||
self.selected_query_recipe_dir()
|
self.selected_query_recipe_dir()
|
||||||
} else {
|
} else {
|
||||||
self.selected_package()
|
self.selected_package().map(|package| {
|
||||||
.map(|package| self.store.recipes_dir().join(&package.name))
|
let dir = self.store.recipes_dir().join(&package.name);
|
||||||
.filter(|path| path.is_dir())
|
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 {
|
let Some(recipe_dir) = build_target else {
|
||||||
|
|||||||
Reference in New Issue
Block a user