huber 1.0.11

Huber, simplify GitHub package management
Documentation
use std::io::stdout;
use std::path::PathBuf;

use anyhow::anyhow;
use async_trait::async_trait;
use clap::{Args, Subcommand, ValueHint};
use libcli_rs::output;
use libcli_rs::output::{OutputFactory, OutputTrait};
use log::{info, warn};
use simpledi_rs::di::{DIContainer, DIContainerTrait};

use crate::cmd::CommandTrait;
use crate::error::HuberError::{RepoAlreadyExist, RepoNotFound};
use crate::lock_huber_ops;
use crate::model::config::Config;
use crate::model::repo::{Repository, LOCAL_REPO};
use crate::service::repo::RepoService;
use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait};

#[derive(Args)]
pub struct RepoArgs {
    #[command(subcommand)]
    pub command: RepoCommands,
}

#[derive(Subcommand)]
pub enum RepoCommands {
    #[command(about = "Add a new repository", bin_name = "add")]
    Add(RepoAddArgs),

    #[command(about = "Remove a repository", bin_name = "remove")]
    Remove(RepoRemoveArgs),

    #[command(about = "Show all repositories", bin_name = "list")]
    Show(RepoShowArgs),
}

#[derive(Args)]
pub struct RepoAddArgs {
    #[arg(help = "Repo name", num_args = 1, value_hint = ValueHint::Unknown)]
    name: String,

    #[arg(
        help = "URL of the Huber package index file",
        long,
        num_args = 1,
        group = "repo",
        required_unless_present_any = &["file"],
        value_hint = ValueHint::Url
    )]
    url: Option<String>,

    #[arg(
        help = "File path of the Huber package index file",
        long,
        num_args = 1,
        group = "repo",
        required_unless_present_any = &["url"],
        value_hint = ValueHint::FilePath
    )]
    file: Option<String>,
}

#[async_trait]
impl CommandTrait for RepoAddArgs {
    async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()> {
        lock_huber_ops!(config);

        let repo_service = container.get::<RepoService>().unwrap();

        if repo_service.has(&self.name)? {
            return Err(anyhow!(RepoAlreadyExist(self.name.clone())));
        }

        let repo = Repository {
            name: self.name.clone(),
            url: self.url.clone(),
            file: self.file.clone().map(PathBuf::from),
        };
        info!("Adding repo {}", repo.name);
        if let Err(err) = repo_service.create(repo.clone()).await {
            return Err(anyhow!("Failed to add repo {}: {}", repo.name, err));
        };
        info!("Repo {} added", repo.name);

        Ok(())
    }
}

#[derive(Args)]
pub struct RepoRemoveArgs {
    #[arg(help = "Repo names", num_args = 1, value_hint = ValueHint::Unknown)]
    name: Vec<String>,
}

#[async_trait]
impl CommandTrait for RepoRemoveArgs {
    async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()> {
        lock_huber_ops!(config);

        let repo_service = container.get::<RepoService>().unwrap();

        for repo in &self.name {
            if repo == LOCAL_REPO {
                warn!("Cannot remove builtin local repo");
                continue;
            }

            if !repo_service.has(repo)? {
                return Err(anyhow!(RepoNotFound(repo.clone())));
            }

            info!("Removing repo {}", repo);
            repo_service.delete(repo)?;
            info!("Repo {} removed", repo);
        }

        Ok(())
    }
}

#[derive(Args)]
pub struct RepoShowArgs {}

#[async_trait]
impl CommandTrait for RepoShowArgs {
    async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()> {
        let repo_service = container.get::<RepoService>().unwrap();

        let repos = repo_service.list()?;
        if repos.is_empty() {
            info!("No repositories added");
            return Ok(());
        }

        output!(
            config.output_format,
            .display(
                stdout(),
                &repos,
                None,
                None,
            )
        )
    }
}