From 1cf8c3564da5ccef2273df69285958609cc72ce3 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Sat, 12 Aug 2023 13:34:25 +0200 Subject: [PATCH] Replaced RegEx APKBUILD parser with a simple shell script --- src/cmds/scan.rs | 2 + src/types.rs | 112 +++++++++++++++++++++++++++++++---------------- 2 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/cmds/scan.rs b/src/cmds/scan.rs index b9c3293..7036ef9 100644 --- a/src/cmds/scan.rs +++ b/src/cmds/scan.rs @@ -13,5 +13,7 @@ pub fn scan(folder: Option, db: Option) { let target = db.unwrap_or(PathBuf::from("db.yml")); if fs::write(target, text).is_err() { println!("Unable to write database to disk"); + } else { + println!("Done! {} packages found", data.packages.len()); } } diff --git a/src/types.rs b/src/types.rs index b8fa6b7..e0355ed 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, collections::{HashMap, HashSet}, fs}; +use std::{path::PathBuf, collections::{HashMap, HashSet}, fs, process::Command}; use regex::Regex; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; @@ -51,12 +51,12 @@ pub struct Database { impl Database { pub fn from_scan(folder: PathBuf) -> Self { let mut db = Self { - packages: HashMap::new(), - depends: HashMap::new(), - provides: HashMap::new(), + packages: HashMap::new(), + depends: HashMap::new(), + provides: HashMap::new(), }; - // loop through all folders, but ignore broken entries + // loop through all folders, but ignore broken entries for entry in WalkDir::new(folder).follow_links(true).into_iter().flatten(){ if entry.file_type().is_dir() { // ignore folders themselves @@ -69,6 +69,7 @@ impl Database { let file = entry.into_path(); + println!("Scanning {}", file.to_str().unwrap()); let folder = file.parent().expect("Unable to get APKBUILD dir").to_path_buf(); let apk = Apkbuild::from_file(file); @@ -142,43 +143,80 @@ struct Apkbuild { } impl Apkbuild { fn from_file(file: PathBuf) -> Self{ - let cont = fs::read_to_string(file) - .expect("Failed to open APKBUILD"); - - let mut apk = Self { - name: easy_match("pkgname", &cont), - description: easy_match("pkgdesc", &cont), - rel: easy_match("pkgrel", &cont).parse() - .expect("pkgrel should be a number"), - arch: HashSet::new(), - version: easy_match("pkgver", &cont), - license: easy_match("license", &cont), - url: easy_match("url", &cont), - depends: HashSet::new(), - provides: HashSet::new(), - subs: Vec::new() - }; + let template = r#"pkgname=\"$pkgname\" pkgdesc=\"$pkgdesc\" pkgrel=\"$pkgrel\" pkgver=\"$pkgver\" url=\"$url\" license=\"$license\" arch=\"$arch\" depends=\"$depends $makedepends $checkdepends\" subpackages=\"$subpackages\" provides=\"$provides\""#; + let child = Command::new("sh") + .arg("-c") + .arg(format!("source {} && echo -e {}", file.to_str().unwrap(), template)) + .output() + .expect("Unable to spawn APKBUILD parser"); + if !child.status.success() { + // handle error + // NOTE: maybe fall back to regex parsing? + panic!("Failed to parse APKBUILD"); + } else { + // parse the output & extract values + let cont = String::from_utf8(child.stdout) + .expect("Unable to parse otuput"); + + let mut apk = Self { + name: easy_match("pkgname", &cont), + description: easy_match("pkgdesc", &cont), + rel: easy_match_digit("pkgrel", &cont).parse() + .expect("pkgrel should be a number"), + arch: HashSet::new(), + version: easy_match("pkgver", &cont), + license: easy_match("license", &cont), + url: easy_match("url", &cont), + depends: HashSet::new(), + provides: HashSet::new(), + subs: Vec::new() + }; - for dep in easy_match_multi("depends", &cont) { - apk.depends.insert(dep); - } - for provider in easy_match_multi("provides", &cont) { - apk.provides.insert(provider); - } - for arch in easy_match_multi("arch", &cont) { - apk.arch.insert(arch); - } - for subpkg in easy_match_multi("subpackages", &cont) { - let subpkg = subpkg - .replace("$pkgname", "${pkgname}") - .replace("${pkgname}", &apk.name); - apk.subs.push(subpkg); - } - apk + for dep in easy_match_multi("depends", &cont) { + let dep = dep.split('<').map(|s|s.to_string()).collect::>().first().unwrap().to_string(); + let dep = dep.split('>').map(|s|s.to_string()).collect::>().first().unwrap().to_string(); + let dep = dep.split('=').map(|s|s.to_string()).collect::>().first().unwrap().to_string(); + + apk.depends.insert(dep); + } + for provider in easy_match_multi("provides", &cont) { + apk.provides.insert(provider); + } + for arch in easy_match_multi("arch", &cont) { + apk.arch.insert(arch); + } + for subpkg in easy_match_multi("subpackages", &cont) { + apk.subs.push(subpkg); + } + + apk + } } } +/// extracts the first occurence of the given variable from the text +/// automatically removes whitespaces and quotes +/// panics if the variable is not defined +/// uses \d +fn easy_match_digit(var: &str, text: &str) -> String { + let re = Regex::new(&format!("{}=\"\\d+\"|{}=\\d+", var,var)).unwrap(); + let extr = re.find(text) + .unwrap_or_else(|| panic!("Invalid APKBUILD, expected to find {}", var)).as_str(); + + // tidy up the value, by removing unneccesarry characters + // var="" + let value = extr.split_at(var.len()) + .1 + .trim() + .trim_start_matches('=') + .trim_start_matches('"') + .trim_end_matches('"') + .trim(); + + String::from(value) +} + /// extracts the first occurence of the given variable from the text /// automatically removes whitespaces and quotes /// panics if the variable is not defined -- 2.38.5