1 //! Missing batteries for standard libraries.
2 use std::iter;
3 use std::{cmp::Ordering, ops, time::Instant};
4 
5 mod macros;
6 pub mod process;
7 pub mod panic_context;
8 
9 pub use always_assert::{always, never};
10 
11 #[inline(always)]
is_ci() -> bool12 pub fn is_ci() -> bool {
13     option_env!("CI").is_some()
14 }
15 
16 #[must_use]
timeit(label: &'static str) -> impl Drop17 pub fn timeit(label: &'static str) -> impl Drop {
18     let start = Instant::now();
19     defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
20 }
21 
22 /// Prints backtrace to stderr, useful for debugging.
print_backtrace()23 pub fn print_backtrace() {
24     #[cfg(feature = "backtrace")]
25     eprintln!("{:?}", backtrace::Backtrace::new());
26 
27     #[cfg(not(feature = "backtrace"))]
28     eprintln!(
29         r#"Enable the backtrace feature.
30 Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
31 "#
32     );
33 }
34 
to_lower_snake_case(s: &str) -> String35 pub fn to_lower_snake_case(s: &str) -> String {
36     to_snake_case(s, char::to_ascii_lowercase)
37 }
to_upper_snake_case(s: &str) -> String38 pub fn to_upper_snake_case(s: &str) -> String {
39     to_snake_case(s, char::to_ascii_uppercase)
40 }
41 
42 // Code partially taken from rust/compiler/rustc_lint/src/nonstandard_style.rs
43 // commit: 9626f2b
to_snake_case<F: Fn(&char) -> char>(mut s: &str, change_case: F) -> String44 fn to_snake_case<F: Fn(&char) -> char>(mut s: &str, change_case: F) -> String {
45     let mut words = vec![];
46 
47     // Preserve leading underscores
48     s = s.trim_start_matches(|c: char| {
49         if c == '_' {
50             words.push(String::new());
51             true
52         } else {
53             false
54         }
55     });
56 
57     for s in s.split('_') {
58         let mut last_upper = false;
59         let mut buf = String::new();
60 
61         if s.is_empty() {
62             continue;
63         }
64 
65         for ch in s.chars() {
66             if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
67                 words.push(buf);
68                 buf = String::new();
69             }
70 
71             last_upper = ch.is_uppercase();
72             buf.extend(iter::once(change_case(&ch)));
73         }
74 
75         words.push(buf);
76     }
77 
78     words.join("_")
79 }
80 
replace(buf: &mut String, from: char, to: &str)81 pub fn replace(buf: &mut String, from: char, to: &str) {
82     if !buf.contains(from) {
83         return;
84     }
85     // FIXME: do this in place.
86     *buf = buf.replace(from, to);
87 }
88 
trim_indent(mut text: &str) -> String89 pub fn trim_indent(mut text: &str) -> String {
90     if text.starts_with('\n') {
91         text = &text[1..];
92     }
93     let indent = text
94         .lines()
95         .filter(|it| !it.trim().is_empty())
96         .map(|it| it.len() - it.trim_start().len())
97         .min()
98         .unwrap_or(0);
99     text.split_inclusive('\n')
100         .map(
101             |line| {
102                 if line.len() <= indent {
103                     line.trim_start_matches(' ')
104                 } else {
105                     &line[indent..]
106                 }
107             },
108         )
109         .collect()
110 }
111 
equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize> where F: FnMut(&T) -> Ordering,112 pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
113 where
114     F: FnMut(&T) -> Ordering,
115 {
116     let start = slice.partition_point(|it| key(it) == Ordering::Less);
117     let len = slice[start..].partition_point(|it| key(it) == Ordering::Equal);
118     start..start + len
119 }
120 
121 #[must_use]
defer<F: FnOnce()>(f: F) -> impl Drop122 pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
123     struct D<F: FnOnce()>(Option<F>);
124     impl<F: FnOnce()> Drop for D<F> {
125         fn drop(&mut self) {
126             if let Some(f) = self.0.take() {
127                 f();
128             }
129         }
130     }
131     D(Some(f))
132 }
133 
134 #[cfg_attr(not(target_arch = "wasm32"), repr(transparent))]
135 #[derive(Debug)]
136 pub struct JodChild(pub std::process::Child);
137 
138 impl ops::Deref for JodChild {
139     type Target = std::process::Child;
deref(&self) -> &std::process::Child140     fn deref(&self) -> &std::process::Child {
141         &self.0
142     }
143 }
144 
145 impl ops::DerefMut for JodChild {
deref_mut(&mut self) -> &mut std::process::Child146     fn deref_mut(&mut self) -> &mut std::process::Child {
147         &mut self.0
148     }
149 }
150 
151 impl Drop for JodChild {
drop(&mut self)152     fn drop(&mut self) {
153         let _ = self.0.kill();
154         let _ = self.0.wait();
155     }
156 }
157 
158 impl JodChild {
into_inner(self) -> std::process::Child159     pub fn into_inner(self) -> std::process::Child {
160         if cfg!(target_arch = "wasm32") {
161             panic!("no processes on wasm");
162         }
163         // SAFETY: repr transparent, except on WASM
164         unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
165     }
166 }
167 
168 // feature: iter_order_by
169 // Iterator::eq_by
iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool where I: IntoIterator, I2: IntoIterator, F: FnMut(I2::Item, I::Item) -> bool,170 pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool
171 where
172     I: IntoIterator,
173     I2: IntoIterator,
174     F: FnMut(I2::Item, I::Item) -> bool,
175 {
176     let mut other = other.into_iter();
177     let mut this = this.into_iter();
178 
179     loop {
180         let x = match this.next() {
181             None => return other.next().is_none(),
182             Some(val) => val,
183         };
184 
185         let y = match other.next() {
186             None => return false,
187             Some(val) => val,
188         };
189 
190         if !eq(x, y) {
191             return false;
192         }
193     }
194 }
195 
196 /// Returns all final segments of the argument, longest first.
slice_tails<T>(this: &[T]) -> impl Iterator<Item = &[T]>197 pub fn slice_tails<T>(this: &[T]) -> impl Iterator<Item = &[T]> {
198     (0..this.len()).map(|i| &this[i..])
199 }
200 
201 #[cfg(test)]
202 mod tests {
203     use super::*;
204 
205     #[test]
test_trim_indent()206     fn test_trim_indent() {
207         assert_eq!(trim_indent(""), "");
208         assert_eq!(
209             trim_indent(
210                 "
211             hello
212             world
213 "
214             ),
215             "hello\nworld\n"
216         );
217         assert_eq!(
218             trim_indent(
219                 "
220             hello
221             world"
222             ),
223             "hello\nworld"
224         );
225         assert_eq!(trim_indent("    hello\n    world\n"), "hello\nworld\n");
226         assert_eq!(
227             trim_indent(
228                 "
229             fn main() {
230                 return 92;
231             }
232         "
233             ),
234             "fn main() {\n    return 92;\n}\n"
235         );
236     }
237 }
238