#![warn(missing_docs, clippy::print_stdout, clippy::print_stderr)]
use std::{fs::File, path::Path};
use git2::Repository;
use ignore::WalkBuilder;
pub mod source;
pub mod tag;
pub use source::{SourceFile, SourceKind};
pub use tag::{Tag, TagKind, TagLevel};
#[derive(Debug, Clone, Copy)]
pub struct SearchOptions {
pub git_ignore: bool,
pub git_blame: bool,
}
impl SearchOptions {
pub fn no_git() -> Self {
Self {
git_ignore: false,
git_blame: false,
}
}
}
impl Default for SearchOptions {
fn default() -> Self {
Self {
git_ignore: true,
git_blame: true,
}
}
}
pub fn search_files<P: AsRef<Path>>(
path: P,
search_options: SearchOptions,
) -> impl Iterator<Item = Tag> {
let repository = open_inside_repository(&path);
let SearchOptions {
git_ignore,
git_blame,
} = search_options;
WalkBuilder::new(&path)
.git_ignore(git_ignore)
.ignore(true)
.hidden(true)
.build()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_some_and(|ft| ft.is_file()))
.filter_map(move |e| {
let kind = SourceKind::identify(e.path())?;
let Ok(file) = File::open(e.path()) else {
return None;
};
Some(SourceFile::new(kind, e.path(), file))
})
.flatten()
.map(move |mut tag| {
if git_blame {
if let Some(repo) = &repository {
tag.git_info = tag.get_blame_info(path.as_ref(), repo);
}
}
tag
})
}
fn open_inside_repository<P: AsRef<Path>>(path: P) -> Option<Repository> {
let path = path.as_ref().canonicalize().ok()?;
let mut p = path.as_path();
loop {
if let Ok(repo) = Repository::open(p) {
return Some(repo);
}
p = p.parent()?;
}
}