use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use ignore::WalkBuilder;
use regex::Regex;
pub const CUSTOM_IGNORE_FILE_NAME: &str = ".cmakefmtignore";
#[derive(Debug, Clone, Default)]
pub struct DiscoveryOptions<'a> {
pub file_filter: Option<&'a Regex>,
pub honor_gitignore: bool,
pub explicit_ignore_paths: &'a [PathBuf],
}
pub fn discover_cmake_files(root: &Path, file_filter: Option<&Regex>) -> Vec<PathBuf> {
discover_cmake_files_with_options(
root,
DiscoveryOptions {
file_filter,
honor_gitignore: false,
explicit_ignore_paths: &[],
},
)
}
pub fn discover_cmake_files_with_options(
root: &Path,
options: DiscoveryOptions<'_>,
) -> Vec<PathBuf> {
let mut builder = WalkBuilder::new(root);
builder.hidden(false);
builder.git_ignore(options.honor_gitignore);
builder.git_global(options.honor_gitignore);
builder.git_exclude(options.honor_gitignore);
builder.require_git(false);
builder.add_custom_ignore_filename(CUSTOM_IGNORE_FILE_NAME);
for ignore_path in options.explicit_ignore_paths {
builder.add_ignore(ignore_path);
}
let mut files: Vec<_> = builder
.build()
.filter_map(Result::ok)
.filter(|entry| entry.file_type().is_some_and(|kind| kind.is_file()))
.map(|entry| entry.into_path())
.filter(|path| is_cmake_file(path))
.filter(|path| matches_filter(path, options.file_filter))
.collect();
files.sort();
files
}
pub fn is_cmake_file(path: &Path) -> bool {
let Some(file_name) = path.file_name().and_then(OsStr::to_str) else {
return false;
};
if file_name == "CMakeLists.txt" {
return true;
}
file_name.ends_with(".cmake") || file_name.ends_with(".cmake.in")
}
pub fn matches_filter(path: &Path, file_filter: Option<&Regex>) -> bool {
let Some(file_filter) = file_filter else {
return true;
};
file_filter.is_match(&path.to_string_lossy())
}