tank_core/
util.rs

1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use std::{borrow::Cow, cmp::min, ffi::CString};
4use syn::Path;
5
6#[derive(Clone)]
7/// Polymorphic iterator adapter returning items from either variant.
8pub enum EitherIterator<A, B>
9where
10    A: Iterator,
11    B: Iterator<Item = A::Item>,
12{
13    Left(A),
14    Right(B),
15}
16impl<A, B> Iterator for EitherIterator<A, B>
17where
18    A: Iterator,
19    B: Iterator<Item = A::Item>,
20{
21    type Item = A::Item;
22    fn next(&mut self) -> Option<Self::Item> {
23        match self {
24            EitherIterator::Left(a) => a.next(),
25            EitherIterator::Right(b) => b.next(),
26        }
27    }
28}
29
30/// Quote a `Cow<T>` preserving borrowed vs owned status for generated code.
31pub fn quote_cow<T: ToOwned + ToTokens + ?Sized>(value: &Cow<T>) -> TokenStream
32where
33    <T as ToOwned>::Owned: ToTokens,
34{
35    match value {
36        Cow::Borrowed(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
37        Cow::Owned(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
38    }
39}
40
41/// Quote an `Option<T>` into tokens.
42pub fn quote_option<T: ToTokens>(value: &Option<T>) -> TokenStream {
43    match value {
44        None => quote! { None },
45        Some(v) => quote! { Some(#v) },
46    }
47}
48
49/// Determine if the trailing segments of a `syn::Path` match the expected identifiers.
50pub fn matches_path(path: &Path, expect: &[&str]) -> bool {
51    let len = min(path.segments.len(), expect.len());
52    path.segments
53        .iter()
54        .rev()
55        .take(len)
56        .map(|v| &v.ident)
57        .eq(expect.iter().rev().take(len))
58}
59
60/// Write an iterator of items separated by a delimiter into a string.
61pub fn separated_by<T, F>(
62    out: &mut String,
63    values: impl IntoIterator<Item = T>,
64    mut f: F,
65    separator: &str,
66) where
67    F: FnMut(&mut String, T),
68{
69    let mut len = out.len();
70    for v in values {
71        if out.len() > len {
72            out.push_str(separator);
73        }
74        len = out.len();
75        f(out, v);
76    }
77}
78
79/// Convenience wrapper converting into a `CString`, panicking on interior NUL.
80pub fn as_c_string<S: Into<Vec<u8>>>(str: S) -> CString {
81    CString::new(str.into()).expect("Expected a valid C string")
82}
83
84/// Consume a prefix of `input` while the predicate returns true, returning that slice.
85pub fn consume_while<'s>(input: &mut &'s str, predicate: impl FnMut(&char) -> bool) -> &'s str {
86    let len = input.chars().take_while(predicate).count();
87    if len == 0 {
88        return "";
89    }
90    let result = &input[..len];
91    *input = &input[len..];
92    result
93}
94
95#[macro_export]
96macro_rules! possibly_parenthesized {
97    ($out:ident, $cond:expr, $v:expr) => {
98        if $cond {
99            $out.push('(');
100            $v;
101            $out.push(')');
102        } else {
103            $v;
104        }
105    };
106}
107
108#[macro_export]
109macro_rules! truncate_long {
110    ($query:expr) => {
111        format_args!(
112            "{}{}\n",
113            &$query[..::std::cmp::min($query.len(), 497)].trim_end(),
114            if $query.len() > 497 { "..." } else { "" },
115        )
116    };
117}
118
119/// Sends the value through the channel and logs in case of error.
120#[macro_export]
121macro_rules! send_value {
122    ($tx:ident, $value:expr) => {{
123        if let Err(e) = $tx.send($value) {
124            log::error!("{:#}", e);
125        }
126    }};
127}
128
129/// Accumulates tokens until one of the supplied parsers succeeds.
130///
131/// Returns `(accumulated_tokens, (parser1_result, parser2_result, ...))` where each
132/// parser result is `Some(parsed)` for the parser that matched, or `None` otherwise.
133#[macro_export]
134macro_rules! take_until {
135    ($original:expr, $($parser:expr),+ $(,)?) => {{
136        let macro_local_input = $original.fork();
137        let mut macro_local_result = (
138            TokenStream::new(),
139            ($({
140                let _ = $parser;
141                None
142            }),+),
143        );
144        loop {
145            if macro_local_input.is_empty() {
146                break;
147            }
148            let mut parsed = false;
149            let produced = ($({
150                let attempt = macro_local_input.fork();
151                if let Ok(content) = ($parser)(&attempt) {
152                    macro_local_input.advance_to(&attempt);
153                    parsed = true;
154                    Some(content)
155                } else {
156                    None
157                }
158            }),+);
159            if parsed {
160                macro_local_result.1 = produced;
161                break;
162            }
163            macro_local_result.0.append(macro_local_input.parse::<TokenTree>()?);
164        }
165        $original.advance_to(&macro_local_input);
166        macro_local_result
167    }};
168}