use std::process::Command;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ToolType {
Fd,
Ripgrep,
Walkdir,
}
impl ToolType {
#[must_use]
pub const fn command_name(&self) -> &'static str {
match self {
Self::Fd => "fd",
Self::Ripgrep => "rg",
Self::Walkdir => "walkdir",
}
}
#[must_use]
pub fn is_available(&self) -> bool {
match self {
Self::Walkdir => true, Self::Fd | Self::Ripgrep => Command::new(self.command_name())
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false),
}
}
}
impl std::fmt::Display for ToolType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fd => write!(f, "fd"),
Self::Ripgrep => write!(f, "ripgrep"),
Self::Walkdir => write!(f, "walkdir"),
}
}
}
#[derive(Debug, Error)]
pub enum ToolError {
#[error("No discovery tools available")]
NoToolsAvailable,
#[error("Tool execution failed for {tool}: {message}")]
ExecutionFailed {
tool: String,
message: String,
},
}
#[must_use]
pub fn detect_tool() -> ToolType {
let tools = [ToolType::Fd, ToolType::Ripgrep, ToolType::Walkdir];
for tool in tools {
if tool.is_available() {
return tool;
}
}
ToolType::Walkdir
}
#[must_use]
pub fn available_tools() -> Vec<ToolType> {
[ToolType::Fd, ToolType::Ripgrep, ToolType::Walkdir]
.iter()
.filter(|t| t.is_available())
.copied()
.collect()
}
#[must_use]
pub fn resolve_alias(alias: &str) -> Option<ToolType> {
match alias {
"fd" | "fdfind" | "find" => Some(ToolType::Fd),
"rg" | "ripgrep" => Some(ToolType::Ripgrep),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tooltype_command_names() {
assert_eq!(ToolType::Fd.command_name(), "fd");
assert_eq!(ToolType::Ripgrep.command_name(), "rg");
assert_eq!(ToolType::Walkdir.command_name(), "walkdir");
}
#[test]
fn test_walkdir_always_available() {
assert!(ToolType::Walkdir.is_available());
}
#[test]
fn test_detect_tool_returns_something() {
let tool = detect_tool();
assert!(tool == ToolType::Fd || tool == ToolType::Ripgrep || tool == ToolType::Walkdir);
}
#[test]
fn test_available_tools_not_empty() {
let tools = available_tools();
assert!(!tools.is_empty());
assert!(tools.contains(&ToolType::Walkdir));
}
#[test]
fn test_resolve_alias_fd() {
assert_eq!(resolve_alias("fd"), Some(ToolType::Fd));
assert_eq!(resolve_alias("fdfind"), Some(ToolType::Fd));
assert_eq!(resolve_alias("find"), Some(ToolType::Fd));
}
#[test]
fn test_resolve_alias_ripgrep() {
assert_eq!(resolve_alias("rg"), Some(ToolType::Ripgrep));
assert_eq!(resolve_alias("ripgrep"), Some(ToolType::Ripgrep));
}
#[test]
fn test_resolve_alias_unknown() {
assert_eq!(resolve_alias("unknown"), None);
assert_eq!(resolve_alias(""), None);
}
#[test]
fn test_tooltype_display() {
assert_eq!(format!("{}", ToolType::Fd), "fd");
assert_eq!(format!("{}", ToolType::Ripgrep), "ripgrep");
assert_eq!(format!("{}", ToolType::Walkdir), "walkdir");
}
#[test]
fn test_tooltype_ord() {
assert!(ToolType::Fd < ToolType::Ripgrep);
assert!(ToolType::Ripgrep < ToolType::Walkdir);
assert!(ToolType::Fd < ToolType::Walkdir);
}
#[test]
fn test_tooltype_partial_eq() {
assert_eq!(ToolType::Fd, ToolType::Fd);
assert_ne!(ToolType::Fd, ToolType::Ripgrep);
}
}