use anyhow::Result;
use camino::Utf8PathBuf;
use ignore::WalkBuilder;
#[derive(Debug, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct FileTypes {
pub has_ruby: bool,
pub has_typescript: bool,
pub has_golang: bool,
pub has_rust: bool,
pub has_python: bool,
pub has_configs: bool,
}
pub fn read_source_file(path: &std::path::Path) -> Option<String> {
std::fs::read_to_string(path).ok()
}
pub fn collect_source_files(path: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut files = Vec::new();
if path.is_file() {
files.push(path.to_path_buf());
return files;
}
let walker = WalkBuilder::new(path)
.standard_filters(true)
.require_git(false)
.build();
for entry in walker.flatten() {
if entry.file_type().is_some_and(|ft| ft.is_file()) {
files.push(entry.into_path());
}
}
files
}
pub fn collect_all_files(paths: &[Utf8PathBuf]) -> Result<Vec<Utf8PathBuf>> {
let mut files = Vec::new();
for path in paths {
for p in collect_source_files(path.as_std_path()) {
let utf8 = Utf8PathBuf::from_path_buf(p)
.map_err(|p| anyhow::anyhow!("non-utf8 path: {}", p.display()))?;
files.push(utf8);
}
}
Ok(files)
}
pub fn detect_file_types(files: &[Utf8PathBuf]) -> FileTypes {
let mut types = FileTypes::default();
for file in files {
if let Some(filename) = file.file_name() {
match filename {
"Cargo.toml"
| "build.rs"
| "rust-toolchain"
| "rust-toolchain.toml"
| ".rustfmt.toml"
| "rustfmt.toml"
| "clippy.toml"
| ".clippy.toml" => {
types.has_rust = true;
continue;
}
"Gemfile" | "Rakefile" | ".rubocop.yml" | ".ruby-version" => {
types.has_ruby = true;
continue;
}
"pyproject.toml" | "setup.py" | "requirements.txt" | ".python-version" => {
types.has_python = true;
continue;
}
"go.mod" | "go.sum" => {
types.has_golang = true;
continue;
}
"tsconfig.json" | "package.json" | "biome.json" | "biome.jsonc"
| ".eslintrc.json" => {
types.has_typescript = true;
continue;
}
_ => {}
}
}
match file.extension() {
Some("rb" | "rake") => types.has_ruby = true,
Some("ts" | "tsx" | "js" | "jsx" | "mjs" | "cjs") => types.has_typescript = true,
Some("go") => types.has_golang = true,
Some("rs") => types.has_rust = true,
Some("py") => types.has_python = true,
Some("md" | "mdx") => types.has_configs = true,
_ => {}
}
}
types
}
#[cfg(test)]
mod tests {
use super::*;
fn path(name: &str) -> Utf8PathBuf {
Utf8PathBuf::from(name)
}
#[test]
fn detects_mjs_and_cjs_as_typescript() {
let files = vec![path("web/app.mjs"), path("cli/tools.cjs")];
let types = detect_file_types(&files);
assert!(types.has_typescript);
assert!(!types.has_ruby && !types.has_golang && !types.has_rust && !types.has_python);
}
#[test]
fn detects_markdown_as_config() {
let files = vec![path("content/post.md"), path("content/page.mdx")];
let types = detect_file_types(&files);
assert!(types.has_configs);
}
}