huber 1.0.11

Huber, simplify GitHub package management
Documentation
use std::fmt::{Display, Formatter};
use std::str::FromStr;

use async_trait::async_trait;
use clap::builder::PossibleValue;
use clap::{Subcommand, ValueEnum};
use clap_complete::Shell;
use config::ConfigArgs;
use current::CurrentArgs;
use flush::FlushArgs;
use info::InfoArgs;
use install::InstallArgs;
use repo::RepoArgs;
use reset::ResetArgs;
use search::SearchArgs;
use self_update::SelfUpdateArgs;
use show::ShowArgs;
use simpledi_rs::di::DIContainer;
use uninstall::UninstallArgs;
use update::UpdateArgs;

use crate::cmd::load::LoadArgs;
use crate::cmd::lock::LockArgs;
use crate::cmd::save::SaveArgs;
use crate::cmd::unlock::UnlockArgs;
use crate::model::config::Config;

pub mod config;
mod current;
mod flush;
mod info;
mod install;
mod load;
pub mod lock;
pub mod repo;
mod reset;
mod save;
mod search;
mod self_update;
mod show;
mod uninstall;
mod unlock;
mod update;

#[async_trait]
pub trait CommandTrait {
    async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()>;
}

#[derive(Subcommand)]
pub enum Commands {
    #[command(about = "Manage Huber configurations", bin_name = "config")]
    Config(ConfigArgs),

    #[command(about = "Update the current package versions", bin_name = "current")]
    Current(CurrentArgs),

    #[command(
        about = "Show command completions for the specified shell",
        bin_name = "completions"
    )]
    Completions {
        #[arg(help = "Shell name", num_args = 1, value_enum)]
        shell: Shell,
    },

    #[command(about = "Remove outdated installed artifacts", bin_name = "flush")]
    Flush(FlushArgs),

    #[command(about = "Show package information", bin_name = "info")]
    Info(InfoArgs),

    #[command(about = "Install packages", bin_name = "install")]
    Install(InstallArgs),

    #[command(about = "Manage repositories", bin_name = "repo")]
    Repo(RepoArgs),

    #[command(about = "Reset Huber", bin_name = "reset")]
    Reset(ResetArgs),

    #[command(about = "Search package", bin_name = "search")]
    Search(SearchArgs),

    #[command(about = "Update Huber", bin_name = "self-update")]
    SelfUpdate(SelfUpdateArgs),

    #[command(about = "Show installed packages", bin_name = "show")]
    Show(ShowArgs),

    #[command(about = "Uninstall packages", bin_name = "uninstall")]
    Uninstall(UninstallArgs),

    #[command(
        about = "Update installed packages to their latest released versions",
        bin_name = "update"
    )]
    Update(UpdateArgs),

    #[command(about = "Save the installed package list to a file", bin_name = "save")]
    Save(SaveArgs),

    #[command(
        about = "Load installed packages from a file generated by save command",
        bin_name = "load"
    )]
    Load(LoadArgs),

    #[command(about = "Lock packages or Show locked packages", bin_name = "lock")]
    Lock(LockArgs),

    #[command(about = "Unlock packages", bin_name = "unlock", bin_name = "unlock")]
    Unlock(UnlockArgs),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PlatformStdLib {
    #[cfg(any(target_os = "linux", target_os = "windows"))]
    Gnu,
    #[cfg(target_os = "linux")]
    Musl,
    #[cfg(target_os = "windows")]
    Msvc,
    None,
}

impl Display for PlatformStdLib {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        self.to_possible_value()
            .expect("no values are skipped")
            .get_name()
            .fmt(f)
    }
}

impl FromStr for PlatformStdLib {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        for variant in Self::value_variants() {
            if variant.to_possible_value().unwrap().matches(s, false) {
                return Ok(*variant);
            }
        }
        Err(format!("invalid variant: {s}"))
    }
}

impl ValueEnum for PlatformStdLib {
    #[cfg(target_os = "linux")]
    fn value_variants<'a>() -> &'a [Self] {
        &[PlatformStdLib::Gnu, PlatformStdLib::Musl]
    }

    #[cfg(target_os = "macos")]
    fn value_variants<'a>() -> &'a [Self] {
        &[]
    }

    #[cfg(target_os = "windows")]
    fn value_variants<'a>() -> &'a [Self] {
        &[PlatformStdLib::Gnu, PlatformStdLib::Msvc]
    }

    #[cfg(target_os = "linux")]
    fn to_possible_value(&self) -> Option<PossibleValue> {
        Some(if let PlatformStdLib::Gnu = self {
            PossibleValue::new("gnu")
        } else {
            PossibleValue::new("musl")
        })
    }

    #[cfg(target_os = "windows")]
    fn to_possible_value(&self) -> Option<PossibleValue> {
        Some(if let PlatformStdLib::Gnu = self {
            PossibleValue::new("gnu")
        } else {
            PossibleValue::new("msvc")
        })
    }

    #[cfg(target_os = "macos")]
    fn to_possible_value(&self) -> Option<PossibleValue> {
        None
    }
}

#[macro_export]
macro_rules! lock_huber_ops {
    ($config:ident) => {
        use huber_procmacro::process_lock;
        use $crate::model::config::ConfigPath;

        #[cfg(not(target_os = "windows"))]
        {
            let lock_file = $config.lock_file()?;
            process_lock!(lock_file);
        }
    };
}

pub fn get_updated_package_version(version: &str, latest_version: &str) -> String {
    if latest_version.starts_with("v") && !version.starts_with("v") {
        format!("v{}", version)
    } else {
        version.to_string()
    }
}

pub fn get_default_stdlib() -> PlatformStdLib {
    #[cfg(target_os = "linux")]
    {
        PlatformStdLib::Gnu
    }
    #[cfg(target_os = "windows")]
    {
        PlatformStdLib::Msvc
    }
    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
    {
        PlatformStdLib::None
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_get_updated_package_version() {
        let version = "1.0.0";
        let latest_version = "v1.0.1";
        assert_eq!(
            get_updated_package_version(version, latest_version),
            "v1.0.0"
        );

        let version = "v1.0.0";
        let latest_version = "1.0.1";
        assert_eq!(
            get_updated_package_version(version, latest_version),
            "v1.0.0"
        );

        let version = "v1.0.0";
        let latest_version = "v1.0.1";
        assert_eq!(
            get_updated_package_version(version, latest_version),
            "v1.0.0"
        );

        let version = "1.0.0";
        let latest_version = "1.0.1";
        assert_eq!(
            get_updated_package_version(version, latest_version),
            "1.0.0"
        );
    }
}