util.rs

  1pub mod arc_cow;
  2pub mod fs;
  3pub mod github;
  4pub mod http;
  5pub mod paths;
  6#[cfg(any(test, feature = "test-support"))]
  7pub mod test;
  8
  9use futures::Future;
 10use lazy_static::lazy_static;
 11use rand::{seq::SliceRandom, Rng};
 12use std::{
 13    borrow::Cow,
 14    cmp::{self, Ordering},
 15    env,
 16    ops::{AddAssign, Range, RangeInclusive},
 17    panic::Location,
 18    pin::Pin,
 19    task::{Context, Poll},
 20    time::Instant,
 21};
 22use unicase::UniCase;
 23
 24pub use take_until::*;
 25
 26#[macro_export]
 27macro_rules! debug_panic {
 28    ( $($fmt_arg:tt)* ) => {
 29        if cfg!(debug_assertions) {
 30            panic!( $($fmt_arg)* );
 31        } else {
 32            let backtrace = std::backtrace::Backtrace::capture();
 33            log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace);
 34        }
 35    };
 36}
 37
 38pub fn truncate(s: &str, max_chars: usize) -> &str {
 39    match s.char_indices().nth(max_chars) {
 40        None => s,
 41        Some((idx, _)) => &s[..idx],
 42    }
 43}
 44
 45pub fn http_proxy_from_env() -> Option<isahc::http::Uri> {
 46    macro_rules! try_env {
 47        ($($env:literal),+) => {
 48            $(
 49                if let Ok(env) = std::env::var($env) {
 50                    return env.parse::<isahc::http::Uri>().ok();
 51                }
 52            )+
 53        };
 54    }
 55
 56    try_env!(
 57        "ALL_PROXY",
 58        "all_proxy",
 59        "HTTPS_PROXY",
 60        "https_proxy",
 61        "HTTP_PROXY",
 62        "http_proxy"
 63    );
 64    None
 65}
 66
 67/// Removes characters from the end of the string if its length is greater than `max_chars` and
 68/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.
 69pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {
 70    debug_assert!(max_chars >= 5);
 71
 72    let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars);
 73    match truncation_ix {
 74        Some(length) => s[..length].to_string() + "",
 75        None => s.to_string(),
 76    }
 77}
 78
 79/// Removes characters from the front of the string if its length is greater than `max_chars` and
 80/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars.
 81pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String {
 82    debug_assert!(max_chars >= 5);
 83
 84    let truncation_ix = s.char_indices().map(|(i, _)| i).nth_back(max_chars);
 85    match truncation_ix {
 86        Some(length) => "".to_string() + &s[length..],
 87        None => s.to_string(),
 88    }
 89}
 90
 91/// Takes only `max_lines` from the string and, if there were more than `max_lines-1`, appends a
 92/// a newline and "..." to the string, so that `max_lines` are returned.
 93/// Returns string unchanged if its length is smaller than max_lines.
 94pub fn truncate_lines_and_trailoff(s: &str, max_lines: usize) -> String {
 95    let mut lines = s.lines().take(max_lines).collect::<Vec<_>>();
 96    if lines.len() > max_lines - 1 {
 97        lines.pop();
 98        lines.join("\n") + "\n"
 99    } else {
100        lines.join("\n")
101    }
102}
103
104pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
105    let prev = *value;
106    *value += T::from(1);
107    prev
108}
109
110/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
111/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
112/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
113pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
114where
115    I: IntoIterator<Item = T>,
116    F: FnMut(&T, &T) -> Ordering,
117{
118    let mut start_index = 0;
119    for new_item in new_items {
120        if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) {
121            let index = start_index + i;
122            if vec.len() < limit {
123                vec.insert(index, new_item);
124            } else if index < vec.len() {
125                vec.pop();
126                vec.insert(index, new_item);
127            }
128            start_index = index;
129        }
130    }
131}
132
133/// Parse the result of calling `usr/bin/env` with no arguments
134pub fn parse_env_output(env: &str, mut f: impl FnMut(String, String)) {
135    let mut current_key: Option<String> = None;
136    let mut current_value: Option<String> = None;
137
138    for line in env.split_terminator('\n') {
139        if let Some(separator_index) = line.find('=') {
140            if &line[..separator_index] != "" {
141                if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
142                    f(key, value)
143                }
144                current_key = Some(line[..separator_index].to_string());
145                current_value = Some(line[separator_index + 1..].to_string());
146                continue;
147            };
148        }
149        if let Some(value) = current_value.as_mut() {
150            value.push('\n');
151            value.push_str(line);
152        }
153    }
154    if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
155        f(key, value)
156    }
157}
158
159pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
160    use serde_json::Value;
161
162    match (source, target) {
163        (Value::Object(source), Value::Object(target)) => {
164            for (key, value) in source {
165                if let Some(target) = target.get_mut(&key) {
166                    merge_json_value_into(value, target);
167                } else {
168                    target.insert(key.clone(), value);
169                }
170            }
171        }
172
173        (source, target) => *target = source,
174    }
175}
176
177pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
178    use serde_json::Value;
179    if let Value::Object(source_object) = source {
180        let target_object = if let Value::Object(target) = target {
181            target
182        } else {
183            *target = Value::Object(Default::default());
184            target.as_object_mut().unwrap()
185        };
186        for (key, value) in source_object {
187            if let Some(target) = target_object.get_mut(&key) {
188                merge_non_null_json_value_into(value, target);
189            } else if !value.is_null() {
190                target_object.insert(key.clone(), value);
191            }
192        }
193    } else if !source.is_null() {
194        *target = source
195    }
196}
197
198pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
199    lazy_static! {
200        pub static ref ZED_MEASUREMENTS: bool = env::var("ZED_MEASUREMENTS")
201            .map(|measurements| measurements == "1" || measurements == "true")
202            .unwrap_or(false);
203    }
204
205    if *ZED_MEASUREMENTS {
206        let start = Instant::now();
207        let result = f();
208        let elapsed = start.elapsed();
209        eprintln!("{}: {:?}", label, elapsed);
210        result
211    } else {
212        f()
213    }
214}
215
216pub trait ResultExt<E> {
217    type Ok;
218
219    fn log_err(self) -> Option<Self::Ok>;
220    /// Assert that this result should never be an error in development or tests.
221    fn debug_assert_ok(self, reason: &str) -> Self;
222    fn warn_on_err(self) -> Option<Self::Ok>;
223    fn inspect_error(self, func: impl FnOnce(&E)) -> Self;
224}
225
226impl<T, E> ResultExt<E> for Result<T, E>
227where
228    E: std::fmt::Debug,
229{
230    type Ok = T;
231
232    #[track_caller]
233    fn log_err(self) -> Option<T> {
234        match self {
235            Ok(value) => Some(value),
236            Err(error) => {
237                let caller = Location::caller();
238                log::error!("{}:{}: {:?}", caller.file(), caller.line(), error);
239                None
240            }
241        }
242    }
243
244    #[track_caller]
245    fn debug_assert_ok(self, reason: &str) -> Self {
246        if let Err(error) = &self {
247            debug_panic!("{reason} - {error:?}");
248        }
249        self
250    }
251
252    fn warn_on_err(self) -> Option<T> {
253        match self {
254            Ok(value) => Some(value),
255            Err(error) => {
256                log::warn!("{:?}", error);
257                None
258            }
259        }
260    }
261
262    /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
263    fn inspect_error(self, func: impl FnOnce(&E)) -> Self {
264        if let Err(err) = &self {
265            func(err);
266        }
267
268        self
269    }
270}
271
272pub trait TryFutureExt {
273    fn log_err(self) -> LogErrorFuture<Self>
274    where
275        Self: Sized;
276
277    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
278    where
279        Self: Sized;
280
281    fn warn_on_err(self) -> LogErrorFuture<Self>
282    where
283        Self: Sized;
284    fn unwrap(self) -> UnwrapFuture<Self>
285    where
286        Self: Sized;
287}
288
289impl<F, T, E> TryFutureExt for F
290where
291    F: Future<Output = Result<T, E>>,
292    E: std::fmt::Debug,
293{
294    #[track_caller]
295    fn log_err(self) -> LogErrorFuture<Self>
296    where
297        Self: Sized,
298    {
299        let location = Location::caller();
300        LogErrorFuture(self, log::Level::Error, *location)
301    }
302
303    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
304    where
305        Self: Sized,
306    {
307        LogErrorFuture(self, log::Level::Error, location)
308    }
309
310    #[track_caller]
311    fn warn_on_err(self) -> LogErrorFuture<Self>
312    where
313        Self: Sized,
314    {
315        let location = Location::caller();
316        LogErrorFuture(self, log::Level::Warn, *location)
317    }
318
319    fn unwrap(self) -> UnwrapFuture<Self>
320    where
321        Self: Sized,
322    {
323        UnwrapFuture(self)
324    }
325}
326
327#[must_use]
328pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
329
330impl<F, T, E> Future for LogErrorFuture<F>
331where
332    F: Future<Output = Result<T, E>>,
333    E: std::fmt::Debug,
334{
335    type Output = Option<T>;
336
337    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
338        let level = self.1;
339        let location = self.2;
340        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
341        match inner.poll(cx) {
342            Poll::Ready(output) => Poll::Ready(match output {
343                Ok(output) => Some(output),
344                Err(error) => {
345                    log::log!(
346                        level,
347                        "{}:{}: {:?}",
348                        location.file(),
349                        location.line(),
350                        error
351                    );
352                    None
353                }
354            }),
355            Poll::Pending => Poll::Pending,
356        }
357    }
358}
359
360pub struct UnwrapFuture<F>(F);
361
362impl<F, T, E> Future for UnwrapFuture<F>
363where
364    F: Future<Output = Result<T, E>>,
365    E: std::fmt::Debug,
366{
367    type Output = T;
368
369    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
370        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
371        match inner.poll(cx) {
372            Poll::Ready(result) => Poll::Ready(result.unwrap()),
373            Poll::Pending => Poll::Pending,
374        }
375    }
376}
377
378pub struct Deferred<F: FnOnce()>(Option<F>);
379
380impl<F: FnOnce()> Deferred<F> {
381    /// Drop without running the deferred function.
382    pub fn abort(mut self) {
383        self.0.take();
384    }
385}
386
387impl<F: FnOnce()> Drop for Deferred<F> {
388    fn drop(&mut self) {
389        if let Some(f) = self.0.take() {
390            f()
391        }
392    }
393}
394
395/// Run the given function when the returned value is dropped (unless it's cancelled).
396#[must_use]
397pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
398    Deferred(Some(f))
399}
400
401pub struct RandomCharIter<T: Rng> {
402    rng: T,
403    simple_text: bool,
404}
405
406impl<T: Rng> RandomCharIter<T> {
407    pub fn new(rng: T) -> Self {
408        Self {
409            rng,
410            simple_text: std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()),
411        }
412    }
413
414    pub fn with_simple_text(mut self) -> Self {
415        self.simple_text = true;
416        self
417    }
418}
419
420impl<T: Rng> Iterator for RandomCharIter<T> {
421    type Item = char;
422
423    fn next(&mut self) -> Option<Self::Item> {
424        if self.simple_text {
425            return if self.rng.gen_range(0..100) < 5 {
426                Some('\n')
427            } else {
428                Some(self.rng.gen_range(b'a'..b'z' + 1).into())
429            };
430        }
431
432        match self.rng.gen_range(0..100) {
433            // whitespace
434            0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.rng).copied(),
435            // two-byte greek letters
436            20..=32 => char::from_u32(self.rng.gen_range(('α' as u32)..('ω' as u32 + 1))),
437            // // three-byte characters
438            33..=45 => ['✋', '✅', '❌', '❎', '⭐']
439                .choose(&mut self.rng)
440                .copied(),
441            // // four-byte characters
442            46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.rng).copied(),
443            // ascii letters
444            _ => Some(self.rng.gen_range(b'a'..b'z' + 1).into()),
445        }
446    }
447}
448
449/// Get an embedded file as a string.
450pub fn asset_str<A: rust_embed::RustEmbed>(path: &str) -> Cow<'static, str> {
451    match A::get(path).unwrap().data {
452        Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes).unwrap()),
453        Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).unwrap()),
454    }
455}
456
457// copy unstable standard feature option unzip
458// https://github.com/rust-lang/rust/issues/87800
459// Remove when this ship in Rust 1.66 or 1.67
460pub fn unzip_option<T, U>(option: Option<(T, U)>) -> (Option<T>, Option<U>) {
461    match option {
462        Some((a, b)) => (Some(a), Some(b)),
463        None => (None, None),
464    }
465}
466
467/// Expands to an immediately-invoked function expression. Good for using the ? operator
468/// in functions which do not return an Option or Result.
469///
470/// Accepts a normal block, an async block, or an async move block.
471#[macro_export]
472macro_rules! maybe {
473    ($block:block) => {
474        (|| $block)()
475    };
476    (async $block:block) => {
477        (|| async $block)()
478    };
479    (async move $block:block) => {
480        (|| async move $block)()
481    };
482}
483
484pub trait RangeExt<T> {
485    fn sorted(&self) -> Self;
486    fn to_inclusive(&self) -> RangeInclusive<T>;
487    fn overlaps(&self, other: &Range<T>) -> bool;
488    fn contains_inclusive(&self, other: &Range<T>) -> bool;
489}
490
491impl<T: Ord + Clone> RangeExt<T> for Range<T> {
492    fn sorted(&self) -> Self {
493        cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
494    }
495
496    fn to_inclusive(&self) -> RangeInclusive<T> {
497        self.start.clone()..=self.end.clone()
498    }
499
500    fn overlaps(&self, other: &Range<T>) -> bool {
501        self.start < other.end && other.start < self.end
502    }
503
504    fn contains_inclusive(&self, other: &Range<T>) -> bool {
505        self.start <= other.start && other.end <= self.end
506    }
507}
508
509impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
510    fn sorted(&self) -> Self {
511        cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone()
512    }
513
514    fn to_inclusive(&self) -> RangeInclusive<T> {
515        self.clone()
516    }
517
518    fn overlaps(&self, other: &Range<T>) -> bool {
519        self.start() < &other.end && &other.start <= self.end()
520    }
521
522    fn contains_inclusive(&self, other: &Range<T>) -> bool {
523        self.start() <= &other.start && &other.end <= self.end()
524    }
525}
526
527/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
528/// case-insensitive.
529///
530/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
531/// into `1-abc, 2, 10, 11-def, .., 21-abc`
532#[derive(Debug, PartialEq, Eq)]
533pub struct NumericPrefixWithSuffix<'a>(i32, &'a str);
534
535impl<'a> NumericPrefixWithSuffix<'a> {
536    pub fn from_numeric_prefixed_str(str: &'a str) -> Option<Self> {
537        let i = str.chars().take_while(|c| c.is_ascii_digit()).count();
538        let (prefix, remainder) = str.split_at(i);
539
540        match prefix.parse::<i32>() {
541            Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
542            Err(_) => None,
543        }
544    }
545}
546
547impl Ord for NumericPrefixWithSuffix<'_> {
548    fn cmp(&self, other: &Self) -> Ordering {
549        let NumericPrefixWithSuffix(num_a, remainder_a) = self;
550        let NumericPrefixWithSuffix(num_b, remainder_b) = other;
551        num_a
552            .cmp(num_b)
553            .then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b)))
554    }
555}
556
557impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
558    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
559        Some(self.cmp(other))
560    }
561}
562lazy_static! {
563    static ref EMOJI_REGEX: regex::Regex = regex::Regex::new("(\\p{Emoji}|\u{200D})").unwrap();
564}
565
566/// Returns true if the given string consists of emojis only.
567/// E.g. "👨‍👩‍👧‍👧👋" will return true, but "👋!" will return false.
568pub fn word_consists_of_emojis(s: &str) -> bool {
569    let mut prev_end = 0;
570    for capture in EMOJI_REGEX.find_iter(s) {
571        if capture.start() != prev_end {
572            return false;
573        }
574        prev_end = capture.end();
575    }
576    prev_end == s.len()
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582
583    #[test]
584    fn test_extend_sorted() {
585        let mut vec = vec![];
586
587        extend_sorted(&mut vec, vec![21, 17, 13, 8, 1, 0], 5, |a, b| b.cmp(a));
588        assert_eq!(vec, &[21, 17, 13, 8, 1]);
589
590        extend_sorted(&mut vec, vec![101, 19, 17, 8, 2], 8, |a, b| b.cmp(a));
591        assert_eq!(vec, &[101, 21, 19, 17, 13, 8, 2, 1]);
592
593        extend_sorted(&mut vec, vec![1000, 19, 17, 9, 5], 8, |a, b| b.cmp(a));
594        assert_eq!(vec, &[1000, 101, 21, 19, 17, 13, 9, 8]);
595    }
596
597    #[test]
598    fn test_iife() {
599        fn option_returning_function() -> Option<()> {
600            None
601        }
602
603        let foo = maybe!({
604            option_returning_function()?;
605            Some(())
606        });
607
608        assert_eq!(foo, None);
609    }
610
611    #[test]
612    fn test_trancate_and_trailoff() {
613        assert_eq!(truncate_and_trailoff("", 5), "");
614        assert_eq!(truncate_and_trailoff("èèèèèè", 7), "èèèèèè");
615        assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
616        assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
617    }
618
619    #[test]
620    fn test_numeric_prefix_str_method() {
621        let target = "1a";
622        assert_eq!(
623            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
624            Some(NumericPrefixWithSuffix(1, "a"))
625        );
626
627        let target = "12ab";
628        assert_eq!(
629            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
630            Some(NumericPrefixWithSuffix(12, "ab"))
631        );
632
633        let target = "12_ab";
634        assert_eq!(
635            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
636            Some(NumericPrefixWithSuffix(12, "_ab"))
637        );
638
639        let target = "1_2ab";
640        assert_eq!(
641            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
642            Some(NumericPrefixWithSuffix(1, "_2ab"))
643        );
644
645        let target = "1.2";
646        assert_eq!(
647            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
648            Some(NumericPrefixWithSuffix(1, ".2"))
649        );
650
651        let target = "1.2_a";
652        assert_eq!(
653            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
654            Some(NumericPrefixWithSuffix(1, ".2_a"))
655        );
656
657        let target = "12.2_a";
658        assert_eq!(
659            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
660            Some(NumericPrefixWithSuffix(12, ".2_a"))
661        );
662
663        let target = "12a.2_a";
664        assert_eq!(
665            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
666            Some(NumericPrefixWithSuffix(12, "a.2_a"))
667        );
668    }
669
670    #[test]
671    fn test_numeric_prefix_with_suffix() {
672        let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
673        sorted.sort_by_key(|s| {
674            NumericPrefixWithSuffix::from_numeric_prefixed_str(s).unwrap_or_else(|| {
675                panic!("Cannot convert string `{s}` into NumericPrefixWithSuffix")
676            })
677        });
678        assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
679
680        for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
681            assert_eq!(
682                NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
683                None,
684                "String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
685            )
686        }
687    }
688
689    #[test]
690    fn test_word_consists_of_emojis() {
691        let words_to_test = vec![
692            ("👨‍👩‍👧‍👧👋🥒", true),
693            ("👋", true),
694            ("!👋", false),
695            ("👋!", false),
696            ("👋 ", false),
697            (" 👋", false),
698            ("Test", false),
699        ];
700
701        for (text, expected_result) in words_to_test {
702            assert_eq!(word_consists_of_emojis(text), expected_result);
703        }
704    }
705
706    #[test]
707    fn test_truncate_lines_and_trailoff() {
708        let text = r#"Line 1
709Line 2
710Line 3"#;
711
712        assert_eq!(
713            truncate_lines_and_trailoff(text, 2),
714            r#"Line 1
715…"#
716        );
717
718        assert_eq!(
719            truncate_lines_and_trailoff(text, 3),
720            r#"Line 1
721Line 2
722…"#
723        );
724
725        assert_eq!(
726            truncate_lines_and_trailoff(text, 4),
727            r#"Line 1
728Line 2
729Line 3"#
730        );
731    }
732}