Skip to main content

vs_core/service/
list.rs

1//! Services for listing installed runtimes.
2
3use std::fs;
4
5use crate::{App, CoreError, InstalledVersion};
6
7impl App {
8    /// Lists installed tool versions in the local cache.
9    pub fn list_installed_versions(&self) -> Result<Vec<InstalledVersion>, CoreError> {
10        let cache_root = self.home().join("cache");
11        if !cache_root.exists() {
12            return Ok(Vec::new());
13        }
14        let mut installed = Vec::new();
15
16        for plugin_entry in fs::read_dir(cache_root)? {
17            let plugin_entry = plugin_entry?;
18            if !plugin_entry.file_type()?.is_dir() {
19                continue;
20            }
21            let plugin_name = match plugin_entry.file_name().into_string() {
22                Ok(name) => name,
23                Err(_) => continue,
24            };
25            let versions_root = plugin_entry.path().join("versions");
26            if !versions_root.exists() {
27                continue;
28            }
29            for version_entry in fs::read_dir(&versions_root)? {
30                let version_entry = version_entry?;
31                if !version_entry.file_type()?.is_dir() {
32                    continue;
33                }
34                if let Ok(version) = version_entry.file_name().into_string() {
35                    installed.push(InstalledVersion {
36                        plugin: plugin_name.clone(),
37                        version,
38                        install_dir: version_entry.path(),
39                    });
40                }
41            }
42        }
43
44        installed.sort_by(|left, right| {
45            left.plugin
46                .cmp(&right.plugin)
47                .then(compare_versions_desc(&left.version, &right.version))
48        });
49        Ok(installed)
50    }
51}
52
53fn compare_versions_desc(left: &str, right: &str) -> std::cmp::Ordering {
54    compare_version_components(left, right).reverse()
55}
56
57fn compare_version_components(left: &str, right: &str) -> std::cmp::Ordering {
58    let left_parts = version_components(left);
59    let right_parts = version_components(right);
60    for (left_part, right_part) in left_parts.iter().zip(right_parts.iter()) {
61        let ordering = match (left_part.parse::<u64>(), right_part.parse::<u64>()) {
62            (Ok(left_num), Ok(right_num)) => left_num.cmp(&right_num),
63            _ => left_part.cmp(right_part),
64        };
65        if ordering != std::cmp::Ordering::Equal {
66            return ordering;
67        }
68    }
69    left_parts.len().cmp(&right_parts.len())
70}
71
72fn version_components(version: &str) -> Vec<&str> {
73    version
74        .trim_start_matches('v')
75        .split(|ch: char| !ch.is_ascii_alphanumeric())
76        .filter(|part| !part.is_empty())
77        .collect()
78}