cargo_whatfeatures/
registry.rs1use anyhow::Context as _;
2use std::{collections::HashSet, path::PathBuf};
3
4use crate::features::Workspace;
5
6pub struct Registry {
8 cached: HashSet<Crate>,
9 local: HashSet<Crate>,
10}
11
12impl Registry {
13 pub fn from_local() -> anyhow::Result<Self> {
15 use crate_version_parse::CrateVersion;
16
17 let home = home::cargo_home()?
19 .join("registry")
20 .join("src")
21 .read_dir()
22 .with_context(|| "expected to have a local registry")?;
23
24 let (mut set, mut local) = (HashSet::new(), HashSet::new());
25
26 for path in home
27 .filter_map(|dir| dir.ok()?.path().read_dir().ok())
28 .flat_map(|dir| dir.flatten())
29 .map(|s| s.path())
30 {
31 if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
32 let CrateVersion { name, version } = CrateVersion::try_parse(name)?;
33 set.insert(Crate {
34 name: name.to_string(),
35 version: version.to_string(),
36 path,
37 yanked: YankState::UnknownLocal, });
39 }
40 }
41
42 if let Ok(base) = crate::util::cache_dir() {
44 for dir in base
46 .read_dir()
47 .into_iter()
48 .flat_map(|dir| dir.flatten())
49 .filter_map(|dir| {
50 let path = dir.path();
51 if !path.is_dir() {
52 return None;
53 }
54 path.into()
55 })
56 {
57 let name = dir.strip_prefix(&base)?.to_str().expect("valid utf-8");
58 let CrateVersion { name, version } = CrateVersion::try_parse(name)?;
59 let crate_ = Crate {
60 name: name.to_string(),
61 version: version.to_string(),
62 path: dir.clone(),
63 yanked: YankState::UnknownLocal, };
65
66 if set.contains(&crate_) {
67 std::fs::remove_dir_all(dir)?;
69 } else {
70 local.insert(crate_);
71 }
72 }
73 }
74
75 Ok(Self { cached: set, local })
76 }
77
78 pub fn get(&self, crate_name: &str, crate_version: &str) -> Option<&Crate> {
80 self.cached
81 .iter()
82 .chain(self.local.iter())
83 .find(|Crate { name, version, .. }| name == crate_name && version == crate_version)
84 }
85
86 pub fn maybe_latest(&self, crate_name: &str) -> Option<&Crate> {
88 self.cached
89 .iter()
90 .chain(self.local.iter())
91 .filter(|Crate { name, .. }| name == crate_name)
92 .max_by(|Crate { version: left, .. }, Crate { version: right, .. }| left.cmp(&right))
93 }
94
95 pub fn purge_local_cache(&mut self) -> anyhow::Result<usize> {
97 let mut count = 0;
98 for crate_ in self.local.drain() {
99 std::fs::remove_dir_all(&crate_.path)?;
100 count += 1;
101 }
102 Ok(count)
103 }
104}
105
106#[derive(Copy, Clone, Debug, PartialEq, Eq)]
108pub enum YankState {
109 Yanked,
111 UnknownLocal,
114 Available,
116}
117
118impl From<bool> for YankState {
119 fn from(yanked: bool) -> Self {
120 if yanked {
121 Self::Yanked
122 } else {
123 Self::Available
124 }
125 }
126}
127
128#[derive(Clone, Debug, Eq)]
130pub struct Crate {
131 pub name: String,
133 pub version: String,
135 pub path: PathBuf,
137 pub yanked: YankState,
139}
140
141impl Crate {
142 pub fn get_features(&self) -> anyhow::Result<Workspace> {
144 cargo_metadata::MetadataCommand::new()
145 .no_deps()
146 .manifest_path(self.path.join("./Cargo.toml"))
147 .exec()
148 .map(|md| Workspace::parse(md, &self.name))
149 .map_err(Into::into)
150 }
151
152 pub fn from_local(path: impl Into<PathBuf>) -> anyhow::Result<Workspace> {
154 let path = path.into();
155
156 if let Some(file_name) = path.file_name() {
157 anyhow::ensure!(
158 file_name == "Cargo.toml",
159 "Path must be a directory or 'Cargo.toml'"
160 );
161 anyhow::ensure!(path.is_file(), "invalid manifest path");
162 } else {
163 anyhow::ensure!(path.join("Cargo.toml").is_file(), "invalid manifest path");
164 }
165
166 let name = path
167 .iter()
168 .last()
169 .unwrap_or_else(|| path.as_ref())
170 .to_string_lossy();
171
172 cargo_metadata::MetadataCommand::new()
173 .current_dir(&path)
174 .no_deps()
175 .exec()
176 .map(|md| Workspace::parse(md, &name))
177 .map(|mut ws| {
178 ws.map.retain(|k, _| {
179 k.repr
180 .splitn(2, ' ')
181 .next()
182 .filter(|&s| s == name)
183 .is_some()
184 });
185 ws
186 })
187 .map_err(Into::into)
188 }
189
190 pub fn from_path(path: impl Into<PathBuf>) -> anyhow::Result<Workspace> {
192 let path = path.into();
193
194 fn find_parent(mut path: PathBuf) -> PathBuf {
195 while !path.is_dir() {
196 if !path.pop() {
197 break;
198 }
199 }
200
201 if !path.is_dir() {
203 let mut path = PathBuf::new();
204 path.push(".");
205 return path;
206 }
207
208 path
209 }
210
211 let name = find_parent(std::fs::canonicalize(path.clone())?);
212 let name = name
213 .iter()
214 .last()
215 .unwrap_or_else(|| path.as_ref())
216 .to_string_lossy();
217
218 let path = find_parent(path.clone());
219
220 cargo_metadata::MetadataCommand::new()
221 .current_dir(&path)
222 .no_deps()
223 .exec()
224 .map(|md| Workspace::parse(md, &name))
225 .map_err(Into::into)
226 }
227}
228
229impl PartialEq for Crate {
230 fn eq(&self, other: &Self) -> bool {
231 self.name == other.name && self.version == other.version
232 }
233}
234
235impl std::hash::Hash for Crate {
236 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
237 state.write(self.name.as_bytes());
238 state.write(self.version.as_bytes());
239 }
240}