1pub mod arc_cow;
2pub mod fs;
3pub mod github;
4pub mod http;
5pub mod paths;
6mod semantic_version;
7#[cfg(any(test, feature = "test-support"))]
8pub mod test;
9
10pub use backtrace::Backtrace;
11use futures::Future;
12use lazy_static::lazy_static;
13use rand::{seq::SliceRandom, Rng};
14pub use semantic_version::SemanticVersion;
15use std::{
16 borrow::Cow,
17 cmp::{self, Ordering},
18 env,
19 ops::{AddAssign, Range, RangeInclusive},
20 panic::Location,
21 pin::Pin,
22 task::{Context, Poll},
23 time::Instant,
24};
25
26pub use take_until::*;
27
28#[macro_export]
29macro_rules! debug_panic {
30 ( $($fmt_arg:tt)* ) => {
31 if cfg!(debug_assertions) {
32 panic!( $($fmt_arg)* );
33 } else {
34 let backtrace = $crate::Backtrace::new();
35 log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace);
36 }
37 };
38}
39
40pub fn truncate(s: &str, max_chars: usize) -> &str {
41 match s.char_indices().nth(max_chars) {
42 None => s,
43 Some((idx, _)) => &s[..idx],
44 }
45}
46
47/// Removes characters from the end of the string if its length is greater than `max_chars` and
48/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.
49pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {
50 debug_assert!(max_chars >= 5);
51
52 let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars);
53 match truncation_ix {
54 Some(length) => s[..length].to_string() + "…",
55 None => s.to_string(),
56 }
57}
58
59/// Removes characters from the front of the string if its length is greater than `max_chars` and
60/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars.
61pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String {
62 debug_assert!(max_chars >= 5);
63
64 let truncation_ix = s.char_indices().map(|(i, _)| i).nth_back(max_chars);
65 match truncation_ix {
66 Some(length) => "…".to_string() + &s[length..],
67 None => s.to_string(),
68 }
69}
70
71pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
72 let prev = *value;
73 *value += T::from(1);
74 prev
75}
76
77/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
78/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
79/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
80pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
81where
82 I: IntoIterator<Item = T>,
83 F: FnMut(&T, &T) -> Ordering,
84{
85 let mut start_index = 0;
86 for new_item in new_items {
87 if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) {
88 let index = start_index + i;
89 if vec.len() < limit {
90 vec.insert(index, new_item);
91 } else if index < vec.len() {
92 vec.pop();
93 vec.insert(index, new_item);
94 }
95 start_index = index;
96 }
97 }
98}
99
100pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
101 use serde_json::Value;
102
103 match (source, target) {
104 (Value::Object(source), Value::Object(target)) => {
105 for (key, value) in source {
106 if let Some(target) = target.get_mut(&key) {
107 merge_json_value_into(value, target);
108 } else {
109 target.insert(key.clone(), value);
110 }
111 }
112 }
113
114 (source, target) => *target = source,
115 }
116}
117
118pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
119 use serde_json::Value;
120 if let Value::Object(source_object) = source {
121 let target_object = if let Value::Object(target) = target {
122 target
123 } else {
124 *target = Value::Object(Default::default());
125 target.as_object_mut().unwrap()
126 };
127 for (key, value) in source_object {
128 if let Some(target) = target_object.get_mut(&key) {
129 merge_non_null_json_value_into(value, target);
130 } else if !value.is_null() {
131 target_object.insert(key.clone(), value);
132 }
133 }
134 } else if !source.is_null() {
135 *target = source
136 }
137}
138
139pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
140 lazy_static! {
141 pub static ref ZED_MEASUREMENTS: bool = env::var("ZED_MEASUREMENTS")
142 .map(|measurements| measurements == "1" || measurements == "true")
143 .unwrap_or(false);
144 }
145
146 if *ZED_MEASUREMENTS {
147 let start = Instant::now();
148 let result = f();
149 let elapsed = start.elapsed();
150 eprintln!("{}: {:?}", label, elapsed);
151 result
152 } else {
153 f()
154 }
155}
156
157pub trait ResultExt<E> {
158 type Ok;
159
160 fn log_err(self) -> Option<Self::Ok>;
161 /// Assert that this result should never be an error in development or tests.
162 fn debug_assert_ok(self, reason: &str) -> Self;
163 fn warn_on_err(self) -> Option<Self::Ok>;
164 fn inspect_error(self, func: impl FnOnce(&E)) -> Self;
165}
166
167impl<T, E> ResultExt<E> for Result<T, E>
168where
169 E: std::fmt::Debug,
170{
171 type Ok = T;
172
173 #[track_caller]
174 fn log_err(self) -> Option<T> {
175 match self {
176 Ok(value) => Some(value),
177 Err(error) => {
178 let caller = Location::caller();
179 log::error!("{}:{}: {:?}", caller.file(), caller.line(), error);
180 None
181 }
182 }
183 }
184
185 #[track_caller]
186 fn debug_assert_ok(self, reason: &str) -> Self {
187 if let Err(error) = &self {
188 debug_panic!("{reason} - {error:?}");
189 }
190 self
191 }
192
193 fn warn_on_err(self) -> Option<T> {
194 match self {
195 Ok(value) => Some(value),
196 Err(error) => {
197 log::warn!("{:?}", error);
198 None
199 }
200 }
201 }
202
203 /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
204 fn inspect_error(self, func: impl FnOnce(&E)) -> Self {
205 if let Err(err) = &self {
206 func(err);
207 }
208
209 self
210 }
211}
212
213pub trait TryFutureExt {
214 fn log_err(self) -> LogErrorFuture<Self>
215 where
216 Self: Sized;
217
218 fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
219 where
220 Self: Sized;
221
222 fn warn_on_err(self) -> LogErrorFuture<Self>
223 where
224 Self: Sized;
225 fn unwrap(self) -> UnwrapFuture<Self>
226 where
227 Self: Sized;
228}
229
230impl<F, T, E> TryFutureExt for F
231where
232 F: Future<Output = Result<T, E>>,
233 E: std::fmt::Debug,
234{
235 #[track_caller]
236 fn log_err(self) -> LogErrorFuture<Self>
237 where
238 Self: Sized,
239 {
240 let location = Location::caller();
241 LogErrorFuture(self, log::Level::Error, *location)
242 }
243
244 fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
245 where
246 Self: Sized,
247 {
248 LogErrorFuture(self, log::Level::Error, location)
249 }
250
251 #[track_caller]
252 fn warn_on_err(self) -> LogErrorFuture<Self>
253 where
254 Self: Sized,
255 {
256 let location = Location::caller();
257 LogErrorFuture(self, log::Level::Warn, *location)
258 }
259
260 fn unwrap(self) -> UnwrapFuture<Self>
261 where
262 Self: Sized,
263 {
264 UnwrapFuture(self)
265 }
266}
267
268#[must_use]
269pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
270
271impl<F, T, E> Future for LogErrorFuture<F>
272where
273 F: Future<Output = Result<T, E>>,
274 E: std::fmt::Debug,
275{
276 type Output = Option<T>;
277
278 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
279 let level = self.1;
280 let location = self.2;
281 let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
282 match inner.poll(cx) {
283 Poll::Ready(output) => Poll::Ready(match output {
284 Ok(output) => Some(output),
285 Err(error) => {
286 log::log!(
287 level,
288 "{}:{}: {:?}",
289 location.file(),
290 location.line(),
291 error
292 );
293 None
294 }
295 }),
296 Poll::Pending => Poll::Pending,
297 }
298 }
299}
300
301pub struct UnwrapFuture<F>(F);
302
303impl<F, T, E> Future for UnwrapFuture<F>
304where
305 F: Future<Output = Result<T, E>>,
306 E: std::fmt::Debug,
307{
308 type Output = T;
309
310 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
311 let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
312 match inner.poll(cx) {
313 Poll::Ready(result) => Poll::Ready(result.unwrap()),
314 Poll::Pending => Poll::Pending,
315 }
316 }
317}
318
319pub struct Deferred<F: FnOnce()>(Option<F>);
320
321impl<F: FnOnce()> Deferred<F> {
322 /// Drop without running the deferred function.
323 pub fn abort(mut self) {
324 self.0.take();
325 }
326}
327
328impl<F: FnOnce()> Drop for Deferred<F> {
329 fn drop(&mut self) {
330 if let Some(f) = self.0.take() {
331 f()
332 }
333 }
334}
335
336/// Run the given function when the returned value is dropped (unless it's cancelled).
337pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
338 Deferred(Some(f))
339}
340
341pub struct RandomCharIter<T: Rng> {
342 rng: T,
343 simple_text: bool,
344}
345
346impl<T: Rng> RandomCharIter<T> {
347 pub fn new(rng: T) -> Self {
348 Self {
349 rng,
350 simple_text: std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()),
351 }
352 }
353
354 pub fn with_simple_text(mut self) -> Self {
355 self.simple_text = true;
356 self
357 }
358}
359
360impl<T: Rng> Iterator for RandomCharIter<T> {
361 type Item = char;
362
363 fn next(&mut self) -> Option<Self::Item> {
364 if self.simple_text {
365 return if self.rng.gen_range(0..100) < 5 {
366 Some('\n')
367 } else {
368 Some(self.rng.gen_range(b'a'..b'z' + 1).into())
369 };
370 }
371
372 match self.rng.gen_range(0..100) {
373 // whitespace
374 0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.rng).copied(),
375 // two-byte greek letters
376 20..=32 => char::from_u32(self.rng.gen_range(('α' as u32)..('ω' as u32 + 1))),
377 // // three-byte characters
378 33..=45 => ['✋', '✅', '❌', '❎', '⭐']
379 .choose(&mut self.rng)
380 .copied(),
381 // // four-byte characters
382 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.rng).copied(),
383 // ascii letters
384 _ => Some(self.rng.gen_range(b'a'..b'z' + 1).into()),
385 }
386 }
387}
388
389/// Get an embedded file as a string.
390pub fn asset_str<A: rust_embed::RustEmbed>(path: &str) -> Cow<'static, str> {
391 match A::get(path).unwrap().data {
392 Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes).unwrap()),
393 Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).unwrap()),
394 }
395}
396
397// copy unstable standard feature option unzip
398// https://github.com/rust-lang/rust/issues/87800
399// Remove when this ship in Rust 1.66 or 1.67
400pub fn unzip_option<T, U>(option: Option<(T, U)>) -> (Option<T>, Option<U>) {
401 match option {
402 Some((a, b)) => (Some(a), Some(b)),
403 None => (None, None),
404 }
405}
406
407/// Evaluates to an immediately invoked function expression. Good for using the ? operator
408/// in functions which do not return an Option or Result
409#[macro_export]
410macro_rules! maybe {
411 ($block:block) => {
412 (|| $block)()
413 };
414}
415
416/// Evaluates to an immediately invoked function expression. Good for using the ? operator
417/// in functions which do not return an Option or Result, but async.
418#[macro_export]
419macro_rules! async_maybe {
420 ($block:block) => {
421 (|| async move { $block })()
422 };
423}
424
425pub trait RangeExt<T> {
426 fn sorted(&self) -> Self;
427 fn to_inclusive(&self) -> RangeInclusive<T>;
428 fn overlaps(&self, other: &Range<T>) -> bool;
429 fn contains_inclusive(&self, other: &Range<T>) -> bool;
430}
431
432impl<T: Ord + Clone> RangeExt<T> for Range<T> {
433 fn sorted(&self) -> Self {
434 cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
435 }
436
437 fn to_inclusive(&self) -> RangeInclusive<T> {
438 self.start.clone()..=self.end.clone()
439 }
440
441 fn overlaps(&self, other: &Range<T>) -> bool {
442 self.start < other.end && other.start < self.end
443 }
444
445 fn contains_inclusive(&self, other: &Range<T>) -> bool {
446 self.start <= other.start && other.end <= self.end
447 }
448}
449
450impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
451 fn sorted(&self) -> Self {
452 cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone()
453 }
454
455 fn to_inclusive(&self) -> RangeInclusive<T> {
456 self.clone()
457 }
458
459 fn overlaps(&self, other: &Range<T>) -> bool {
460 self.start() < &other.end && &other.start <= self.end()
461 }
462
463 fn contains_inclusive(&self, other: &Range<T>) -> bool {
464 self.start() <= &other.start && &other.end <= self.end()
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
473 fn test_extend_sorted() {
474 let mut vec = vec![];
475
476 extend_sorted(&mut vec, vec![21, 17, 13, 8, 1, 0], 5, |a, b| b.cmp(a));
477 assert_eq!(vec, &[21, 17, 13, 8, 1]);
478
479 extend_sorted(&mut vec, vec![101, 19, 17, 8, 2], 8, |a, b| b.cmp(a));
480 assert_eq!(vec, &[101, 21, 19, 17, 13, 8, 2, 1]);
481
482 extend_sorted(&mut vec, vec![1000, 19, 17, 9, 5], 8, |a, b| b.cmp(a));
483 assert_eq!(vec, &[1000, 101, 21, 19, 17, 13, 9, 8]);
484 }
485
486 #[test]
487 fn test_iife() {
488 fn option_returning_function() -> Option<()> {
489 None
490 }
491
492 let foo = maybe!({
493 option_returning_function()?;
494 Some(())
495 });
496
497 assert_eq!(foo, None);
498 }
499
500 #[test]
501 fn test_trancate_and_trailoff() {
502 assert_eq!(truncate_and_trailoff("", 5), "");
503 assert_eq!(truncate_and_trailoff("èèèèèè", 7), "èèèèèè");
504 assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
505 assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
506 }
507}