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