pub mod cli;
pub mod config;
pub mod constants;
pub mod core_types;
pub mod discovery;
pub mod errors;
use crate::filtering::is_likely_text;
mod filtering;
pub mod git;
pub mod output;
pub mod processing;
pub mod signal;
use crate::config::Config;
use crate::core_types::FileInfo;
use anyhow::Result;
use log::debug;
use log::info;
use rayon::prelude::*;
use std::io::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc;
pub fn run(config: Config) -> Result<()> {
info!("Starting dircat run with config: {:?}", config);
#[cfg(not(test))]
let stop_signal: Arc<AtomicBool> = {
info!("Setting up real signal handler.");
signal::setup_signal_handler()?
};
#[cfg(test)]
let stop_signal: Arc<AtomicBool> = {
info!("Using dummy signal handler for test run.");
Arc::new(AtomicBool::new(true))
};
debug!(
"stop_signal initial state: {}",
stop_signal.load(Ordering::Relaxed) );
info!("Signal handler setup complete (real or dummy).");
let (normal_files, last_files) = discovery::discover_files(&config, stop_signal.clone())?; info!(
"Discovered {} normal files and {} files to process last.",
normal_files.len(),
last_files.len()
);
let all_candidates: Vec<FileInfo> = normal_files.into_iter().chain(last_files).collect();
let result = {
let writer_setup = output::writer::setup_output_writer(&config)?;
let mut writer: Box<dyn Write + Send> = writer_setup.writer;
let clipboard_buffer = writer_setup.clipboard_buffer;
if config.dry_run {
info!("Performing dry run, filtering for binary files...");
let filtered_files: Vec<FileInfo> = all_candidates
.into_par_iter()
.filter(|fi| {
if config.include_binary {
return true;
}
match is_likely_text(&fi.absolute_path) {
Ok(is_text) => is_text,
Err(e) => {
log::warn!(
"Dry run: Could not check file type for '{}', skipping. Error: {}",
fi.absolute_path.display(),
e
);
false
}
}
})
.collect();
if filtered_files.is_empty() {
info!("Dry run: No files found matching criteria after filtering.");
eprintln!("dircat: No files found matching the specified criteria.");
} else {
let file_refs: Vec<&FileInfo> = filtered_files.iter().collect();
output::dry_run::write_dry_run_output(&mut writer, &file_refs, &config)?;
info!("Dry run output generated.");
}
} else {
info!("Starting file content processing...");
let processed_files =
processing::process_and_filter_files(all_candidates, &config, stop_signal)?;
if processed_files.is_empty() {
info!("No files found matching criteria (or all were filtered out). Exiting.");
eprintln!("dircat: No files found matching the specified criteria.");
} else {
let (last_files, normal_files): (Vec<_>, Vec<_>) = processed_files
.into_iter()
.partition(|fi| fi.is_process_last);
info!("File content processing complete.");
info!("Generating final output...");
output::generate_output(&normal_files, &last_files, &config, &mut writer)?;
info!("Final output generated.");
}
}
output::writer::finalize_output(writer, clipboard_buffer, &config)
};
match result {
Ok(_) => info!("dircat run completed successfully."),
Err(e) => {
log::error!("dircat run failed: {}", e);
if let Some(app_err) = e.downcast_ref::<errors::AppError>() {
if matches!(app_err, errors::AppError::Interrupted) {
log::error!("Run failed due to AppError::Interrupted.");
eprintln!("Operation cancelled."); } else {
eprintln!("Error: {}", e); }
} else {
eprintln!("Error: {}", e); }
return Err(e); }
}
Ok(())
}