1#![warn(missing_docs)]
4#![warn(missing_debug_implementations)]
5#![warn(rustdoc::broken_intra_doc_links)]
6#![warn(rustdoc::private_intra_doc_links)]
7pub mod api;
24pub mod cli;
25pub mod errors;
26pub mod health;
27pub mod i18n;
28pub mod output;
29pub mod platform;
30pub mod storage;
31
32use anyhow::{Context, Result};
33use clap::{CommandFactory, Parser};
34use std::path::PathBuf;
35use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
36
37use cli::{Cli, Command};
38
39pub struct LogGuard(#[allow(dead_code)] tracing_appender::non_blocking::WorkerGuard);
46
47impl std::fmt::Debug for LogGuard {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_tuple("LogGuard").finish()
50 }
51}
52
53pub fn init_logging() -> Result<LogGuard> {
58 const BINARY_NAME: &str = env!("CARGO_PKG_NAME");
59
60 let logs_dir = storage::discover_xdg_log_paths().unwrap_or_else(|| {
62 let compile_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
63 if compile_root.join("Cargo.toml").exists() {
64 compile_root.join("logs")
65 } else {
66 PathBuf::from("logs")
67 }
68 });
69
70 let log_path = logs_dir.join(format!("{}.log", BINARY_NAME));
71
72 if log_path.exists() {
74 std::fs::remove_file(&log_path)
75 .with_context(|| format!("Failed to delete previous log: {}", log_path.display()))?;
76 }
77
78 std::fs::create_dir_all(&logs_dir)
79 .with_context(|| format!("Failed to create logs directory: {}", logs_dir.display()))?;
80
81 let appender_arquivo =
82 tracing_appender::rolling::never(&logs_dir, format!("{}.log", BINARY_NAME));
83 let (non_blocking_writer, guard) = tracing_appender::non_blocking(appender_arquivo);
84
85 let filtro = EnvFilter::try_from_default_env()
87 .unwrap_or_else(|_| EnvFilter::new(format!("{}=info", BINARY_NAME)));
88
89 let terminal_layer = tracing_subscriber::fmt::layer()
90 .with_ansi(true)
91 .with_target(false)
92 .with_writer(std::io::stderr);
93
94 let file_layer = tracing_subscriber::fmt::layer()
95 .with_ansi(false)
96 .with_target(true)
97 .with_writer(non_blocking_writer);
98
99 tracing_subscriber::registry()
100 .with(filtro)
101 .with(terminal_layer)
102 .with(file_layer)
103 .init();
104
105 Ok(LogGuard(guard))
106}
107
108pub async fn run() -> Result<()> {
123 let args = Cli::parse();
124
125 let language = i18n::resolve_language(args.lang.as_deref());
128 i18n::set_language(language);
129
130 if std::env::var("NO_COLOR").is_ok() {
132 colored::control::set_override(false);
133 }
134 if std::env::var("CLICOLOR_FORCE")
136 .map(|v| v == "1")
137 .unwrap_or(false)
138 {
139 colored::control::set_override(true);
140 }
141 if args.no_color || args.json || args.plain {
143 colored::control::set_override(false);
144 }
145 output::set_quiet(args.quiet);
147
148 tokio::select! {
149 result = async {
150 match args.command {
151 Command::Keys { operation } => cli::run_keys(operation, args.json),
152
153 Command::Library { name, query } => cli::run_library(name, query, args.json).await,
154
155 Command::Docs {
156 library_id,
157 query,
158 text,
159 } => cli::run_docs(library_id, query, text, args.json).await,
160
161 Command::Completions { shell } => {
162 clap_complete::generate(
163 shell,
164 &mut cli::Cli::command(),
165 "context7",
166 &mut std::io::stdout(),
167 );
168 Ok(())
169 }
170
171 Command::Health => {
172 let code = health::run_health(args.json).await?;
173 if code != 0 {
174 std::process::exit(code);
175 }
176 Ok(())
177 }
178 }
179 } => result,
180 _ = tokio::signal::ctrl_c() => {
181 tracing::warn!("Interrupted by user (Ctrl+C)");
182 std::process::exit(130)
183 }
184 }
185}
186
187#[cfg(test)]
190mod tests {
191 #[test]
194 fn test_duration_available() {
195 let _ = tokio::time::Duration::from_millis(500);
196 }
197}