]> git.lizzy.rs Git - rust.git/blob - crates/stdx/src/lib.rs
Merge #7374
[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 /// ```text
138 ///     ∀ x in slice[..idx]:  pred(x)
139 ///  && ∀ x in slice[idx..]: !pred(x)
140 /// ```
141 ///
142 /// https://github.com/rust-lang/rust/issues/73831
143 pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
144 where
145     P: FnMut(&T) -> bool,
146 {
147     let mut left = 0;
148     let mut right = slice.len();
149
150     while left != right {
151         let mid = left + (right - left) / 2;
152         // SAFETY:
153         // When left < right, left <= mid < right.
154         // Therefore left always increases and right always decreases,
155         // and either of them is selected.
156         // In both cases left <= right is satisfied.
157         // Therefore if left < right in a step,
158         // left <= right is satisfied in the next step.
159         // Therefore as long as left != right, 0 <= left < right <= len is satisfied
160         // and if this case 0 <= mid < len is satisfied too.
161         let value = unsafe { slice.get_unchecked(mid) };
162         if pred(value) {
163             left = mid + 1;
164         } else {
165             right = mid;
166         }
167     }
168
169     left
170 }
171
172 pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
173 where
174     F: FnMut(&T) -> Ordering,
175 {
176     let start = partition_point(slice, |it| key(it) == Ordering::Less);
177     let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
178     start..start + len
179 }
180
181 pub struct JodChild(pub process::Child);
182
183 impl ops::Deref for JodChild {
184     type Target = process::Child;
185     fn deref(&self) -> &process::Child {
186         &self.0
187     }
188 }
189
190 impl ops::DerefMut for JodChild {
191     fn deref_mut(&mut self) -> &mut process::Child {
192         &mut self.0
193     }
194 }
195
196 impl Drop for JodChild {
197     fn drop(&mut self) {
198         let _ = self.0.kill();
199     }
200 }
201
202 #[cfg(test)]
203 mod tests {
204     use super::*;
205
206     #[test]
207     fn test_trim_indent() {
208         assert_eq!(trim_indent(""), "");
209         assert_eq!(
210             trim_indent(
211                 "
212             hello
213             world
214 "
215             ),
216             "hello\nworld\n"
217         );
218         assert_eq!(
219             trim_indent(
220                 "
221             hello
222             world"
223             ),
224             "hello\nworld"
225         );
226         assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
227         assert_eq!(
228             trim_indent(
229                 "
230             fn main() {
231                 return 92;
232             }
233         "
234             ),
235             "fn main() {\n    return 92;\n}\n"
236         );
237     }
238 }