]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/stdx/src/lib.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / stdx / src / lib.rs
1 //! Missing batteries for standard libraries.
2
3 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
4
5 use std::process::Command;
6 use std::{cmp::Ordering, ops, time::Instant};
7 use std::{io as sio, iter};
8
9 mod macros;
10 pub mod process;
11 pub mod panic_context;
12 pub mod non_empty_vec;
13
14 pub use always_assert::{always, never};
15
16 #[inline(always)]
17 pub fn is_ci() -> bool {
18 option_env!("CI").is_some()
19 }
20
21 #[must_use]
22 pub fn timeit(label: &'static str) -> impl Drop {
23 let start = Instant::now();
24 defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
25 }
26
27 /// Prints backtrace to stderr, useful for debugging.
28 pub fn print_backtrace() {
29 #[cfg(feature = "backtrace")]
30 eprintln!("{:?}", backtrace::Backtrace::new());
31
32 #[cfg(not(feature = "backtrace"))]
33 eprintln!(
34 r#"Enable the backtrace feature.
35 Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
36 "#
37 );
38 }
39
40 pub fn to_lower_snake_case(s: &str) -> String {
41 to_snake_case(s, char::to_ascii_lowercase)
42 }
43 pub fn to_upper_snake_case(s: &str) -> String {
44 to_snake_case(s, char::to_ascii_uppercase)
45 }
46
47 // Code partially taken from rust/compiler/rustc_lint/src/nonstandard_style.rs
48 // commit: 9626f2b
49 fn to_snake_case<F: Fn(&char) -> char>(mut s: &str, change_case: F) -> String {
50 let mut words = vec![];
51
52 // Preserve leading underscores
53 s = s.trim_start_matches(|c: char| {
54 if c == '_' {
55 words.push(String::new());
56 true
57 } else {
58 false
59 }
60 });
61
62 for s in s.split('_') {
63 let mut last_upper = false;
64 let mut buf = String::new();
65
66 if s.is_empty() {
67 continue;
68 }
69
70 for ch in s.chars() {
71 if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
72 words.push(buf);
73 buf = String::new();
74 }
75
76 last_upper = ch.is_uppercase();
77 buf.extend(iter::once(change_case(&ch)));
78 }
79
80 words.push(buf);
81 }
82
83 words.join("_")
84 }
85
86 pub fn replace(buf: &mut String, from: char, to: &str) {
87 if !buf.contains(from) {
88 return;
89 }
90 // FIXME: do this in place.
91 *buf = buf.replace(from, to);
92 }
93
94 pub fn trim_indent(mut text: &str) -> String {
95 if text.starts_with('\n') {
96 text = &text[1..];
97 }
98 let indent = text
99 .lines()
100 .filter(|it| !it.trim().is_empty())
101 .map(|it| it.len() - it.trim_start().len())
102 .min()
103 .unwrap_or(0);
104 text.split_inclusive('\n')
105 .map(
106 |line| {
107 if line.len() <= indent {
108 line.trim_start_matches(' ')
109 } else {
110 &line[indent..]
111 }
112 },
113 )
114 .collect()
115 }
116
117 pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
118 where
119 F: FnMut(&T) -> Ordering,
120 {
121 let start = slice.partition_point(|it| key(it) == Ordering::Less);
122 let len = slice[start..].partition_point(|it| key(it) == Ordering::Equal);
123 start..start + len
124 }
125
126 #[must_use]
127 pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
128 struct D<F: FnOnce()>(Option<F>);
129 impl<F: FnOnce()> Drop for D<F> {
130 fn drop(&mut self) {
131 if let Some(f) = self.0.take() {
132 f();
133 }
134 }
135 }
136 D(Some(f))
137 }
138
139 /// A [`std::process::Child`] wrapper that will kill the child on drop.
140 #[cfg_attr(not(target_arch = "wasm32"), repr(transparent))]
141 #[derive(Debug)]
142 pub struct JodChild(pub std::process::Child);
143
144 impl ops::Deref for JodChild {
145 type Target = std::process::Child;
146 fn deref(&self) -> &std::process::Child {
147 &self.0
148 }
149 }
150
151 impl ops::DerefMut for JodChild {
152 fn deref_mut(&mut self) -> &mut std::process::Child {
153 &mut self.0
154 }
155 }
156
157 impl Drop for JodChild {
158 fn drop(&mut self) {
159 let _ = self.0.kill();
160 let _ = self.0.wait();
161 }
162 }
163
164 impl JodChild {
165 pub fn spawn(mut command: Command) -> sio::Result<Self> {
166 command.spawn().map(Self)
167 }
168
169 pub fn into_inner(self) -> std::process::Child {
170 if cfg!(target_arch = "wasm32") {
171 panic!("no processes on wasm");
172 }
173 // SAFETY: repr transparent, except on WASM
174 unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
175 }
176 }
177
178 // feature: iter_order_by
179 // Iterator::eq_by
180 pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool
181 where
182 I: IntoIterator,
183 I2: IntoIterator,
184 F: FnMut(I2::Item, I::Item) -> bool,
185 {
186 let mut other = other.into_iter();
187 let mut this = this.into_iter();
188
189 loop {
190 let x = match this.next() {
191 None => return other.next().is_none(),
192 Some(val) => val,
193 };
194
195 let y = match other.next() {
196 None => return false,
197 Some(val) => val,
198 };
199
200 if !eq(x, y) {
201 return false;
202 }
203 }
204 }
205
206 /// Returns all final segments of the argument, longest first.
207 pub fn slice_tails<T>(this: &[T]) -> impl Iterator<Item = &[T]> {
208 (0..this.len()).map(|i| &this[i..])
209 }
210
211 #[cfg(test)]
212 mod tests {
213 use super::*;
214
215 #[test]
216 fn test_trim_indent() {
217 assert_eq!(trim_indent(""), "");
218 assert_eq!(
219 trim_indent(
220 "
221 hello
222 world
223 "
224 ),
225 "hello\nworld\n"
226 );
227 assert_eq!(
228 trim_indent(
229 "
230 hello
231 world"
232 ),
233 "hello\nworld"
234 );
235 assert_eq!(trim_indent(" hello\n world\n"), "hello\nworld\n");
236 assert_eq!(
237 trim_indent(
238 "
239 fn main() {
240 return 92;
241 }
242 "
243 ),
244 "fn main() {\n return 92;\n}\n"
245 );
246 }
247 }