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