1pub mod api;
18pub mod cli;
19pub mod errors;
20pub mod health;
21pub mod i18n;
22pub mod output;
23pub mod platform;
24pub mod storage;
25
26use anyhow::{Context, Result};
27use clap::{CommandFactory, Parser};
28use std::path::PathBuf;
29use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
30
31use cli::{Cli, Comando};
32
33pub struct GuardaLog(#[allow(dead_code)] tracing_appender::non_blocking::WorkerGuard);
40
41pub fn inicializar_logging() -> Result<GuardaLog> {
46 const NOME_BINARIO: &str = env!("CARGO_PKG_NAME");
47
48 let pasta_logs = storage::descobrir_caminho_logs_xdg().unwrap_or_else(|| {
50 let raiz_compile = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
51 if raiz_compile.join("Cargo.toml").exists() {
52 raiz_compile.join("logs")
53 } else {
54 PathBuf::from("logs")
55 }
56 });
57
58 let caminho_log = pasta_logs.join(format!("{}.log", NOME_BINARIO));
59
60 if caminho_log.exists() {
62 std::fs::remove_file(&caminho_log)
63 .with_context(|| format!("Falha ao deletar log anterior: {}", caminho_log.display()))?;
64 }
65
66 std::fs::create_dir_all(&pasta_logs)
67 .with_context(|| format!("Falha ao criar pasta de logs: {}", pasta_logs.display()))?;
68
69 let appender_arquivo =
70 tracing_appender::rolling::never(&pasta_logs, format!("{}.log", NOME_BINARIO));
71 let (escritor_nao_bloqueante, guard) = tracing_appender::non_blocking(appender_arquivo);
72
73 let filtro = EnvFilter::try_from_default_env()
75 .unwrap_or_else(|_| EnvFilter::new(format!("{}=info", NOME_BINARIO)));
76
77 let camada_terminal = tracing_subscriber::fmt::layer()
78 .with_ansi(true)
79 .with_target(false)
80 .with_writer(std::io::stderr);
81
82 let camada_arquivo = tracing_subscriber::fmt::layer()
83 .with_ansi(false)
84 .with_target(true)
85 .with_writer(escritor_nao_bloqueante);
86
87 tracing_subscriber::registry()
88 .with(filtro)
89 .with(camada_terminal)
90 .with(camada_arquivo)
91 .init();
92
93 Ok(GuardaLog(guard))
94}
95
96pub async fn run() -> Result<()> {
104 let args = Cli::parse();
105
106 let idioma = i18n::resolver_idioma(args.lang.as_deref());
109 i18n::definir_idioma(idioma);
110
111 if std::env::var("NO_COLOR").is_ok() {
113 colored::control::set_override(false);
114 }
115 if std::env::var("CLICOLOR_FORCE")
117 .map(|v| v == "1")
118 .unwrap_or(false)
119 {
120 colored::control::set_override(true);
121 }
122 if args.no_color || args.json || args.plain {
124 colored::control::set_override(false);
125 }
126 output::definir_silencioso(args.quiet);
128
129 tokio::select! {
130 resultado = async {
131 match args.comando {
132 Comando::Keys { operacao } => cli::executar_keys(operacao, args.json),
133
134 Comando::Library { name, query } => cli::executar_library(name, query, args.json).await,
135
136 Comando::Docs {
137 library_id,
138 query,
139 text,
140 } => cli::executar_docs(library_id, query, text, args.json).await,
141
142 Comando::Completions { shell } => {
143 clap_complete::generate(
144 shell,
145 &mut cli::Cli::command(),
146 "context7",
147 &mut std::io::stdout(),
148 );
149 Ok(())
150 }
151
152 Comando::Health => {
153 let code = health::executar_health(args.json).await?;
154 if code != 0 {
155 std::process::exit(code);
156 }
157 Ok(())
158 }
159 }
160 } => resultado,
161 _ = tokio::signal::ctrl_c() => {
162 tracing::warn!("Interrompido pelo usuário (Ctrl+C)");
163 std::process::exit(130)
164 }
165 }
166}
167
168#[cfg(test)]
171mod testes {
172 #[test]
175 fn testa_duration_disponivel() {
176 let _ = tokio::time::Duration::from_millis(500);
177 }
178}