]> git.lizzy.rs Git - rust.git/blob - crates/stdx/src/lib.rs
Merge #8671
[rust.git] / crates / stdx / src / lib.rs
1 //! Missing batteries for standard libraries.
2 use std::{cmp::Ordering, ops, time::Instant};
3
4 mod macros;
5 pub mod process;
6 pub mod panic_context;
7
8 pub use always_assert::{always, never};
9
10 #[inline(always)]
11 pub fn is_ci() -> bool {
12     option_env!("CI").is_some()
13 }
14
15 #[must_use]
16 pub fn timeit(label: &'static str) -> impl Drop {
17     let start = Instant::now();
18     defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
19 }
20
21 /// Prints backtrace to stderr, useful for debugging.
22 #[cfg(feature = "backtrace")]
23 pub fn print_backtrace() {
24     let bt = backtrace::Backtrace::new();
25     eprintln!("{:?}", bt);
26 }
27 #[cfg(not(feature = "backtrace"))]
28 pub fn print_backtrace() {
29     eprintln!(
30         r#"Enable the backtrace feature.
31 Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
32 "#
33     );
34 }
35
36 pub fn to_lower_snake_case(s: &str) -> String {
37     to_snake_case(s, char::to_ascii_lowercase)
38 }
39 pub fn to_upper_snake_case(s: &str) -> String {
40     to_snake_case(s, char::to_ascii_uppercase)
41 }
42 fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
43     let mut buf = String::with_capacity(s.len());
44     let mut prev = false;
45     for c in s.chars() {
46         // `&& prev` is required to not insert `_` before the first symbol.
47         if c.is_ascii_uppercase() && prev {
48             // This check is required to not translate `Weird_Case` into `weird__case`.
49             if !buf.ends_with('_') {
50                 buf.push('_')
51             }
52         }
53         prev = true;
54
55         buf.push(change_case(&c));
56     }
57     buf
58 }
59
60 pub fn replace(buf: &mut String, from: char, to: &str) {
61     if !buf.contains(from) {
62         return;
63     }
64     // FIXME: do this in place.
65     *buf = buf.replace(from, to)
66 }
67
68 // https://github.com/rust-lang/rust/issues/74773
69 pub fn split_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
70     let mut split = haystack.splitn(2, delim);
71     let prefix = split.next()?;
72     let suffix = split.next()?;
73     Some((prefix, suffix))
74 }
75 pub fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
76     let mut split = haystack.rsplitn(2, delim);
77     let suffix = split.next()?;
78     let prefix = split.next()?;
79     Some((prefix, suffix))
80 }
81
82 pub fn trim_indent(mut text: &str) -> String {
83     if text.starts_with('\n') {
84         text = &text[1..];
85     }
86     let indent = text
87         .lines()
88         .filter(|it| !it.trim().is_empty())
89         .map(|it| it.len() - it.trim_start().len())
90         .min()
91         .unwrap_or(0);
92     lines_with_ends(text)
93         .map(
94             |line| {
95                 if line.len() <= indent {
96                     line.trim_start_matches(' ')
97                 } else {
98                     &line[indent..]
99                 }
100             },
101         )
102         .collect()
103 }
104
105 pub fn lines_with_ends(text: &str) -> LinesWithEnds {
106     LinesWithEnds { text }
107 }
108
109 pub struct LinesWithEnds<'a> {
110     text: &'a str,
111 }
112
113 impl<'a> Iterator for LinesWithEnds<'a> {
114     type Item = &'a str;
115     fn next(&mut self) -> Option<&'a str> {
116         if self.text.is_empty() {
117             return None;
118         }
119         let idx = self.text.find('\n').map_or(self.text.len(), |it| it + 1);
120         let (res, next) = self.text.split_at(idx);
121         self.text = next;
122         Some(res)
123     }
124 }
125
126 /// Returns `idx` such that:
127 ///
128 /// ```text
129 ///     ∀ x in slice[..idx]:  pred(x)
130 ///  && ∀ x in slice[idx..]: !pred(x)
131 /// ```
132 ///
133 /// https://github.com/rust-lang/rust/issues/73831
134 pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
135 where
136     P: FnMut(&T) -> bool,
137 {
138     let mut left = 0;
139     let mut right = slice.len();
140
141     while left != right {
142         let mid = left + (right - left) / 2;
143         // SAFETY:
144         // When left < right, left <= mid < right.
145         // Therefore left always increases and right always decreases,
146         // and either of them is selected.
147         // In both cases left <= right is satisfied.
148         // Therefore if left < right in a step,
149         // left <= right is satisfied in the next step.
150         // Therefore as long as left != right, 0 <= left < right <= len is satisfied
151         // and if this case 0 <= mid < len is satisfied too.
152         let value = unsafe { slice.get_unchecked(mid) };
153         if pred(value) {
154             left = mid + 1;
155         } else {
156             right = mid;
157         }
158     }
159
160     left
161 }
162
163 pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
164 where
165     F: FnMut(&T) -> Ordering,
166 {
167     let start = partition_point(slice, |it| key(it) == Ordering::Less);
168     let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
169     start..start + len
170 }
171
172 #[must_use]
173 pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
174     struct D<F: FnOnce()>(Option<F>);
175     impl<F: FnOnce()> Drop for D<F> {
176         fn drop(&mut self) {
177             if let Some(f) = self.0.take() {
178                 f()
179             }
180         }
181     }
182     D(Some(f))
183 }
184
185 #[repr(transparent)]
186 pub struct JodChild(pub std::process::Child);
187
188 impl ops::Deref for JodChild {
189     type Target = std::process::Child;
190     fn deref(&self) -> &std::process::Child {
191         &self.0
192     }
193 }
194
195 impl ops::DerefMut for JodChild {
196     fn deref_mut(&mut self) -> &mut std::process::Child {
197         &mut self.0
198     }
199 }
200
201 impl Drop for JodChild {
202     fn drop(&mut self) {
203         let _ = self.0.kill();
204         let _ = self.0.wait();
205     }
206 }
207
208 impl JodChild {
209     pub fn into_inner(self) -> std::process::Child {
210         // SAFETY: repr transparent
211         unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
212     }
213 }
214
215 #[cfg(test)]
216 mod tests {
217     use super::*;
218
219     #[test]
220     fn test_trim_indent() {
221         assert_eq!(trim_indent(""), "");
222         assert_eq!(
223             trim_indent(
224                 "
225             hello
226             world
227 "
228             ),
229             "hello\nworld\n"
230         );
231         assert_eq!(
232             trim_indent(
233                 "
234             hello
235             world"
236             ),
237             "hello\nworld"
238         );
239         assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
240         assert_eq!(
241             trim_indent(
242                 "
243             fn main() {
244                 return 92;
245             }
246         "
247             ),
248             "fn main() {\n    return 92;\n}\n"
249         );
250     }
251 }