standarbuild-detect 0.1.0

Detect project kind (Rust, Node, Bun, Deno, Python, Lua, C/C++) and scan polyglot monorepo workspaces
Documentation
//! Single-directory detection: look at the files present in one directory and
//! infer its [`ProjectKind`] + the signal filenames that matched.

use std::path::Path;

use crate::kind::ProjectKind;
use crate::signals::SIGNAL_FILES;

pub fn detect_in_dir(dir: &Path) -> (ProjectKind, Vec<String>) {
	let mut signals = Vec::new();
	if !dir.is_dir() {
		return (ProjectKind::Unknown, signals);
	}

	for sf in SIGNAL_FILES {
		if dir.join(sf.name).exists() {
			signals.push(sf.name.to_string());
		}
	}

	let has = |name: &str| signals.iter().any(|s| s == name);

	let kind = if has("Cargo.toml") {
		ProjectKind::Rust
	} else if has("bun.lock") || has("bun.lockb") || has("bunfig.toml") {
		ProjectKind::Bun
	} else if has("deno.json") || has("deno.jsonc") || has("deno.lock") {
		ProjectKind::Deno
	} else if has("package.json") {
		ProjectKind::Node
	} else if has("pyproject.toml")
		|| has("requirements.txt")
		|| has("setup.py")
		|| has("setup.cfg")
	{
		ProjectKind::Python
	} else if has(".luarc.json") || has_any_extension(dir, "rockspec") {
		ProjectKind::Lua
	} else if has("CMakeLists.txt") {
		if has_any_extension(dir, "cpp") || has_any_extension(dir, "cc") || has_any_extension(dir, "cxx") {
			ProjectKind::Cpp
		} else if has_any_extension(dir, "c") {
			ProjectKind::C
		} else {
			ProjectKind::Unknown
		}
	} else {
		ProjectKind::Unknown
	};

	(kind, signals)
}

fn has_any_extension(dir: &Path, ext: &str) -> bool {
	let Ok(read) = std::fs::read_dir(dir) else {
		return false;
	};
	for entry in read.flatten() {
		if entry.path().extension().map(|e| e == ext).unwrap_or(false) {
			return true;
		}
	}
	false
}