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