vx_paths/
resolver.rs

1//! Path resolver for finding and resolving tool paths
2
3use crate::PathManager;
4use anyhow::Result;
5use std::path::PathBuf;
6
7/// Resolves tool paths and finds installed tools
8#[derive(Debug, Clone)]
9pub struct PathResolver {
10    manager: PathManager,
11}
12
13impl PathResolver {
14    /// Create a new PathResolver
15    pub fn new(manager: PathManager) -> Self {
16        Self { manager }
17    }
18
19    /// Find all executable paths for a tool (all versions)
20    pub fn find_tool_executables(&self, tool_name: &str) -> Result<Vec<PathBuf>> {
21        let versions = self.manager.list_tool_versions(tool_name)?;
22        let mut executables = Vec::new();
23
24        for version in versions {
25            let exe_path = self.manager.tool_executable_path(tool_name, &version);
26            if exe_path.exists() {
27                executables.push(exe_path);
28            }
29        }
30
31        Ok(executables)
32    }
33
34    /// Find the latest executable for a tool
35    pub fn find_latest_executable(&self, tool_name: &str) -> Result<Option<PathBuf>> {
36        if let Some(latest_version) = self.manager.get_latest_tool_version(tool_name)? {
37            let exe_path = self
38                .manager
39                .tool_executable_path(tool_name, &latest_version);
40            if exe_path.exists() {
41                return Ok(Some(exe_path));
42            }
43        }
44        Ok(None)
45    }
46
47    /// Find executable for a specific tool version
48    pub fn find_version_executable(&self, tool_name: &str, version: &str) -> Option<PathBuf> {
49        let exe_path = self.manager.tool_executable_path(tool_name, version);
50        if exe_path.exists() {
51            Some(exe_path)
52        } else {
53            None
54        }
55    }
56
57    /// Check if a tool is installed (any version)
58    pub fn is_tool_installed(&self, tool_name: &str) -> Result<bool> {
59        let versions = self.manager.list_tool_versions(tool_name)?;
60        Ok(!versions.is_empty())
61    }
62
63    /// Get all installed tools with their versions
64    pub fn get_installed_tools_with_versions(&self) -> Result<Vec<(String, Vec<String>)>> {
65        let tools = self.manager.list_installed_tools()?;
66        let mut result = Vec::new();
67
68        for tool in tools {
69            let versions = self.manager.list_tool_versions(&tool)?;
70            result.push((tool, versions));
71        }
72
73        Ok(result)
74    }
75
76    /// Resolve tool path with version preference
77    /// If version is specified, try to find that specific version
78    /// Otherwise, return the latest version
79    pub fn resolve_tool_path(
80        &self,
81        tool_name: &str,
82        version: Option<&str>,
83    ) -> Result<Option<PathBuf>> {
84        match version {
85            Some(v) => Ok(self.find_version_executable(tool_name, v)),
86            None => self.find_latest_executable(tool_name),
87        }
88    }
89
90    /// Get the path manager
91    pub fn manager(&self) -> &PathManager {
92        &self.manager
93    }
94}
95
96impl Default for PathResolver {
97    fn default() -> Self {
98        Self::new(PathManager::default())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use tempfile::TempDir;
106
107    #[test]
108    fn test_path_resolver() {
109        let temp_dir = TempDir::new().unwrap();
110        let manager = PathManager::with_base_dir(temp_dir.path()).unwrap();
111        let resolver = PathResolver::new(manager);
112
113        // Initially no tools
114        assert!(!resolver.is_tool_installed("node").unwrap());
115        assert_eq!(
116            resolver.find_tool_executables("node").unwrap(),
117            Vec::<PathBuf>::new()
118        );
119        assert_eq!(resolver.find_latest_executable("node").unwrap(), None);
120
121        // Create a tool installation
122        let _version_dir = resolver
123            .manager()
124            .create_tool_version_dir("node", "18.17.0")
125            .unwrap();
126        let exe_path = resolver.manager().tool_executable_path("node", "18.17.0");
127        std::fs::write(&exe_path, "fake executable").unwrap();
128
129        // Now it should be found
130        assert!(resolver.is_tool_installed("node").unwrap());
131        assert_eq!(
132            resolver.find_tool_executables("node").unwrap(),
133            vec![exe_path.clone()]
134        );
135        assert_eq!(
136            resolver.find_latest_executable("node").unwrap(),
137            Some(exe_path.clone())
138        );
139        assert_eq!(
140            resolver.find_version_executable("node", "18.17.0"),
141            Some(exe_path.clone())
142        );
143        assert_eq!(
144            resolver.resolve_tool_path("node", None).unwrap(),
145            Some(exe_path.clone())
146        );
147        assert_eq!(
148            resolver.resolve_tool_path("node", Some("18.17.0")).unwrap(),
149            Some(exe_path)
150        );
151    }
152}