#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(rustdoc::private_intra_doc_links)]
pub mod api;
pub mod cli;
pub mod errors;
pub mod health;
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, Command};
pub struct LogGuard(#[allow(dead_code)] tracing_appender::non_blocking::WorkerGuard);
impl std::fmt::Debug for LogGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("LogGuard").finish()
}
}
pub fn init_logging() -> Result<LogGuard> {
const BINARY_NAME: &str = env!("CARGO_PKG_NAME");
let logs_dir = storage::discover_xdg_log_paths().unwrap_or_else(|| {
let compile_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
if compile_root.join("Cargo.toml").exists() {
compile_root.join("logs")
} else {
PathBuf::from("logs")
}
});
let log_path = logs_dir.join(format!("{}.log", BINARY_NAME));
if log_path.exists() {
std::fs::remove_file(&log_path)
.with_context(|| format!("Failed to delete previous log: {}", log_path.display()))?;
}
std::fs::create_dir_all(&logs_dir)
.with_context(|| format!("Failed to create logs directory: {}", logs_dir.display()))?;
let appender_arquivo =
tracing_appender::rolling::never(&logs_dir, format!("{}.log", BINARY_NAME));
let (non_blocking_writer, guard) = tracing_appender::non_blocking(appender_arquivo);
let filtro = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(format!("{}=info", BINARY_NAME)));
let terminal_layer = tracing_subscriber::fmt::layer()
.with_ansi(true)
.with_target(false)
.with_writer(std::io::stderr);
let file_layer = tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_target(true)
.with_writer(non_blocking_writer);
tracing_subscriber::registry()
.with(filtro)
.with(terminal_layer)
.with(file_layer)
.init();
Ok(LogGuard(guard))
}
pub async fn run() -> Result<()> {
let args = Cli::parse();
let language = i18n::resolve_language(args.lang.as_deref());
i18n::set_language(language);
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);
}
output::set_quiet(args.quiet);
tokio::select! {
result = async {
match args.command {
Command::Keys { operation } => cli::run_keys(operation, args.json),
Command::Library { name, query } => cli::run_library(name, query, args.json).await,
Command::Docs {
library_id,
query,
text,
} => cli::run_docs(library_id, query, text, args.json).await,
Command::Completions { shell } => {
clap_complete::generate(
shell,
&mut cli::Cli::command(),
"context7",
&mut std::io::stdout(),
);
Ok(())
}
Command::Health => {
let code = health::run_health(args.json).await?;
if code != 0 {
std::process::exit(code);
}
Ok(())
}
}
} => result,
_ = tokio::signal::ctrl_c() => {
tracing::warn!("Interrupted by user (Ctrl+C)");
std::process::exit(130)
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_duration_available() {
let _ = tokio::time::Duration::from_millis(500);
}
}