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