gh-download 0.2.0

Download a file or directory from a GitHub repository path.
Documentation
use clap::CommandFactory;

use crate::i18n::Language;

use super::types::Cli;

pub fn command() -> clap::Command {
    command_for_language(Language::En)
}

pub fn command_for_language(language: Language) -> clap::Command {
    let mut command = Cli::command()
        .help_template(help_template(language))
        .about(command_about(language))
        .after_help(command_after_help(language));

    command = command
        .mut_arg("repo", |arg| arg.help(repo_help(language)))
        .mut_arg("remote_path", |arg| arg.help(remote_path_help(language)))
        .mut_arg("local_target", |arg| arg.help(local_target_help(language)))
        .mut_arg("git_ref", |arg| arg.help(ref_help(language)))
        .mut_arg("token", |arg| arg.help(token_help(language)))
        .mut_arg("proxy_base", |arg| arg.help(proxy_help(language)))
        .mut_arg("prefix_mode", |arg| arg.help(prefix_mode_help(language)))
        .mut_arg("language", |arg| arg.help(language_help(language)))
        .mut_arg("debug", |arg| arg.help(debug_help(language)))
        .mut_arg("no_color", |arg| arg.help(no_color_help(language)));

    command
}

fn command_about(language: Language) -> &'static str {
    match language {
        Language::En => "Download a file or directory from a GitHub repository path",
        Language::Zh => "下载 GitHub 仓库里的单个文件或整个目录",
    }
}

fn command_after_help(language: Language) -> &'static str {
    match language {
        Language::En => {
            "Examples:\n  gh-download openai/openai-python README.md ./README.md\n  gh-download owner/repo src ./downloads --ref main\n  gh-download owner/private-repo docs ./docs --token <token>\n  gh-download owner/repo docs ./docs --lang zh"
        }
        Language::Zh => {
            "示例:\n  gh-download openai/openai-python README.md ./README.md\n  gh-download owner/repo src ./downloads --ref main\n  gh-download owner/private-repo docs ./docs --token <token>\n  gh-download owner/repo docs ./docs --lang zh"
        }
    }
}

fn help_template(language: Language) -> &'static str {
    match language {
        Language::En => {
            "{about-with-newline}\nUsage: {usage}\n\nArguments:\n{positionals}\nOptions:\n{options}{after-help}\n"
        }
        Language::Zh => {
            "{about-with-newline}\n用法: {usage}\n\n参数:\n{positionals}\n选项:\n{options}{after-help}\n"
        }
    }
}

fn repo_help(language: Language) -> &'static str {
    match language {
        Language::En => "GitHub repository in OWNER/REPO format, for example openai/openai-python",
        Language::Zh => "GitHub 仓库,格式为 OWNER/REPO,例如 openai/openai-python",
    }
}

fn remote_path_help(language: Language) -> &'static str {
    match language {
        Language::En => "Path inside the repository, for example README.md or src/openai",
        Language::Zh => "仓库内路径,例如 README.md 或 src/openai",
    }
}

fn local_target_help(language: Language) -> &'static str {
    match language {
        Language::En => {
            "Local destination path. Files may be written to a file path or an existing directory; directory downloads treat it as the parent directory by default"
        }
        Language::Zh => "本地目标路径。文件可写入文件路径或现有目录;目录下载时默认作为父目录",
    }
}

fn ref_help(language: Language) -> &'static str {
    match language {
        Language::En => "Branch, tag, or commit SHA",
        Language::Zh => "分支、tag 或 commit SHA",
    }
}

fn token_help(language: Language) -> &'static str {
    match language {
        Language::En => "GitHub token. Defaults to GITHUB_TOKEN or GH_TOKEN",
        Language::Zh => "GitHub token。默认读取 GITHUB_TOKEN 或 GH_TOKEN",
    }
}

fn proxy_help(language: Language) -> &'static str {
    match language {
        Language::En => {
            "URL-prefix proxy base for anonymous raw downloads. Defaults to GH_PROXY_BASE; in fallback/prefer mode it falls back to the built-in gh-proxy when unset"
        }
        Language::Zh => {
            "匿名 raw 下载使用的 URL 前缀代理。默认读取 GH_PROXY_BASE;在 fallback/prefer 模式下未设置时会回退到内置 gh-proxy"
        }
    }
}

fn prefix_mode_help(language: Language) -> &'static str {
    match language {
        Language::En => {
            "Raw download prefix-proxy mode: direct, fallback, or prefer. Defaults to GH_DOWNLOAD_PREFIX_MODE or direct"
        }
        Language::Zh => {
            "raw 下载的前缀代理模式:direct、fallback 或 prefer。默认读取 GH_DOWNLOAD_PREFIX_MODE,未设置时为 direct"
        }
    }
}

fn language_help(language: Language) -> &'static str {
    match language {
        Language::En => {
            "Force the user-facing language. Defaults to English unless the locale indicates Chinese"
        }
        Language::Zh => "显式指定用户可见语言。默认英文;当 locale 指向中文时自动切换为中文",
    }
}

fn debug_help(language: Language) -> &'static str {
    match language {
        Language::En => {
            "Print debug diagnostics for request URLs and download strategy. Defaults to GH_DOWNLOAD_DEBUG"
        }
        Language::Zh => "打印请求 URL 和下载策略的调试信息。默认读取 GH_DOWNLOAD_DEBUG",
    }
}

fn no_color_help(language: Language) -> &'static str {
    match language {
        Language::En => "Disable ANSI colors",
        Language::Zh => "关闭 ANSI 彩色输出",
    }
}

#[cfg(test)]
mod tests {
    use std::ffi::OsString;

    use clap::error::ErrorKind;

    use super::*;

    #[test]
    fn help_is_localized_for_chinese() {
        let mut command = command_for_language(Language::Zh);
        let rendered = command.render_help().to_string();
        assert!(rendered.contains("用法:"));
        assert!(rendered.contains("显式指定用户可见语言"));
    }

    #[test]
    fn empty_invocation_uses_localized_help_flow() {
        let args = vec![OsString::from("gh-download"), OsString::from("--help")];
        let mut command = command_for_language(Language::Zh);
        let error = command
            .try_get_matches_from_mut(args)
            .expect_err("empty invocation should display help");

        assert_eq!(error.kind(), ErrorKind::DisplayHelp);
        let rendered = error.to_string();
        assert!(rendered.contains("用法:"));
        assert!(rendered.contains("下载 GitHub 仓库里的单个文件或整个目录"));
    }

    #[test]
    fn partial_invocation_still_requires_missing_arguments() {
        let args = vec![OsString::from("gh-download"), OsString::from("owner/repo")];
        let mut command = command_for_language(Language::En);
        let error = command
            .try_get_matches_from_mut(args)
            .expect_err("partial invocation should still fail");

        assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument);
        let rendered = error.to_string();
        assert!(rendered.contains("<REMOTE_PATH>"));
        assert!(rendered.contains("<LOCAL_TARGET>"));
    }
}