]> git.lizzy.rs Git - rust.git/blob - crates/stdx/src/lib.rs
Apply suggestions from code review
[rust.git] / crates / stdx / src / lib.rs
1 //! Missing batteries for standard libraries.
2 use std::{
3     sync::atomic::{AtomicUsize, Ordering},
4     time::Instant,
5 };
6
7 mod macros;
8 pub mod panic_context;
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 pub fn to_lower_snake_case(s: &str) -> String {
32     let mut buf = String::with_capacity(s.len());
33     let mut prev = false;
34     for c in s.chars() {
35         // `&& prev` is required to not insert `_` before the first symbol.
36         if c.is_ascii_uppercase() && prev {
37             // This check is required to not translate `Weird_Case` into `weird__case`.
38             if !buf.ends_with('_') {
39                 buf.push('_')
40             }
41         }
42         prev = true;
43
44         buf.push(c.to_ascii_lowercase());
45     }
46     buf
47 }
48
49 pub fn replace(buf: &mut String, from: char, to: &str) {
50     if !buf.contains(from) {
51         return;
52     }
53     // FIXME: do this in place.
54     *buf = buf.replace(from, to)
55 }
56
57 // https://github.com/rust-lang/rust/issues/74773
58 pub fn split_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
59     let mut split = haystack.splitn(2, delim);
60     let prefix = split.next()?;
61     let suffix = split.next()?;
62     Some((prefix, suffix))
63 }
64 pub fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
65     let mut split = haystack.rsplitn(2, delim);
66     let suffix = split.next()?;
67     let prefix = split.next()?;
68     Some((prefix, suffix))
69 }
70
71 pub fn trim_indent(mut text: &str) -> String {
72     if text.starts_with('\n') {
73         text = &text[1..];
74     }
75     let indent = text
76         .lines()
77         .filter(|it| !it.trim().is_empty())
78         .map(|it| it.len() - it.trim_start().len())
79         .min()
80         .unwrap_or(0);
81     lines_with_ends(text)
82         .map(
83             |line| {
84                 if line.len() <= indent {
85                     line.trim_start_matches(' ')
86                 } else {
87                     &line[indent..]
88                 }
89             },
90         )
91         .collect()
92 }
93
94 pub fn lines_with_ends(text: &str) -> LinesWithEnds {
95     LinesWithEnds { text }
96 }
97
98 pub struct LinesWithEnds<'a> {
99     text: &'a str,
100 }
101
102 impl<'a> Iterator for LinesWithEnds<'a> {
103     type Item = &'a str;
104     fn next(&mut self) -> Option<&'a str> {
105         if self.text.is_empty() {
106             return None;
107         }
108         let idx = self.text.find('\n').map_or(self.text.len(), |it| it + 1);
109         let (res, next) = self.text.split_at(idx);
110         self.text = next;
111         Some(res)
112     }
113 }
114
115 // https://github.com/rust-lang/rust/issues/73831
116 pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
117 where
118     P: FnMut(&T) -> bool,
119 {
120     let mut left = 0;
121     let mut right = slice.len();
122
123     while left != right {
124         let mid = left + (right - left) / 2;
125         // SAFETY:
126         // When left < right, left <= mid < right.
127         // Therefore left always increases and right always decreases,
128         // and either of them is selected.
129         // In both cases left <= right is satisfied.
130         // Therefore if left < right in a step,
131         // left <= right is satisfied in the next step.
132         // Therefore as long as left != right, 0 <= left < right <= len is satisfied
133         // and if this case 0 <= mid < len is satisfied too.
134         let value = unsafe { slice.get_unchecked(mid) };
135         if pred(value) {
136             left = mid + 1;
137         } else {
138             right = mid;
139         }
140     }
141
142     left
143 }
144
145 pub struct RacyFlag(AtomicUsize);
146
147 impl RacyFlag {
148     pub const fn new() -> RacyFlag {
149         RacyFlag(AtomicUsize::new(!0))
150     }
151
152     pub fn get(&self, init: impl FnMut() -> bool) -> bool {
153         let mut init = Some(init);
154         self.get_impl(&mut || init.take().map_or(false, |mut f| f()))
155     }
156
157     fn get_impl(&self, init: &mut dyn FnMut() -> bool) -> bool {
158         match self.0.load(Ordering::Relaxed) {
159             0 => false,
160             1 => true,
161             _ => {
162                 let res = init();
163                 self.0.store(if res { 1 } else { 0 }, Ordering::Relaxed);
164                 res
165             }
166         }
167     }
168 }
169
170 #[cfg(test)]
171 mod tests {
172     use super::*;
173
174     #[test]
175     fn test_trim_indent() {
176         assert_eq!(trim_indent(""), "");
177         assert_eq!(
178             trim_indent(
179                 "
180             hello
181             world
182 "
183             ),
184             "hello\nworld\n"
185         );
186         assert_eq!(
187             trim_indent(
188                 "
189             hello
190             world"
191             ),
192             "hello\nworld"
193         );
194         assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
195         assert_eq!(
196             trim_indent(
197                 "
198             fn main() {
199                 return 92;
200             }
201         "
202             ),
203             "fn main() {\n    return 92;\n}\n"
204         );
205     }
206 }