1#![allow(
2 unused,
3 reason = "These are general utilities that I copy-paste between projects"
4)]
5
6use crate::*;
7
8mod visitors {
9 pub mod lifetime;
10}
11pub use visitors::lifetime::*;
12
13#[derive(Copy, Clone)]
24pub struct FullSpan(Span, Span);
25
26impl FullSpan {
27 pub fn from_span(span: Span) -> Self {
28 Self(span, span)
29 }
30 pub fn from_spanned<T: ToTokens>(span: &T) -> Self {
31 let mut tokens = span.to_token_stream().into_iter().map(|t| t.span());
32 let start = tokens.next().unwrap_or(Span::call_site());
33 let end = tokens.last().unwrap_or(start);
34 Self(start, end)
35 }
36 pub fn apply(self, a: TokenStream, b: TokenStream) -> TokenStream {
37 let mut ret = self.apply_start(a);
38 ret.extend(self.apply_end(b));
39 ret
40 }
41 pub fn apply_start(self, a: TokenStream) -> TokenStream {
42 a.with_span(self.0)
43 }
44 pub fn apply_end(self, b: TokenStream) -> TokenStream {
45 b.with_span(self.1)
46 }
47}
48
49pub fn find_closest<'a>(s: &str, compare: &[&'a str]) -> Option<&'a str> {
51 let mut best_confidence = 0.8; let mut best_match = None;
53 for valid in compare {
54 let confidence = strsim::jaro_winkler(s, valid);
55 if confidence > best_confidence {
56 best_confidence = confidence;
57 best_match = Some(*valid);
58 }
59 }
60 best_match
61}
62pub fn take_closest<T: Display>(s: &str, compare: &mut Vec<T>) -> Option<T> {
64 let mut best_confidence = 0.8; let mut best_index = None;
66 for (i, valid) in compare.iter().enumerate() {
67 let confidence = strsim::jaro_winkler(s, &valid.to_string());
68 if confidence > best_confidence {
69 best_confidence = confidence;
70 best_index = Some(i);
71 }
72 }
73 best_index.map(|index| compare.remove(index))
74}
75
76pub fn list_items<T: Display>(items: &[T]) -> String {
78 list_items_with(items, |x| x)
79}
80
81pub fn list_items_quoted<T: Display>(items: &[T], quote: char) -> String {
83 list_items_with(items, |x| format!("{quote}{x}{quote}"))
84}
85
86pub fn list_items_with<'a, T, D: Display + 'a>(
88 items: &'a [T],
89 mut display: impl FnMut(&'a T) -> D,
90) -> String {
91 match items {
92 [] => String::new(),
93 [x] => display(x).to_string(),
94 [a, b] => format!("{} or {}", display(a), display(b)),
95 [start @ .., last] => {
96 use std::fmt::Write;
97 let mut s = String::new();
98 for item in start {
99 write!(s, "{}, ", display(item)).unwrap();
100 }
101 write!(s, "or {}", display(last)).unwrap();
102 s
103 }
104 }
105}
106
107pub trait TokenStreamExt {
109 fn set_span(&mut self, span: Span);
110 fn with_span(self, span: Span) -> Self;
111}
112impl TokenStreamExt for TokenStream {
113 fn set_span(&mut self, span: Span) {
114 let old = std::mem::replace(self, TokenStream::new());
115 *self = old.with_span(span);
116 }
117 fn with_span(self, span: Span) -> Self {
118 self.into_iter()
119 .map(|mut t| {
120 if let proc_macro2::TokenTree::Group(ref mut g) = t {
121 *g = proc_macro2::Group::new(g.delimiter(), g.stream().with_span(span));
122 }
123 t.set_span(span);
124 t
125 })
126 .collect()
127 }
128}
129
130pub trait SpanExt {
134 fn stable_start(&self) -> Span;
135 fn stable_end(&self) -> Span;
136 fn stable_line(&self) -> usize;
137 fn stable_column(&self) -> usize;
138 fn stable_file(&self) -> String;
139}
140#[cfg(not(test))]
141impl SpanExt for Span {
142 fn stable_start(&self) -> Span {
143 self.unwrap().start().into() }
145 fn stable_end(&self) -> Span {
146 self.unwrap().end().into() }
148 fn stable_line(&self) -> usize {
149 self.unwrap().line() }
151 fn stable_column(&self) -> usize {
152 self.unwrap().column() }
154 fn stable_file(&self) -> String {
155 self.unwrap().file()
156 }
157}
158#[cfg(test)]
159impl SpanExt for Span {
160 fn stable_start(&self) -> Span {
161 *self
163 }
164 fn stable_end(&self) -> Span {
165 *self
166 }
167 fn stable_line(&self) -> usize {
168 999
169 }
170 fn stable_column(&self) -> usize {
171 123
172 }
173 fn stable_file(&self) -> String {
174 "/inside/a/test.rs".to_string()
175 }
176}
177
178pub trait ToTokensExt {
179 fn start_span(&self) -> Span;
180 fn end_span(&self) -> Span;
181}
182
183impl<T: ToTokens> ToTokensExt for T {
184 fn start_span(&self) -> Span {
185 self.to_token_stream()
186 .into_iter()
187 .next()
188 .map_or_else(Span::call_site, |t| t.span().stable_start())
189 }
190 fn end_span(&self) -> Span {
191 self.to_token_stream()
192 .into_iter()
193 .last()
194 .map_or_else(Span::call_site, |t| t.span().stable_end())
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[macro_export]
204 macro_rules! assert_panic_message_eq {
205 ( $block:block, $message:literal $(,)? ) => {
206 let Err(error) = std::panic::catch_unwind(move || $block) else {
207 panic!("code {} did not panic", stringify!($block));
208 };
209 if let Some(s) = error.downcast_ref::<&'static str>() {
210 assert_eq!(*s, $message);
211 } else if let Some(s) = error.downcast_ref::<String>() {
212 assert_eq!(s, $message);
213 } else {
214 panic!("unexpected panic payload: {:?}", error);
215 }
216 };
217 ( $expression:expr, $message:literal $(,)? ) => {
218 assert_panic_message_eq!(
219 {
220 $expression; },
222 $message
223 );
224 };
225 ( $statement:stmt, $message:literal $(,)? ) => {
226 assert_panic_message_eq!({ $statement }, $message);
227 };
228 }
229
230 #[test]
231 fn find_closest_basic() {
232 let options = ["apple", "banana", "cherry", "date"];
233
234 assert_eq!(find_closest("appl", &options), Some("apple"));
235 assert_eq!(find_closest("bannana", &options), Some("banana"));
236 assert_eq!(find_closest("cheri", &options), Some("cherry"));
237 assert_eq!(find_closest("dat", &options), Some("date"));
238
239 assert_eq!(find_closest("xyz", &options), None);
240 }
241
242 #[test]
243 fn take_closest_basic() {
244 let mut options = vec![
245 "apple".to_string(),
246 "banana".to_string(),
247 "cherry".to_string(),
248 "date".to_string(),
249 ];
250
251 assert_eq!(
252 take_closest("appl", &mut options),
253 Some("apple".to_string())
254 );
255 assert_eq!(take_closest("appl", &mut options), None); assert_eq!(
258 take_closest("bannana", &mut options),
259 Some("banana".to_string())
260 );
261 assert_eq!(
262 take_closest("cheri", &mut options),
263 Some("cherry".to_string())
264 );
265 assert_eq!(take_closest("dat", &mut options), Some("date".to_string()));
266
267 assert_eq!(take_closest("xyz", &mut options), None);
268 }
269
270 #[test]
271 fn list_items_empty() {
272 let items: [&str; 0] = [];
273 assert_eq!(list_items(&items), "");
274 assert_eq!(list_items_quoted(&items, '"'), "");
275 assert_eq!(list_items_with(&items, |x| format!("{x} ^ {x}")), "");
276 }
277
278 #[test]
279 fn list_items_single() {
280 let items = ["apple"];
281 assert_eq!(list_items(&items), "apple");
282 assert_eq!(list_items_quoted(&items, '`'), "`apple`");
283 assert_eq!(
284 list_items_with(&items, |x| format!("{x} ^ {x}")),
285 "apple ^ apple"
286 );
287 }
288
289 #[test]
290 fn list_items_two() {
291 let items = ["apple", "banana"];
292 assert_eq!(list_items(&items), "apple or banana");
293 assert_eq!(list_items_quoted(&items, '\''), "'apple' or 'banana'");
294 assert_eq!(
295 list_items_with(&items, |x| format!("{x} ^ {x}")),
296 "apple ^ apple or banana ^ banana"
297 );
298 }
299
300 #[test]
301 fn list_items_many() {
302 let items = ["apple", "banana", "cherry"];
303 assert_eq!(list_items(&items), "apple, banana, or cherry");
304 assert_eq!(
305 list_items_quoted(&items, '"'),
306 "\"apple\", \"banana\", or \"cherry\""
307 );
308 assert_eq!(
309 list_items_with(&items, |x| format!("{x} ^ {x}")),
310 "apple ^ apple, banana ^ banana, or cherry ^ cherry"
311 );
312
313 let items = ["apple", "banana", "cherry", "date"];
314 assert_eq!(list_items(&items), "apple, banana, cherry, or date");
315 assert_eq!(
316 list_items_quoted(&items, '`'),
317 "`apple`, `banana`, `cherry`, or `date`"
318 );
319 assert_eq!(
320 list_items_with(&items, |x| format!("{x} ^ {x}")),
321 "apple ^ apple, banana ^ banana, cherry ^ cherry, or date ^ date"
322 );
323 }
324}