]> git.lizzy.rs Git - rust.git/blob - crates/stdx/src/lib.rs
Remove proc macro management thread
[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     let start = Instant::now();
18     defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
19 }
20
21 /// Prints backtrace to stderr, useful for debugging.
22 #[cfg(feature = "backtrace")]
23 pub fn print_backtrace() {
24     let bt = backtrace::Backtrace::new();
25     eprintln!("{:?}", bt);
26 }
27 #[cfg(not(feature = "backtrace"))]
28 pub fn print_backtrace() {
29     eprintln!(
30         r#"Enable the backtrace feature.
31 Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
32 "#
33     );
34 }
35
36 pub fn to_lower_snake_case(s: &str) -> String {
37     to_snake_case(s, char::to_ascii_lowercase)
38 }
39 pub fn to_upper_snake_case(s: &str) -> String {
40     to_snake_case(s, char::to_ascii_uppercase)
41 }
42 fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
43     let mut buf = String::with_capacity(s.len());
44     let mut prev = false;
45     for c in s.chars() {
46         // `&& prev` is required to not insert `_` before the first symbol.
47         if c.is_ascii_uppercase() && prev {
48             // This check is required to not translate `Weird_Case` into `weird__case`.
49             if !buf.ends_with('_') {
50                 buf.push('_')
51             }
52         }
53         prev = true;
54
55         buf.push(change_case(&c));
56     }
57     buf
58 }
59
60 pub fn replace(buf: &mut String, from: char, to: &str) {
61     if !buf.contains(from) {
62         return;
63     }
64     // FIXME: do this in place.
65     *buf = buf.replace(from, to)
66 }
67
68 pub fn trim_indent(mut text: &str) -> String {
69     if text.starts_with('\n') {
70         text = &text[1..];
71     }
72     let indent = text
73         .lines()
74         .filter(|it| !it.trim().is_empty())
75         .map(|it| it.len() - it.trim_start().len())
76         .min()
77         .unwrap_or(0);
78     text.split_inclusive('\n')
79         .map(
80             |line| {
81                 if line.len() <= indent {
82                     line.trim_start_matches(' ')
83                 } else {
84                     &line[indent..]
85                 }
86             },
87         )
88         .collect()
89 }
90
91 pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
92 where
93     F: FnMut(&T) -> Ordering,
94 {
95     let start = slice.partition_point(|it| key(it) == Ordering::Less);
96     let len = slice[start..].partition_point(|it| key(it) == Ordering::Equal);
97     start..start + len
98 }
99
100 #[must_use]
101 pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
102     struct D<F: FnOnce()>(Option<F>);
103     impl<F: FnOnce()> Drop for D<F> {
104         fn drop(&mut self) {
105             if let Some(f) = self.0.take() {
106                 f()
107             }
108         }
109     }
110     D(Some(f))
111 }
112
113 #[cfg_attr(not(target_arch = "wasm32"), repr(transparent))]
114 #[derive(Debug)]
115 pub struct JodChild(pub std::process::Child);
116
117 impl ops::Deref for JodChild {
118     type Target = std::process::Child;
119     fn deref(&self) -> &std::process::Child {
120         &self.0
121     }
122 }
123
124 impl ops::DerefMut for JodChild {
125     fn deref_mut(&mut self) -> &mut std::process::Child {
126         &mut self.0
127     }
128 }
129
130 impl Drop for JodChild {
131     fn drop(&mut self) {
132         let _ = self.0.kill();
133         let _ = self.0.wait();
134     }
135 }
136
137 impl JodChild {
138     pub fn into_inner(self) -> std::process::Child {
139         if cfg!(target_arch = "wasm32") {
140             panic!("no processes on wasm");
141         }
142         // SAFETY: repr transparent, except on WASM
143         unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
144     }
145 }
146
147 // feature: iter_order_by
148 // Iterator::eq_by
149 pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool
150 where
151     I: IntoIterator,
152     I2: IntoIterator,
153     F: FnMut(I2::Item, I::Item) -> bool,
154 {
155     let mut other = other.into_iter();
156     let mut this = this.into_iter();
157
158     loop {
159         let x = match this.next() {
160             None => return other.next().is_none(),
161             Some(val) => val,
162         };
163
164         let y = match other.next() {
165             None => return false,
166             Some(val) => val,
167         };
168
169         if !eq(x, y) {
170             return false;
171         }
172     }
173 }
174
175 #[cfg(test)]
176 mod tests {
177     use super::*;
178
179     #[test]
180     fn test_trim_indent() {
181         assert_eq!(trim_indent(""), "");
182         assert_eq!(
183             trim_indent(
184                 "
185             hello
186             world
187 "
188             ),
189             "hello\nworld\n"
190         );
191         assert_eq!(
192             trim_indent(
193                 "
194             hello
195             world"
196             ),
197             "hello\nworld"
198         );
199         assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
200         assert_eq!(
201             trim_indent(
202                 "
203             fn main() {
204                 return 92;
205             }
206         "
207             ),
208             "fn main() {\n    return 92;\n}\n"
209         );
210     }
211 }