use anyhow::Result;
use content_inspector::{inspect, ContentType};
use rayon::prelude::*;
use std::{
collections::HashMap,
fs::{self, File},
io::{self, Read, Write},
path::Path,
};
pub mod config;
pub mod defaults;
mod parallel;
pub mod priority;
use config::FullYekConfig;
use parallel::{process_files_parallel, ProcessedFile};
use priority::compute_recentness_boost;
pub fn is_text_file(path: &Path, user_binary_extensions: &[String]) -> io::Result<bool> {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if user_binary_extensions.iter().any(|bin_ext| bin_ext == ext) {
return Ok(false);
}
}
const INSPECTION_BYTES: usize = 8192;
let mut file = File::open(path)?;
let mut buf = vec![0u8; INSPECTION_BYTES];
let n = file.read(&mut buf)?;
buf.truncate(n);
Ok(inspect(&buf) != ContentType::BINARY)
}
pub fn serialize_repo(config: &FullYekConfig) -> Result<(String, Vec<ProcessedFile>)> {
let combined_commit_times = config
.input_dirs
.par_iter()
.filter_map(|dir| {
let repo_path = Path::new(dir);
priority::get_recent_commit_times_git2(repo_path)
})
.flatten()
.collect::<HashMap<String, u64>>();
let recentness_boost =
compute_recentness_boost(&combined_commit_times, config.git_boost_max.unwrap_or(100));
let merged_files = config
.input_dirs
.par_iter()
.map(|dir| {
let path = Path::new(dir);
process_files_parallel(path, config, &recentness_boost)
})
.collect::<Result<Vec<Vec<ProcessedFile>>>>()?
.into_iter()
.flatten()
.collect::<Vec<ProcessedFile>>();
let mut files = merged_files;
files.par_sort_by(|a, b| {
a.priority
.cmp(&b.priority)
.reverse()
.then_with(|| a.file_index.cmp(&b.file_index))
});
let output_string = concat_files(files.clone(), config);
write_output(&output_string, config)?;
Ok((output_string, files))
}
fn concat_files(files: Vec<ProcessedFile>, config: &FullYekConfig) -> String {
if config.json {
serde_json::to_string_pretty(
&files
.into_iter()
.map(|f| {
serde_json::json!({
"filename": f.rel_path,
"content": f.content,
})
})
.collect::<Vec<_>>(),
)
.unwrap()
} else {
files
.into_iter()
.map(|f| {
config
.output_template
.replace("FILE_PATH", &f.rel_path)
.replace("FILE_CONTENT", &f.content)
.replace("\\\n", "\n")
})
.collect::<Vec<_>>()
.join("\n")
}
}
fn write_output(content: &str, config: &FullYekConfig) -> io::Result<()> {
if config.stream {
let mut stdout = io::stdout();
stdout.write_all(content.as_bytes())?;
stdout.flush()
} else {
let path = Path::new(&config.output_file_full_path);
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
fs::write(path, content.as_bytes())
}
}