pub mod api;
pub mod cli;
pub mod errors;
pub mod i18n;
pub mod output;
pub mod platform;
pub mod storage;
use anyhow::{Context, Result};
use clap::{CommandFactory, Parser};
use std::path::PathBuf;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use cli::{Cli, Comando};
pub struct GuardaLog(#[allow(dead_code)] tracing_appender::non_blocking::WorkerGuard);
pub fn inicializar_logging() -> Result<GuardaLog> {
const NOME_BINARIO: &str = env!("CARGO_PKG_NAME");
let pasta_logs = storage::descobrir_caminho_logs_xdg().unwrap_or_else(|| {
let raiz_compile = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
if raiz_compile.join("Cargo.toml").exists() {
raiz_compile.join("logs")
} else {
PathBuf::from("logs")
}
});
let caminho_log = pasta_logs.join(format!("{}.log", NOME_BINARIO));
if caminho_log.exists() {
std::fs::remove_file(&caminho_log)
.with_context(|| format!("Falha ao deletar log anterior: {}", caminho_log.display()))?;
}
std::fs::create_dir_all(&pasta_logs)
.with_context(|| format!("Falha ao criar pasta de logs: {}", pasta_logs.display()))?;
let appender_arquivo =
tracing_appender::rolling::never(&pasta_logs, format!("{}.log", NOME_BINARIO));
let (escritor_nao_bloqueante, guard) = tracing_appender::non_blocking(appender_arquivo);
let filtro = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(format!("{}=info", NOME_BINARIO)));
let camada_terminal = tracing_subscriber::fmt::layer()
.with_ansi(true)
.with_target(false)
.with_writer(std::io::stderr);
let camada_arquivo = tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_target(true)
.with_writer(escritor_nao_bloqueante);
tracing_subscriber::registry()
.with(filtro)
.with(camada_terminal)
.with(camada_arquivo)
.init();
Ok(GuardaLog(guard))
}
pub async fn run() -> Result<()> {
let args = Cli::parse();
let idioma = i18n::resolver_idioma(args.lang.as_deref());
i18n::definir_idioma(idioma);
if std::env::var("NO_COLOR").is_ok() {
colored::control::set_override(false);
}
if std::env::var("CLICOLOR_FORCE")
.map(|v| v == "1")
.unwrap_or(false)
{
colored::control::set_override(true);
}
if args.no_color || args.json || args.plain {
colored::control::set_override(false);
}
tokio::select! {
resultado = async {
match args.comando {
Comando::Keys { operacao } => cli::executar_keys(operacao, args.json),
Comando::Library { name, query } => cli::executar_library(name, query, args.json).await,
Comando::Docs {
library_id,
query,
text,
} => cli::executar_docs(library_id, query, text, args.json).await,
Comando::Completions { shell } => {
clap_complete::generate(
shell,
&mut cli::Cli::command(),
"context7",
&mut std::io::stdout(),
);
Ok(())
}
}
} => resultado,
_ = tokio::signal::ctrl_c() => {
tracing::warn!("Interrompido pelo usuário (Ctrl+C)");
std::process::exit(130)
}
}
}
#[cfg(test)]
mod testes {
#[test]
fn testa_duration_disponivel() {
let _ = tokio::time::Duration::from_millis(500);
}
}