use crate::types::{Config, DirectoryResolution};
use anyhow::{anyhow, Context, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::PathBuf;
use std::sync::Mutex;
static REGEX_CACHE: Lazy<Mutex<HashMap<String, Regex>>> = Lazy::new(|| Mutex::new(HashMap::new()));
fn get_cached_regex(pattern: &str) -> Result<Regex> {
let mut cache = REGEX_CACHE.lock()
.expect("regex cache mutex should not be poisoned");
if let Some(regex) = cache.get(pattern) {
return Ok(regex.clone());
}
let regex = Regex::new(pattern).context("Failed to compile regex pattern")?;
cache.insert(pattern.to_string(), regex.clone());
Ok(regex)
}
pub fn resolve_directory(config: &Config, alias: &str) -> Result<DirectoryResolution> {
let directory_path = config.semantic_directories.get(alias)
.ok_or_else(|| anyhow!("Directory alias '{}' not found", alias))?;
let expanded_path = expand_path(directory_path)?;
let canonical_path = fs::canonicalize(&expanded_path)
.with_context(|| format!("Failed to resolve path: {}", expanded_path.display()))?;
Ok(DirectoryResolution {
canonical_path: canonical_path.to_string_lossy().to_string(),
alias_used: alias.to_string(),
variables_substituted: Vec::new(),
})
}
pub fn detect_directory_references(config: &Config, text: &str) -> Vec<DirectoryResolution> {
let mut results = Vec::new();
for alias in config.semantic_directories.keys() {
let alias_pattern = format!(r"(^|\s)({})(\s|$)", regex::escape(alias));
if let Ok(regex) = get_cached_regex(&alias_pattern) {
if regex.is_match(text) {
if let Ok(resolution) = resolve_directory(config, alias) {
results.push(resolution);
}
}
}
}
results.sort_by(|a, b| a.canonical_path.cmp(&b.canonical_path));
results.dedup_by(|a, b| a.canonical_path == b.canonical_path);
results
}
fn expand_path(path: &str) -> Result<PathBuf> {
if path.starts_with('~') {
let home_dir = env::var("HOME")
.with_context(|| "Failed to get HOME environment variable")?;
let expanded = path.replacen('~', &home_dir, 1);
Ok(PathBuf::from(expanded))
} else {
Ok(PathBuf::from(path))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn create_test_config() -> Config {
let mut semantic_directories = HashMap::new();
semantic_directories.insert("docs".to_string(), "~/Documents/Documentation".to_string());
semantic_directories.insert("project_docs".to_string(), "~/Documents/Documentation/project".to_string());
Config {
commands: HashMap::new(),
semantic_directories,
command_history: None,
}
}
#[test]
fn test_expand_path() {
env::set_var("HOME", "/home/testuser");
let result = expand_path("~/Documents").unwrap();
assert_eq!(result, PathBuf::from("/home/testuser/Documents"));
let result = expand_path("/absolute/path").unwrap();
assert_eq!(result, PathBuf::from("/absolute/path"));
}
#[test]
fn test_detect_directory_references() {
let config = create_test_config();
let text = "Please check the docs directory for examples";
let results = detect_directory_references(&config, text);
assert!(results.len() <= 1); }
#[test]
fn test_directory_alias_matching_patterns() {
let config = create_test_config();
let text = "check the project docs directory";
let results = detect_directory_references(&config, text);
assert!(results.len() <= 1, "Should detect at most one alias match");
let no_fuzzy_match = "check documentation folder";
let results2 = detect_directory_references(&config, &no_fuzzy_match);
assert_eq!(results2.len(), 0, "Should not fuzzy-match 'documentation' to 'docs'");
}
}