gh-download 0.5.1

Download a file or directory from a GitHub repository path.
Documentation
use std::path::PathBuf;

use clap::{ArgAction, Parser, ValueEnum};
use serde::Deserialize;

use crate::i18n::Language;

pub const DEFAULT_DOWNLOAD_CONCURRENCY: usize = 4;

fn parse_concurrency(value: &str) -> Result<usize, String> {
    let parsed = value
        .parse::<usize>()
        .map_err(|_| "concurrency must be a positive integer".to_string())?;
    if parsed == 0 {
        return Err("concurrency must be at least 1".to_string());
    }
    Ok(parsed)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PrefixProxyMode {
    #[default]
    Direct,
    Fallback,
    Prefer,
}

impl PrefixProxyMode {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Direct => "direct",
            Self::Fallback => "fallback",
            Self::Prefer => "prefer",
        }
    }
}

#[derive(Debug, Parser, Clone)]
#[command(name = "gh-download", version, long_about = None)]
pub(crate) struct ParsedCli {
    #[arg()]
    pub repo: String,

    #[arg()]
    pub remote_path: String,

    #[arg()]
    pub local_target: PathBuf,

    #[arg(long, value_name = "PATH")]
    pub config: Option<PathBuf>,

    #[arg(long = "ref", value_name = "REF")]
    pub git_ref: Option<String>,

    #[arg(long, value_name = "TOKEN")]
    pub token: Option<String>,

    #[arg(long, value_name = "URL")]
    pub api_base: Option<String>,

    #[arg(long, value_name = "URL")]
    pub proxy_base: Option<String>,

    #[arg(long = "prefix-mode", value_enum, value_name = "MODE")]
    pub prefix_mode: Option<PrefixProxyMode>,

    #[arg(
        short = 'c',
        long,
        value_name = "N",
        value_parser = parse_concurrency
    )]
    pub concurrency: Option<usize>,

    #[arg(long = "lang", value_enum, value_name = "LANG")]
    pub language: Option<Language>,

    #[arg(long, action = ArgAction::SetTrue)]
    pub overwrite: bool,

    #[arg(long, action = ArgAction::SetTrue)]
    pub json: bool,

    #[arg(long, action = ArgAction::SetTrue)]
    pub debug: bool,

    #[arg(long, action = ArgAction::SetTrue)]
    pub no_color: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CliInvocation {
    pub cli: Cli,
    pub config_path: Option<PathBuf>,
    pub explicit_concurrency: Option<usize>,
}

#[derive(Debug, Parser, Clone, PartialEq, Eq)]
#[command(name = "gh-download", version, long_about = None)]
pub struct Cli {
    #[arg()]
    pub repo: String,

    #[arg()]
    pub remote_path: String,

    #[arg()]
    pub local_target: PathBuf,

    #[arg(long = "ref", value_name = "REF")]
    pub git_ref: Option<String>,

    #[arg(long, value_name = "TOKEN")]
    pub token: Option<String>,

    #[arg(long, value_name = "URL")]
    pub api_base: Option<String>,

    #[arg(long, value_name = "URL")]
    pub proxy_base: Option<String>,

    #[arg(long = "prefix-mode", value_enum, value_name = "MODE")]
    pub prefix_mode: Option<PrefixProxyMode>,

    #[arg(
        short = 'c',
        long,
        value_name = "N",
        default_value_t = DEFAULT_DOWNLOAD_CONCURRENCY,
        value_parser = parse_concurrency
    )]
    pub concurrency: usize,

    #[arg(long = "lang", value_enum, value_name = "LANG")]
    pub language: Option<Language>,

    #[arg(long, action = ArgAction::SetTrue)]
    pub overwrite: bool,

    #[arg(long, action = ArgAction::SetTrue)]
    pub json: bool,

    #[arg(long, action = ArgAction::SetTrue)]
    pub debug: bool,

    #[arg(long, action = ArgAction::SetTrue)]
    pub no_color: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedOptions {
    pub repo: String,
    pub remote_path: String,
    pub local_target: PathBuf,
    pub git_ref: Option<String>,
    pub token: Option<String>,
    pub api_base: String,
    pub proxy_base: String,
    pub prefix_mode: PrefixProxyMode,
    pub concurrency: usize,
    pub language: Language,
    pub overwrite: bool,
    pub json: bool,
    pub debug: bool,
    pub no_color: bool,
}

impl From<ParsedCli> for CliInvocation {
    fn from(value: ParsedCli) -> Self {
        let explicit_concurrency = value.concurrency;
        let cli = Cli {
            repo: value.repo,
            remote_path: value.remote_path,
            local_target: value.local_target,
            git_ref: value.git_ref,
            token: value.token,
            api_base: value.api_base,
            proxy_base: value.proxy_base,
            prefix_mode: value.prefix_mode,
            concurrency: explicit_concurrency.unwrap_or(DEFAULT_DOWNLOAD_CONCURRENCY),
            language: value.language,
            overwrite: value.overwrite,
            json: value.json,
            debug: value.debug,
            no_color: value.no_color,
        };

        Self {
            cli,
            config_path: value.config,
            explicit_concurrency,
        }
    }
}