1#![allow(unused)]
2
3use std::io::{IsTerminal as _, Write as _};
4use std::sync::LazyLock;
5use std::{env, fmt, fs, io, path};
6
7use color_eyre::eyre::{self, ContextCompat};
8use rustc_hir::def_id::DefId;
9use rustc_middle::ty::TyCtxt;
10use serde::Serialize;
11use tracing::{debug, instrument};
12use tracing_subscriber::EnvFilter;
13use walkdir::WalkDir;
14
15use crate::consts;
16
17static TRACING_INIT: LazyLock<()> = LazyLock::new(|| {
18 let no_color = env::var("NO_ANSI").ok().is_some_and(|val| val == "1");
19 let enable_ansi = !no_color || io::stdout().is_terminal();
20
21 tracing_subscriber::fmt()
22 .with_env_filter(EnvFilter::from_default_env())
23 .with_writer(io::stderr)
24 .with_ansi(enable_ansi)
25 .init();
26});
27
28pub fn init_tracing() {
29 let () = &*TRACING_INIT;
30}
31
32pub fn expect_one<T: fmt::Debug>(vs: Vec<T>) -> eyre::Result<T> {
33 eyre::ensure!(
34 vs.len() == 1,
35 "expected one item, found either none or multiple (vs: {vs:?})"
36 );
37
38 let fst = vs.into_iter().next().expect("already checked");
39 Ok(fst)
40}
41
42pub fn is_bin_on_path(bin: &str) -> eyre::Result<bool> {
43 if let Ok(paths) = env::var("PATH") {
44 for dir in paths.split(':') {
45 let full_path = path::Path::new(dir).join(bin);
46 if full_path.try_exists()? {
47 return Ok(true);
48 }
49 }
50 }
51 Ok(false)
52}
53
54#[instrument]
55pub(crate) fn copy_dir<P, Q>(from: P, to: Q) -> eyre::Result<()>
56where
57 P: AsRef<path::Path> + fmt::Debug,
58 Q: AsRef<path::Path> + fmt::Debug,
59{
60 let to = to.as_ref();
61 if to.exists() {
62 debug!("Removing existing {}", to.display());
63 fs::remove_dir_all(to)?;
64 }
65
66 for entry in WalkDir::new(&from) {
67 let entry = entry?;
68 let f = entry.path();
69 let t = to.join(f.strip_prefix(&from)?);
70
71 if entry.file_type().is_dir() {
72 fs::create_dir(t)?;
74 } else if entry.file_type().is_file() {
75 fs::copy(f, t)?;
77 } else {
78 let target = entry.path().read_link()?;
80 std::os::unix::fs::symlink(target, t)?;
81 }
82 }
83
84 Ok(())
85}
86
87pub fn should_save_intermediates() -> bool {
88 *consts::SAVE_INTERMEDIATES
89}
90
91pub fn save_intermediate<T: Serialize>(data: T, fname: &str) {
92 let intermediates_dir = env::current_dir()
93 .expect("failed to get current_dir")
94 .join(&*consts::INTERMEDIATES_DIR);
95 fs::create_dir_all(&intermediates_dir).expect("failed to create intermediates dir");
96
97 let fpath = intermediates_dir.join(fname);
98
99 save_to_json_file(&data, fpath).expect("failed to write to json file");
100}
101
102pub fn save_to_json_file<T>(value: &T, filename: impl AsRef<path::Path>) -> eyre::Result<()>
103where
104 T: ?Sized + Serialize,
105{
106 let file = fs::File::create(filename.as_ref())?;
107 let mut writer = io::BufWriter::new(file);
109 serde_json::to_writer_pretty(&mut writer, value)?;
110 writer.flush()?;
111 Ok(())
112}
113
114pub fn intermediates_dir() -> Option<impl AsRef<path::Path>> {
115 let intermediates_dir = env::current_dir()
116 .expect("failed to get current_dir")
117 .join(&*consts::INTERMEDIATES_DIR);
118
119 if intermediates_dir.is_dir() {
120 Some(intermediates_dir)
121 } else {
122 debug!(
123 "[!] intermediates_dir ({}) is not a dir",
124 intermediates_dir.display()
125 );
126 None
127 }
128}
129
130pub fn def_kind_descr(tcx: TyCtxt<'_>, def_id: DefId) -> &'static str {
131 let def_kind = tcx.def_kind(def_id);
132 tcx.def_kind_descr(def_kind, def_id)
133}
134
135pub fn remove_cratenum(path: &str) -> eyre::Result<String> {
136 if !path.contains('[') && !path.contains(']') {
137 return Ok(path.to_string());
138 }
139
140 let (crate_, rest) = path
141 .split_once('[')
142 .wrap_err_with(|| "'[' not found in path")?;
143
144 let (_, rest_path) = rest
145 .split_once(']')
146 .wrap_err_with(|| "']' not found in path")?;
147
148 let path = format!("{crate_}{rest_path}");
149 Ok(path)
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_remove_cratenum_prim_ty() {
158 let input = "u32";
159 let result = remove_cratenum(input).unwrap();
160 assert_eq!(result, "u32");
161 }
162
163 #[test]
164 fn test_remove_cratenum_valid_path() {
165 let input = "my_crate[123]::module::function";
166 let result = remove_cratenum(input).unwrap();
167 assert_eq!(result, "my_crate::module::function");
168 }
169
170 #[test]
171 fn test_remove_cratenum_with_zero() {
172 let input = "std[0]::collections::HashMap";
173 let result = remove_cratenum(input).unwrap();
174 assert_eq!(result, "std::collections::HashMap");
175 }
176
177 #[test]
178 fn test_remove_cratenum_missing_opening_bracket() {
179 let input = "my_crate123]::module::function";
180 let result = remove_cratenum(input);
181 assert!(result.is_err());
182 assert!(
183 result
184 .unwrap_err()
185 .to_string()
186 .contains("'[' not found in path")
187 );
188 }
189
190 #[test]
191 fn test_remove_cratenum_missing_closing_bracket() {
192 let input = "my_crate[123::module::function";
193 let result = remove_cratenum(input);
194 assert!(result.is_err());
195 assert!(
196 result
197 .unwrap_err()
198 .to_string()
199 .contains("']' not found in path")
200 );
201 }
202
203 #[test]
204 fn test_remove_cratenum_empty_brackets() {
205 let input = "my_crate[]::module::function";
206 let result = remove_cratenum(input).unwrap();
207 assert_eq!(result, "my_crate::module::function");
208 }
209}