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