~comcloudway/cabin

1cf8c3564da5ccef2273df69285958609cc72ce3 — Jakob Meier 1 year, 1 month ago a8fb084
Replaced RegEx APKBUILD parser with a simple shell script
2 files changed, 77 insertions(+), 37 deletions(-)

M src/cmds/scan.rs
M src/types.rs
M src/cmds/scan.rs => src/cmds/scan.rs +2 -0
@@ 13,5 13,7 @@ pub fn scan(folder: Option<PathBuf>, db: Option<PathBuf>) {
    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());
    }
}

M src/types.rs => src/types.rs +75 -37
@@ 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,46 143,83 @@ 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::<Vec<String>>().first().unwrap().to_string();
                let dep = dep.split('>').map(|s|s.to_string()).collect::<Vec<String>>().first().unwrap().to_string();
                let dep = dep.split('=').map(|s|s.to_string()).collect::<Vec<String>>().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="<want>"
    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
fn easy_match(var: &str, text: &str) -> String {
    let re = Regex::new(&format!("{}=\"[^\"]+\"|{}=[^\"\n]+\n", var,var)).unwrap();
    let extr = re.find(text)