use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::Path;
#[derive(Clone)]
pub struct CommandIndexer {
commands: Vec<String>,
}
impl CommandIndexer {
pub fn new() -> Self {
let mut indexer = CommandIndexer {
commands: Vec::new(),
};
indexer.scan_path();
indexer
}
fn scan_path(&mut self) {
let mut seen = HashSet::new();
if let Ok(path_var) = env::var("PATH") {
for dir in path_var.split(':') {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
if let Ok(file_name) = entry.file_name().into_string() {
if self.is_executable(&entry.path()) && !seen.contains(&file_name) {
seen.insert(file_name.clone());
self.commands.push(file_name);
}
}
}
}
}
}
self.commands.sort();
}
#[cfg(unix)]
fn is_executable(&self, path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
if let Ok(metadata) = fs::metadata(path) {
let permissions = metadata.permissions();
permissions.mode() & 0o111 != 0
} else {
false
}
}
#[cfg(not(unix))]
fn is_executable(&self, path: &Path) -> bool {
if let Some(ext) = path.extension() {
matches!(
ext.to_str().unwrap_or("").to_lowercase().as_str(),
"exe" | "bat" | "cmd" | "com"
)
} else {
false
}
}
pub fn get_commands(&self) -> &[String] {
&self.commands
}
#[allow(dead_code)]
pub fn count(&self) -> usize {
self.commands.len()
}
}
impl Default for CommandIndexer {
fn default() -> Self {
Self::new()
}
}