gh-download 0.2.0

Download a file or directory from a GitHub repository path.
Documentation
use crate::cli::ResolvedOptions;
use crate::download::format_remote_path;
use crate::error::UserFacingError;
use crate::i18n::Language;
use std::path::Path;

#[derive(Debug, Clone)]
pub struct Output {
    color: bool,
    language: Language,
}

impl Output {
    pub fn new(color: bool, language: Language) -> Self {
        Self { color, language }
    }

    pub fn startup(&self, options: &ResolvedOptions) {
        self.print_separator();
        println!(
            "{}{}",
            self.paint("34", self.label_repository()),
            options.repo
        );
        println!(
            "{}{}",
            self.paint("32", self.label_ref()),
            options
                .git_ref
                .as_deref()
                .unwrap_or(self.default_ref_label())
        );
        println!(
            "{}{}",
            self.paint("33", self.label_remote()),
            format_remote_path(&options.remote_path)
        );
        println!(
            "{}{}",
            self.paint("35", self.label_local()),
            options.local_target.display()
        );
        self.print_separator();
    }

    pub fn found_directory(&self, count: usize, remote_path: &str) {
        println!("{}", self.message_found_files(count, remote_path));
    }

    pub fn created_directory(&self, path: &Path) {
        println!(
            "{}{}",
            self.paint("34", self.label_created_directory()),
            path.display()
        );
        self.print_separator();
    }

    pub fn downloading(&self, path: &str) {
        println!("{}{}", self.paint("34", self.label_downloading()), path);
    }

    pub fn warning(&self, message: &str) {
        println!("{} {}", self.paint("33", ""), message);
    }

    pub fn success(&self, message: &str) {
        println!("{} {}", self.paint("32", ""), message);
    }

    pub fn completion(
        &self,
        repo: &str,
        remote_path: &str,
        saved_path: &Path,
        files_downloaded: usize,
        skipped_entries: usize,
    ) {
        self.print_separator();
        println!("{}", self.message_completion(repo, remote_path, saved_path));
        if files_downloaded > 1 || skipped_entries > 0 {
            println!(
                "{}",
                self.message_download_stats(files_downloaded, skipped_entries)
            );
        }
    }

    pub fn print_user_error(&self, error: &UserFacingError) {
        eprintln!("{}", self.paint("31", &error.title));
        eprintln!("{} {}", self.reason_label(), error.reason);
        if !error.suggestions.is_empty() {
            eprintln!("{}", self.suggestions_label());
            for suggestion in &error.suggestions {
                eprintln!("- {}", suggestion);
            }
        }
    }

    fn label_repository(&self) -> &'static str {
        if self.language.is_chinese() {
            "📦 仓库:"
        } else {
            "📦 Repository:"
        }
    }

    fn label_ref(&self) -> &'static str {
        if self.language.is_chinese() {
            "🌿 分支:"
        } else {
            "🌿 Ref:"
        }
    }

    fn default_ref_label(&self) -> &'static str {
        if self.language.is_chinese() {
            "默认分支"
        } else {
            "default branch"
        }
    }

    fn label_remote(&self) -> &'static str {
        if self.language.is_chinese() {
            "📂 远端路径:"
        } else {
            "📂 Remote Path:"
        }
    }

    fn label_local(&self) -> &'static str {
        if self.language.is_chinese() {
            "💾 本地路径:"
        } else {
            "💾 Local Path:"
        }
    }

    fn label_created_directory(&self) -> &'static str {
        if self.language.is_chinese() {
            "📁 创建本地目录:"
        } else {
            "📁 Created Local Directory:"
        }
    }

    fn label_downloading(&self) -> &'static str {
        if self.language.is_chinese() {
            "⬇️ 下载:"
        } else {
            "⬇️ Download:"
        }
    }

    fn reason_label(&self) -> &'static str {
        if self.language.is_chinese() {
            "原因:"
        } else {
            "Reason:"
        }
    }

    fn suggestions_label(&self) -> &'static str {
        if self.language.is_chinese() {
            "建议:"
        } else {
            "Suggestions:"
        }
    }

    fn message_found_files(&self, count: usize, remote_path: &str) -> String {
        if self.language.is_chinese() {
            format!(
                "{} 发现 {} 个文件,目录:{}",
                self.paint("33", "🔎"),
                count,
                format_remote_path(remote_path)
            )
        } else {
            format!(
                "{} Found {} files in directory: {}",
                self.paint("33", "🔎"),
                count,
                format_remote_path(remote_path)
            )
        }
    }

    fn message_completion(&self, repo: &str, remote_path: &str, saved_path: &Path) -> String {
        if self.language.is_chinese() {
            format!(
                "{} 完成:{}{} 已保存到 {}",
                self.paint("32", ""),
                repo,
                format_remote_path(remote_path),
                saved_path.display()
            )
        } else {
            format!(
                "{} Done: {} {} saved to {}",
                self.paint("32", ""),
                repo,
                format_remote_path(remote_path),
                saved_path.display()
            )
        }
    }

    fn message_download_stats(&self, files_downloaded: usize, skipped_entries: usize) -> String {
        if self.language.is_chinese() {
            format!(
                "共下载 {} 个文件,跳过 {} 个条目",
                files_downloaded, skipped_entries
            )
        } else {
            format!(
                "Downloaded {} files, skipped {} entries",
                files_downloaded, skipped_entries
            )
        }
    }

    fn print_separator(&self) {
        println!(
            "{}",
            self.paint("90", "-------------------------------------")
        );
    }

    fn paint(&self, code: &str, text: &str) -> String {
        if self.color {
            format!("\u{1b}[{}m{}\u{1b}[0m", code, text)
        } else {
            text.to_string()
        }
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::*;

    #[test]
    fn chinese_found_directory_mentions_count_and_remote_path() {
        let output = Output::new(false, Language::Zh);

        assert_eq!(
            output.message_found_files(11, "skills/baoyu-translate"),
            "🔎 发现 11 个文件,目录:skills/baoyu-translate"
        );
    }

    #[test]
    fn chinese_completion_mentions_repo_remote_path_and_saved_path() {
        let output = Output::new(false, Language::Zh);

        assert_eq!(
            output.message_completion(
                "jimliu/baoyu-skills",
                "skills/baoyu-translate",
                Path::new("/tmp/baoyu-translate")
            ),
            "✅ 完成:jimliu/baoyu-skills 的 skills/baoyu-translate 已保存到 /tmp/baoyu-translate"
        );
    }
}