~comcloudway/cabin

1c4af03a0b61410a56dd6fd56ca07dea67e8ff07 — Jakob Meier 1 year, 1 month ago 9d31ed2
Added package list and initial package info support
5 files changed, 134 insertions(+), 58 deletions(-)

A src/cmds/info.rs
A src/cmds/list.rs
M src/cmds/mod.rs
M src/main.rs
M src/types.rs
A src/cmds/info.rs => src/cmds/info.rs +46 -0
@@ 0,0 1,46 @@
use std::path::PathBuf;
use crate::types::{Database, PackageName};

/// show package information for given package (if available)
pub fn info(name: PackageName, db: Option<PathBuf>, show_provides: bool, show_depends: bool) {
    let db = Database::from_file(db.unwrap_or(PathBuf::from("db.yml")))
        .expect("Unable to open database");

    {
        let pkg = db.packages.get(&name).expect("Package not found");

        println!("{}-{}-r{}", pkg.name, pkg.version, pkg.rel);
        println!("{}", pkg.description);
        println!("license: {}", pkg.license);
        println!("url: {}", pkg.url);
        println!("path: {}", pkg.path.to_str().unwrap_or(""));

        {
            print!("arch: ");
            for a in &pkg.arch {
                print!("{}", a);
            }
            println!();
        }
    }

    if show_depends {
        println!("---Dependencies---");
        if let Some(deps) = db.depends.get(&name) {
            for dep in deps {
                println!("{}", dep);
            }
        }
    }

    if show_provides {
        println!("---Provides---");
        if let Some(prv) = db.provides.get(&name) {
            for pr in prv {
                println!("{}", pr);
            }
        }

    }

}

A src/cmds/list.rs => src/cmds/list.rs +12 -0
@@ 0,0 1,12 @@
use std::path::PathBuf;
use crate::types::Database;

/// lists all packages in the repo
pub fn list(db: Option<PathBuf>) {
    let db = Database::from_file(db.unwrap_or(PathBuf::from("db.yml")))
        .expect("Unable to open database");

    for pkg in db.packages.values() {
        println!("{}-{}-r{} ({})", pkg.name, pkg.version, pkg.rel, pkg.license);
    }
}

M src/cmds/mod.rs => src/cmds/mod.rs +4 -0
@@ 1,2 1,6 @@
mod scan;
mod list;
mod info;
pub use scan::scan;
pub use list::list;
pub use info::info;

M src/main.rs => src/main.rs +10 -2
@@ 1,4 1,4 @@
use std::{path::PathBuf, collections::HashMap};
use std::path::PathBuf;
use clap::Parser;

mod cmds;


@@ 30,7 30,13 @@ enum Commands {
        /// the packagename
        package: String,
        /// package database path
        db: Option<PathBuf>
        db: Option<PathBuf>,
        /// prints the dependencies after the package info
        #[arg(short='d',long)]
        show_depends: bool,
        /// prints the packages provided by the package after the package info
        #[arg(short='p', long)]
        show_provides: bool
    },
    /// Searches the database for a package with the name
    /// or containing the name


@@ 47,6 53,8 @@ fn main() {

    match cli {
        Commands::Scan { folder, db } => cmds::scan(folder, db),
        Commands::List { db } => cmds::list(db),
        Commands::Info { package, db, show_depends, show_provides } => cmds::info(package, db, show_provides, show_depends),
        _ => panic!("Unimplemented")
    }
}

M src/types.rs => src/types.rs +62 -56
@@ 1,5 1,4 @@
use std::{path::PathBuf, collections::{HashMap, HashSet}, fs::{FileType, DirEntry, self}};

use std::{path::PathBuf, collections::{HashMap, HashSet}, fs};
use regex::Regex;
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;


@@ 12,21 11,21 @@ pub type PackageName = String;
#[derive(Serialize, Deserialize, Debug)]
pub struct Package {
    /// the name of the package
    name: String,
    pub name: String,
    /// the version of the package
    version: String,
    pub version: String,
    /// license used by the package
    license: String,
    pub license: String,
    /// project url
    url: String,
    pub url: String,
    /// package description
    description: String,
    pub description: String,
    /// package release number (starts at zero)
    rel: usize,
    pub rel: usize,
    /// list? of supported/disabled architectures
    arch: HashSet<String>,
    pub arch: HashSet<String>,
    /// relative path to APKBUILD
    path: PathBuf
    pub path: PathBuf
}

/// Package information collection


@@ 34,73 33,80 @@ pub struct Package {
pub struct Database {
    /// dictionary of package names associated with a dictionary
    /// of additional package info
    packages: HashMap<PackageName, Package>,
    pub packages: HashMap<PackageName, Package>,
    /// dictionary of package names associated with a dependency list
    /// the dependencies may not create cycles
    /// all dependencies of a given package have to be build,
    /// before the package can be build
    depends: HashMap<PackageName, HashSet<PackageName>>,
    pub depends: HashMap<PackageName, HashSet<PackageName>>,
    /// dictionary of provide names associated with a list of packages,
    /// which provide the given package
    /// in case multiple packages provide the same provider,
    /// all of them should be build
    /// (runtime apk should take care of the rest)
    provides: HashMap<PackageName, HashSet<PackageName>>
    pub provides: HashMap<PackageName, HashSet<PackageName>>
}
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(),
        };

        for entry in WalkDir::new(folder).follow_links(true) {
            // ignore broken entries
            if let Ok(entry) = entry {
                if entry.file_type().is_dir() {
                    // ignore folders themselves
                    continue;
                }
                if entry.file_type().is_file() && entry.file_name() != "APKBUILD" {
                    // ignore every file that is not the APKBUILD file itself
                    continue;
                }


                let file = entry.into_path();
                let folder = file.parent().expect("Unable to get APKBUILD dir").to_path_buf();

                let apk = APKBUILD::from_file(file);
                let depends = apk.depends;
                let mut provides = apk.provides;
                for sub in apk.subs {
                    provides.insert(sub);
                }
                let pkg = Package {
                    name: apk.name.to_string(),
                    arch: apk.arch,
                    version: apk.version,
                    license: apk.license,
                    description: apk.description,
                    url: apk.url,
                    rel: apk.rel,
                    path: folder
                };

                db.packages.insert(apk.name.to_string(), pkg);
                db.depends.insert(apk.name.to_string(), depends);
                db.provides.insert(apk.name.to_string(), provides);
 // 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
                continue;
            }
            if entry.file_type().is_file() && entry.file_name() != "APKBUILD" {
                // ignore every file that is not the APKBUILD file itself
                continue;
            }


            let file = entry.into_path();
            let folder = file.parent().expect("Unable to get APKBUILD dir").to_path_buf();

            let apk = Apkbuild::from_file(file);
            let depends = apk.depends;
            let mut provides = apk.provides;
            for sub in apk.subs {
                provides.insert(sub);
            }
            let pkg = Package {
                name: apk.name.to_string(),
                arch: apk.arch,
                version: apk.version,
                license: apk.license,
                description: apk.description,
                url: apk.url,
                rel: apk.rel,
                path: folder
            };

            db.packages.insert(apk.name.to_string(), pkg);
            db.depends.insert(apk.name.to_string(), depends);
            db.provides.insert(apk.name.to_string(), provides);
        }

        db
    }
    pub fn from_file(file: PathBuf) -> Option<Self> {
        if let Ok(content) = fs::read_to_string(file) {
            if let Ok(yml) = serde_yaml::from_str(&content) {
                return Some(yml);
            }
        }

        None
    }
}

/// unprocessed Data directly extracted from APKBUILD
#[derive(Debug)]
struct APKBUILD {
struct Apkbuild {
    /// the name of the package
    name: String,
    /// the version of the package


@@ 122,12 128,12 @@ struct APKBUILD {
    /// list of subpackages
    subs: Vec<String>
}
impl APKBUILD {
impl Apkbuild {
    fn from_file(file: PathBuf) -> Self{
        let cont = fs::read_to_string(file)
            .expect("Failed to open APKBUILD");

        let mut apk = APKBUILD {
        let mut apk = Self {
            name: easy_match("pkgname", &cont),
            description: easy_match("pkgdesc", &cont),
            rel: easy_match("pkgrel", &cont).parse()


@@ 167,7 173,7 @@ impl APKBUILD {
fn easy_match(var: &str, text: &str) -> String {
    let re = Regex::new(&format!("{}=\"[^\"]+\"|{}=[^\"\n]+\n", var,var)).unwrap();
    let extr = re.find(text)
                 .expect(&format!("Invalid APKBUILD, expected to find {}", var)).as_str();
                 .unwrap_or_else(|| panic!("Invalid APKBUILD, expected to find {}", var)).as_str();

    // tidy up the value, by removing unneccesarry characters
    // var="<want>"