anchor_cli/debugger/
path_label.rs1use std::path::{Component, Path, PathBuf};
20
21#[derive(Clone, Debug)]
23pub struct PathLabel {
24 pub label: String,
27 pub path_display: String,
31}
32
33pub fn classify(
40 path: &Path,
41 src_roots: &[PathBuf],
42 path_rewrites: &[(PathBuf, PathBuf)],
43 cwd: Option<&Path>,
44) -> PathLabel {
45 if let Some(cwd) = cwd {
48 if let Some(label) = classify_cwd(path, cwd) {
49 return label;
50 }
51 }
52 if let Some(label) = classify_stdlib(path, path_rewrites) {
53 return label;
54 }
55 if let Some(label) = classify_cargo_registry(path) {
56 return label;
57 }
58 if let Some(label) = classify_cargo_git(path) {
59 return label;
60 }
61 if let Some(label) = classify_workspace(path, src_roots) {
62 return label;
63 }
64 PathLabel {
65 label: "(unknown)".to_owned(),
66 path_display: path.display().to_string(),
67 }
68}
69
70fn classify_cwd(path: &Path, cwd: &Path) -> Option<PathLabel> {
71 let rel = path.strip_prefix(cwd).ok()?;
72 let pkg = enclosing_package_name(cwd, rel);
73 let label = pkg
74 .or_else(|| cwd.file_name().and_then(|n| n.to_str()).map(str::to_owned))
75 .unwrap_or_else(|| "workspace".to_owned());
76 Some(PathLabel {
77 label,
78 path_display: rel.display().to_string(),
79 })
80}
81
82fn classify_stdlib(path: &Path, rewrites: &[(PathBuf, PathBuf)]) -> Option<PathLabel> {
83 for (_, replacement) in rewrites {
84 if let Ok(rel) = path.strip_prefix(replacement) {
85 let mut comps = rel.components();
88 let crate_name = comps.next()?.as_os_str().to_str()?.to_owned();
89 let after_crate: PathBuf = comps.collect();
90 return Some(PathLabel {
91 label: format!("stdlib · {crate_name}"),
92 path_display: after_crate.display().to_string(),
93 });
94 }
95 }
96 None
97}
98
99fn classify_cargo_registry(path: &Path) -> Option<PathLabel> {
100 let cargo_home = cargo_home()?;
101 let registry = cargo_home.join("registry").join("src");
102 let rel = path.strip_prefix(®istry).ok()?;
103 let mut comps = rel.components();
105 let _index = comps.next()?;
106 let pkg_dir = comps.next()?.as_os_str().to_str()?;
107 let rest: PathBuf = comps.collect();
108 let (name, version) = split_name_version(pkg_dir);
109 let label = match version {
110 Some(v) => format!("{name} v{v}"),
111 None => name.to_owned(),
112 };
113 Some(PathLabel {
114 label,
115 path_display: rest.display().to_string(),
116 })
117}
118
119fn classify_cargo_git(path: &Path) -> Option<PathLabel> {
120 let cargo_home = cargo_home()?;
121 let checkouts = cargo_home.join("git").join("checkouts");
122 let rel = path.strip_prefix(&checkouts).ok()?;
123 let mut comps = rel.components();
125 let repo_dir = comps.next()?.as_os_str().to_str()?;
126 let _rev = comps.next();
127 let rest: PathBuf = comps.collect();
128 let repo = repo_dir
132 .rsplit_once('-')
133 .filter(|(_, hash)| hash.len() == 16 && hash.bytes().all(|b| b.is_ascii_hexdigit()))
134 .map(|(name, _)| name)
135 .unwrap_or(repo_dir);
136 Some(PathLabel {
137 label: format!("{repo} (git)"),
138 path_display: rest.display().to_string(),
139 })
140}
141
142fn classify_workspace(path: &Path, src_roots: &[PathBuf]) -> Option<PathLabel> {
143 for root in src_roots {
144 let rel = match path.strip_prefix(root) {
145 Ok(r) => r,
146 Err(_) => continue,
147 };
148 let pkg = enclosing_package_name(root, rel);
149 let label = pkg
150 .or_else(|| root.file_name().and_then(|n| n.to_str()).map(str::to_owned))
151 .unwrap_or_else(|| "workspace".to_owned());
152 return Some(PathLabel {
153 label,
154 path_display: rel.display().to_string(),
155 });
156 }
157 None
158}
159
160fn enclosing_package_name(workspace: &Path, rel: &Path) -> Option<String> {
163 let mut current = workspace.join(rel);
164 while current.pop() {
165 if !current.starts_with(workspace) {
166 return None;
167 }
168 let manifest = current.join("Cargo.toml");
169 if manifest.is_file() {
170 return read_package_name(&manifest);
171 }
172 }
173 None
174}
175
176fn read_package_name(manifest: &Path) -> Option<String> {
180 let contents = std::fs::read_to_string(manifest).ok()?;
181 let mut in_package = false;
182 for line in contents.lines() {
183 let trimmed = line.trim();
184 if trimmed.starts_with('[') {
185 in_package = trimmed == "[package]";
186 continue;
187 }
188 if !in_package {
189 continue;
190 }
191 let Some(rest) = trimmed.strip_prefix("name") else {
193 continue;
194 };
195 let after_eq = rest.trim_start().strip_prefix('=')?.trim_start();
196 let stripped = after_eq.strip_prefix('"')?;
197 let end = stripped.find('"')?;
198 return Some(stripped[..end].to_owned());
199 }
200 None
201}
202
203fn split_name_version(dir_name: &str) -> (&str, Option<&str>) {
210 if let Some((name, version)) = dir_name.rsplit_once('-') {
211 if version.starts_with(|c: char| c.is_ascii_digit()) {
212 return (name, Some(version));
213 }
214 }
215 (dir_name, None)
216}
217
218fn cargo_home() -> Option<PathBuf> {
219 if let Some(p) = std::env::var_os("CARGO_HOME") {
220 return Some(PathBuf::from(p));
221 }
222 dirs::home_dir().map(|h| h.join(".cargo"))
223}
224
225#[allow(dead_code)]
229pub fn canonicalize(path: &Path) -> PathBuf {
230 std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
231}
232
233#[allow(dead_code)]
237pub fn is_src_dir(c: Component<'_>) -> bool {
238 c.as_os_str() == "src"
239}