Skip to main content

context7_cli/
output.rs

1/// Terminal output formatting.
2///
3/// This is the **only** module allowed to call `println!` or `eprintln!`.
4/// All coloured formatting via the `colored` crate is centralised here.
5/// All user-facing strings are resolved via [`crate::i18n::t`].
6use colored::Colorize;
7
8use crate::api::{DocumentationSnippet, LibrarySearchResult, RespostaDocumentacao};
9use crate::i18n::{idioma_atual, t, Idioma, Mensagem};
10use crate::storage::ChaveArmazenada;
11
12// ─── BIBLIOTECA ───────────────────────────────────────────────────────────────
13
14/// Prints the list of libraries returned by the search endpoint.
15///
16/// Displays index, library ID (bold), title (dimmed), optional description
17/// (italic), and optional trust score.
18pub fn exibir_bibliotecas_formatado(resultados: &[LibrarySearchResult]) {
19    if resultados.is_empty() {
20        println!("{}", t(Mensagem::NenhumaBibliotecaEncontrada).yellow());
21        return;
22    }
23
24    println!("{}", t(Mensagem::BibliotecasEncontradas).green().bold());
25    println!("{}", "─".repeat(60).dimmed());
26
27    for (i, lib) in resultados.iter().enumerate() {
28        let numero = format!("{}.", i + 1);
29        println!("{} {} {}", numero.cyan(), lib.id.bold(), lib.title.dimmed());
30
31        if let Some(desc) = &lib.description {
32            println!("   {}", desc.italic());
33        }
34
35        if let Some(score) = lib.trust_score {
36            println!("   {} {:.1}", t(Mensagem::ConfiancaScore).dimmed(), score);
37        }
38
39        println!();
40    }
41}
42
43// ─── DOCUMENTAÇÃO ─────────────────────────────────────────────────────────────
44
45/// Prints structured documentation from the docs endpoint.
46///
47/// Iterates over `snippets` when present; falls back to raw `content`.
48pub fn exibir_documentacao_formatada(doc: &RespostaDocumentacao) {
49    if let Some(snippets) = &doc.snippets {
50        if snippets.is_empty() {
51            println!("{}", t(Mensagem::NenhumaDocumentacaoEncontrada).yellow());
52            return;
53        }
54
55        println!("{}", t(Mensagem::TituloDocumentacao).green().bold());
56        println!("{}", "─".repeat(60).dimmed());
57
58        for snippet in snippets {
59            exibir_snippet(snippet);
60        }
61    } else if let Some(conteudo) = &doc.content {
62        println!("{}", conteudo);
63    } else {
64        println!("{}", t(Mensagem::SemConteudoDisponivel).yellow());
65    }
66}
67
68/// Prints a single documentation snippet with its source URLs.
69fn exibir_snippet(snippet: &DocumentationSnippet) {
70    println!("{}", snippet.content);
71
72    if let Some(urls) = &snippet.source_urls {
73        println!("\n{}", t(Mensagem::TituloFontes).dimmed());
74        for url in urls {
75            println!("  {}", url.blue().bold());
76        }
77    }
78
79    println!();
80}
81
82// ─── CHAVES ───────────────────────────────────────────────────────────────────
83
84/// Prints all stored keys with 1-based indices and masked values.
85pub fn exibir_chaves_mascaradas(chaves: &[ChaveArmazenada], mascarar: impl Fn(&str) -> String) {
86    println!(
87        "{}",
88        format!("{} {}", chaves.len(), t(Mensagem::ContadorChaves))
89            .green()
90            .bold()
91    );
92    println!("{}", "─".repeat(60).dimmed());
93
94    let rotulo_adicionada = match idioma_atual() {
95        Idioma::English => "added:",
96        Idioma::Portugues => "adicionada:",
97    };
98
99    for (i, chave) in chaves.iter().enumerate() {
100        println!(
101            "  {}  {}  {}",
102            format!("[{}]", i + 1).cyan(),
103            mascarar(&chave.value).bold(),
104            format!("({} {})", rotulo_adicionada, chave.added_at).dimmed()
105        );
106    }
107}
108
109/// Prints the "no keys stored" hint message.
110pub fn exibir_nenhuma_chave() {
111    println!("{}", t(Mensagem::NenhumaChaveArmazenada).yellow());
112    println!("{}", t(Mensagem::UsarKeysAdd).cyan());
113}
114
115/// Prints the "no keys to remove" message.
116pub fn exibir_nenhuma_chave_para_remover() {
117    println!("{}", t(Mensagem::NenhumaChaveParaRemover).yellow());
118}
119
120/// Prints an invalid index error.
121pub fn exibir_indice_invalido(indice: usize, total: usize) {
122    println!(
123        "{}",
124        format!(
125            "{} {} {} {}.",
126            t(Mensagem::IndiceInvalido),
127            indice,
128            "/",
129            total
130        )
131        .red()
132    );
133}
134
135/// Prints the success message for `keys add`.
136pub fn exibir_chave_adicionada(caminho: &std::path::Path) {
137    println!(
138        "{} {}",
139        t(Mensagem::ChaveAdicionada),
140        caminho.display().to_string().green()
141    );
142}
143
144/// Prints the success message for `keys remove`.
145pub fn exibir_chave_removida(chave_mascarada: &str) {
146    println!(
147        "{} {}",
148        chave_mascarada.bold(),
149        t(Mensagem::ChaveRemovidaSucesso)
150    );
151}
152
153/// Prints the cancellation message for `keys clear`.
154pub fn exibir_operacao_cancelada() {
155    println!("{}", t(Mensagem::OperacaoCancelada).yellow());
156}
157
158/// Prints the success message for `keys clear`.
159pub fn exibir_chaves_removidas() {
160    println!("{}", t(Mensagem::TodasChavesRemovidas).green());
161}
162
163/// Prints an "XDG not supported" error for `keys path`.
164pub fn exibir_xdg_nao_suportado() {
165    println!("{}", t(Mensagem::SistemaXdgNaoSuportado).red());
166}
167
168/// Prints the success message for `keys import`.
169pub fn exibir_importacao_concluida(importadas: usize, total: usize) {
170    println!(
171        "{}",
172        format!(
173            "{}/{} {}",
174            importadas,
175            total,
176            t(Mensagem::ChavesImportadasSucesso)
177        )
178        .green()
179    );
180}