libpkgx 0.7.0

Install and run `pkgx` packages
Documentation
use crate::config::Config;
use crate::types::{Installation, Package, PackageReq};
use libsemverator::semver::Semver as Version;
use std::error::Error;
use std::path::PathBuf;
use tokio::fs;

pub async fn ls(project: &str, config: &Config) -> Result<Vec<Installation>, Box<dyn Error>> {
    let d = config.pkgx_dir.join(project);

    match fs::metadata(&d).await {
        Ok(metadata) => {
            if !metadata.is_dir() {
                return Err(format!("err: expected directory: {:?}", d).into());
            }
        }
        Err(e) => {
            if e.kind() == std::io::ErrorKind::NotFound {
                return Ok(vec![]);
            } else {
                return Err(e.into());
            }
        }
    }

    let mut rv = vec![];
    let mut entries = fs::read_dir(&d).await?;
    while let Some(entry) = entries.next_entry().await? {
        let path = entry.path();
        let name = entry.file_name().to_string_lossy().to_string();

        if !name.starts_with('v') || name == "var" {
            continue;
        }
        if !fs::symlink_metadata(&path).await?.is_dir() {
            continue;
        }

        if let Ok(version) = Version::parse(&name[1..]) {
            rv.push(Installation {
                path,
                pkg: Package {
                    project: project.to_string(),
                    version,
                },
            });
        }
    }

    Ok(rv)
}

pub async fn resolve(
    pkgreq: &PackageReq,
    config: &Config,
) -> Result<Option<Installation>, Box<dyn Error>> {
    Ok(ls(&pkgreq.project, config)
        .await?
        .iter()
        .filter(|i| pkgreq.constraint.satisfies(&i.pkg.version))
        .max_by_key(|i| i.pkg.version.clone())
        .cloned())
}

pub fn dst(pkg: &Package, config: &Config) -> PathBuf {
    config
        .pkgx_dir
        .join(pkg.project.clone())
        .join(format!("v{}", pkg.version.raw))
}