mercur 0.1.5

mercur - junolang package manager
use std::path::Path;

use anyhow::anyhow;
use clap::Parser;

use crate::registry::{SearchVersion, SemVersion};
mod args;
mod registry;
mod build;

#[tokio::main]
async fn main() {
    let args = args::App::parse();
    let binding = args.global_opts.directory.unwrap_or(".".to_string());
    let path = Path::new(&binding);
    if !path.is_dir() {
        panic!("--directory must be a directory: {}", path.to_string_lossy())
    }
    match args.command {
        args::Command::Registry(a) => match a.command {
            args::RegistrySubbommand::Compile {} => {
                let _ = registry::proj2fb(path, path).await;
            }
            args::RegistrySubbommand::Sync {} => {
                todo!("sync command");
            }
        },
        args::Command::Add(a) => {
            for i in a.pkgs {
                let pkg_query_parsed = parse_pkg(&i);
                let mut search_engine = registry::SearchEngine::new(5);
                let result = match search_engine
                    .search_package(
                        pkg_query_parsed.pkg_name,
                        pkg_query_parsed.pkg_namespace,
                        pkg_query_parsed.pkg_authority,
                        pkg_query_parsed.pkg_version,
                        pkg_query_parsed.pkg_hash,
                        pkg_query_parsed.reg_ref,
                    )
                    .await
                {
                    Err(e) => {
                        eprintln!("{}", e);
                        continue;
                    }
                    Ok(o) => o,
                };

                if result.is_empty() {
                    println!("'{}' NOT Found :(", i)
                } else {
                    if result.len() == 1 {
                        let result = &result[0];
                        let version_item = &result.versions;
                        let v = get_version(version_item.to_vec(), pkg_query_parsed.pkg_version)
                            .unwrap();
                        let mut tag = v.tag.unwrap_or("".to_string());
                        if tag != String::new() {
                            tag = format!("-{}", tag);
                        }
                        let h = v.hash;
                        println!(
                            "adding {}@{}.{}.{}{}%{:16x}{:16x}{:16x}{:16x} from ",
                            result.name, v.major, v.minor, v.patch, tag, h[0], h[1], h[2], h[3]
                        )
                    }
                }
            }
        }
        args::Command::Build(build_subcommand_args) => {
            build::build_proj(path).unwrap();
        },
    }
    //
    //
}
fn get_version(
    versions: Vec<SearchVersion>,
    version: Option<&str>,
) -> anyhow::Result<SearchVersion> {
    if let Some(version) = version {
        let v = SemVersion::parse(version);
        for i in versions {
            if i.major == v.major
                && i.minor == v.minor
                && i.patch == v.patch
                && i.tag.as_deref().unwrap_or("") == v.clone().tag.unwrap_or("".to_string())
            {
                return Ok(i);
            }
        }
    } else {
        let mut major: u16 = 0;
        let mut minor: u8 = 0;
        let mut patch: u8 = 0;
        let mut newest_version: Option<SearchVersion> = None;
        for i in versions {
            let ma = i.major;
            let mi = i.minor;
            let pa = i.patch;
            if ma > major {
                major = ma;
                if mi > minor {
                    minor = mi;
                    if pa > patch {
                        patch = pa;
                        newest_version = Some(i);
                    }
                }
            }
        }

        if major == 0 && minor == 0 && patch == 0 {
            return Err(anyhow!(
                "No version found (NOTE: Version 0.0.0 is forbidden)"
            ));
        } else {
            return Ok(newest_version.unwrap());
        }
    }

    Err(anyhow!("No version found"))
}
struct Pkg<'a> {
    pkg_name: &'a str,
    pkg_namespace: &'a str,
    pkg_authority: Option<&'a str>,
    pkg_version: Option<&'a str>,
    pkg_hash: Option<&'a str>,
    reg_ref: Option<&'a str>,
}

fn parse_pkg<'a>(pkg_input: &'a str) -> Pkg<'a> {
    let mut input = pkg_input;

    let mut reg_ref = None;
    let mut authority: Option<&str> = None;
    let mut version: Option<&str> = None;
    let mut hash: Option<&str> = None;

    // -----------------------------
    // registry / authority
    // -----------------------------
    if let Some(pos) = input.rfind('#') {
        let left = &input[..pos];
        let right = &input[pos + 1..];

        if left.starts_with("github:")
            || left.starts_with("http://")
            || left.starts_with("https://")
        {
            reg_ref = Some(left);
            input = right;
        } else {
            authority = Some(left);
            input = right;
        }
    }

    // -----------------------------
    // hash
    // -----------------------------
    if let Some(pos) = input.rfind('%') {
        hash = Some(&input[pos + 1..]);
        input = &input[..pos];
    }

    // -----------------------------
    // version
    // -----------------------------
    if let Some(pos) = input.rfind('@') {
        version = Some(&input[pos + 1..]);
        input = &input[..pos];
    }

    // -----------------------------
    // namespace + package
    // -----------------------------
    let (namespace, name) = match input.rfind('/') {
        Some(pos) => (&input[..pos], &input[pos + 1..]),
        None => ("", input),
    };

    Pkg {
        pkg_name: &name,
        pkg_namespace: &namespace,
        pkg_authority: authority,
        pkg_version: version,
        pkg_hash: hash,
        reg_ref,
    }
}