normalize_package_index/index/
nimble.rs1use super::{IndexError, PackageIndex, PackageMeta, VersionMeta};
12use crate::cache;
13use std::time::Duration;
14
15const INDEX_CACHE_TTL: Duration = Duration::from_secs(60 * 60);
17
18pub struct Nimble;
20
21impl Nimble {
22 const PACKAGES_URL: &'static str =
24 "https://raw.githubusercontent.com/nim-lang/packages/master/packages.json";
25
26 fn fetch_packages_list() -> Result<Vec<serde_json::Value>, IndexError> {
28 let (data, _was_cached) = cache::fetch_with_cache(
29 "nimble",
30 "packages-all",
31 Self::PACKAGES_URL,
32 INDEX_CACHE_TTL,
33 )
34 .map_err(IndexError::Network)?;
35
36 let packages: Vec<serde_json::Value> = serde_json::from_slice(&data)?;
37 Ok(packages)
38 }
39}
40
41impl PackageIndex for Nimble {
42 fn ecosystem(&self) -> &'static str {
43 "nimble"
44 }
45
46 fn display_name(&self) -> &'static str {
47 "Nimble (Nim)"
48 }
49
50 fn fetch(&self, name: &str) -> Result<PackageMeta, IndexError> {
51 let packages = Self::fetch_packages_list()?;
52
53 let pkg = packages
54 .iter()
55 .find(|p| p["name"].as_str().map(|n| n.to_lowercase()) == Some(name.to_lowercase()))
56 .ok_or_else(|| IndexError::NotFound(name.to_string()))?;
57
58 Ok(pkg_to_meta(pkg))
59 }
60
61 fn fetch_versions(&self, name: &str) -> Result<Vec<VersionMeta>, IndexError> {
62 let packages = Self::fetch_packages_list()?;
65
66 let _pkg = packages
67 .iter()
68 .find(|p| p["name"].as_str().map(|n| n.to_lowercase()) == Some(name.to_lowercase()))
69 .ok_or_else(|| IndexError::NotFound(name.to_string()))?;
70
71 Ok(vec![VersionMeta {
73 version: "latest".to_string(),
74 released: None,
75 yanked: false,
76 }])
77 }
78
79 fn supports_fetch_all(&self) -> bool {
80 true
81 }
82
83 fn fetch_all(&self) -> Result<Vec<PackageMeta>, IndexError> {
84 let packages = Self::fetch_packages_list()?;
85 Ok(packages.iter().map(pkg_to_meta).collect())
86 }
87
88 fn search(&self, query: &str) -> Result<Vec<PackageMeta>, IndexError> {
89 let packages = Self::fetch_packages_list()?;
90 let query_lower = query.to_lowercase();
91
92 Ok(packages
93 .iter()
94 .filter(|pkg| {
95 let name = pkg["name"].as_str().unwrap_or("");
96 let desc = pkg["description"].as_str().unwrap_or("");
97 let tags = pkg["tags"]
98 .as_array()
99 .map(|t| {
100 t.iter()
101 .filter_map(|v| v.as_str())
102 .collect::<Vec<_>>()
103 .join(" ")
104 })
105 .unwrap_or_default();
106
107 name.to_lowercase().contains(&query_lower)
108 || desc.to_lowercase().contains(&query_lower)
109 || tags.to_lowercase().contains(&query_lower)
110 })
111 .take(50)
112 .map(pkg_to_meta)
113 .collect())
114 }
115}
116
117fn pkg_to_meta(pkg: &serde_json::Value) -> PackageMeta {
118 let url = pkg["url"].as_str().unwrap_or("");
119
120 PackageMeta {
121 name: pkg["name"].as_str().unwrap_or("").to_string(),
122 version: "latest".to_string(), description: pkg["description"].as_str().map(String::from),
124 homepage: pkg["web"].as_str().map(String::from),
125 repository: if url.contains("github.com") || url.contains("gitlab.com") {
126 Some(url.to_string())
127 } else {
128 None
129 },
130 license: pkg["license"].as_str().map(String::from),
131 binaries: Vec::new(),
132 keywords: pkg["tags"]
133 .as_array()
134 .map(|t| {
135 t.iter()
136 .filter_map(|v| v.as_str().map(String::from))
137 .collect()
138 })
139 .unwrap_or_default(),
140 maintainers: Vec::new(),
141 published: None,
142 downloads: None,
143 archive_url: Some(url.to_string()),
144 checksum: None,
145 extra: Default::default(),
146 }
147}