wagon_utils/
lib.rs

1#![warn(missing_docs)]
2//! Utility methods for the WAGon suite of libraries.
3//!
4//! Provides a number of simple functions, as well as a trait version of [`std::iter::Peekable`]
5//! and a fallible version of [`itertools::Itertools`].
6
7/// A trait version of [`std::iter::Peekable`].
8mod peek;
9/// A fallible version of [`itertools::Itertools`].
10mod fallible_itertools;
11
12use std::{error::Error, fmt::Display, marker::PhantomData, ops::Range, str::Chars};
13
14use itertools::Itertools;
15pub use peek::Peek;
16pub use fallible_itertools::FallibleItertools;
17
18/// Removes the first and last character of a string.
19/// # Example
20/// ```
21/// use wagon_utils::rem_first_and_last_char;
22///
23/// let s = "123";
24/// assert_eq!("2", rem_first_and_last_char(s));
25/// ```
26#[must_use]
27pub fn rem_first_and_last_char(value: &str) -> String {
28    _rem_last_char(_rem_first_char(value))
29}
30
31/// Removes the first character of a string.
32/// # Example
33/// ```
34/// use wagon_utils::rem_first_char;
35///
36/// let s = "123";
37/// assert_eq!("23", rem_first_char(s));
38/// ```
39#[must_use]
40pub fn rem_first_char(value: &str) -> String {
41    _rem_first_char(value).as_str().to_string()
42}
43
44/// Removes last character of a string.
45/// # Example
46/// ```
47/// use wagon_utils::rem_last_char;
48///
49/// let s = "123";
50/// assert_eq!("12", rem_last_char(s));
51/// ```
52#[must_use]
53pub fn rem_last_char(value: &str) -> String {
54    _rem_last_char(value.chars())
55}
56
57/// Removes the first n characters of a string.
58/// # Example
59/// ```
60/// use wagon_utils::rem_first_char_n;
61///
62/// let s = "123";
63/// assert_eq!("3", rem_first_char_n(s, 2));
64/// ```
65#[must_use]
66pub fn rem_first_char_n(value: &str, n: usize) -> String {
67    _rem_first_char_n(value, n).as_str().to_string()
68}
69
70/// Removes all whitespace from a string.
71/// # Example
72/// ```
73/// use wagon_utils::remove_whitespace;
74///
75/// let s = "   1  2 3      ".to_string();
76/// assert_eq!("123", remove_whitespace(s));
77/// ```
78#[must_use]
79pub fn remove_whitespace(mut s: String) -> String {
80    s.retain(|c| !c.is_whitespace());
81    s
82}
83
84/// Given string and a character. Attempt to split the string at that character into 2 distinct parts.
85/// # Example
86/// ```
87/// use wagon_utils::split_to_twople;
88///
89/// let s = "1:2";
90/// assert_eq!(Ok(("1".to_string(), "2".to_string())), split_to_twople(s, ':'));
91/// ```
92///
93/// # Errors
94/// Returns a [`SplitError`] when the function is unable to split the string on the `split` character.
95pub fn split_to_twople(s: &str, split: char) -> Result<(String, String), SplitError> {
96    let mut splitted = s.split(split);
97    if let Some(left) = splitted.next() {
98        if let Some(right) = splitted.next() {
99            return Ok((left.trim().to_string(), right.trim().to_string()))
100        }
101    }
102    Err(SplitError(s.to_owned(), split))
103}
104
105/// An struct for when [`split_to_twople`] fails.
106#[derive(Eq, PartialEq, Debug)]
107pub struct SplitError(String, char);
108
109impl SplitError {
110    /// Get the input string that caused the error.
111    #[must_use]
112    pub fn get_input(self) -> String {
113        self.0
114    }
115    /// Get the character that we weren't able to split on.
116    #[must_use]
117    pub fn get_split(self) -> char {
118        self.1
119    }
120    /// Get both values in a tuple.
121    #[must_use]
122    pub fn decompose(self) -> (String, char) {
123        (self.0, self.1)
124    }
125}
126
127fn _rem_first_char(value: &str) -> Chars {
128    let mut chars = value.chars();
129    chars.next();
130    chars
131}
132
133fn _rem_last_char(mut chars: Chars) -> String {
134    chars.next_back();
135    return chars.as_str().to_string();
136}
137
138fn _rem_first_char_n(value: &str, n: usize) -> Chars {
139    let mut chars = value.chars();
140    let mut i = 0;
141    while i < n {
142        i += 1;
143        chars.next();
144    }
145    chars
146}
147
148/// Given a vector of strings, return a string that has all the values joined by a `,`. Except for the last which is joined by ` or `.
149///
150/// # Example
151/// ```
152/// use wagon_utils::comma_separated_with_or;
153///
154/// let v = vec!["1".to_string(), "2".to_string(), "3".to_string()];
155/// assert_eq!("1, 2 or 3".to_string(), comma_separated_with_or(&v));
156/// let v = vec!["1".to_string()];
157/// assert_eq!("1".to_string(), comma_separated_with_or(&v));
158/// let v = vec![];
159/// assert_eq!("".to_string(), comma_separated_with_or(&v));
160/// ```
161pub fn comma_separated_with_or(strings: &[String]) -> String {
162    strings.last().map_or_else(String::new, |last| {
163        let len = strings.len();
164        if len == 1 {
165            last.clone()
166        } else {
167            let joined = strings.iter().take(len - 1).map(String::as_str).collect::<Vec<&str>>().join(", ");
168            format!("{joined} or {last}")
169        }
170    })
171}
172
173/// Same as [`comma_separated_with_or`], but takes a vector of `&str` instead.
174///
175/// This is a separate function from [`comma_separated_with_or`] because it is slightly slower
176/// as it requires a copy operation per string instead of just a borrow.
177///
178/// # Example
179/// ```
180/// use wagon_utils::comma_separated_with_or_str;
181///
182/// let v = vec!["1", "2", "3"];
183/// assert_eq!("1, 2 or 3".to_string(), comma_separated_with_or_str(&v));
184/// ```
185pub fn comma_separated_with_or_str(strings: &[&str]) -> String {
186    strings.last().map_or_else(String::new, |last| {
187        let len = strings.len();
188        if len == 1 {
189            (*last).to_string()
190        } else {
191            let joined = strings.iter().take(len - 1).copied().collect::<Vec<&str>>().join(", ");
192            format!("{joined} or {last}")
193        }
194    })
195}
196
197/// Given a list of objects that implement [`std::fmt::Display`]. Returns a comma separated string.
198#[must_use] 
199pub fn comma_separated<T: std::fmt::Display>(input: &[T]) -> String {
200    input.iter().join(",")
201}
202
203/// Given a list of values, attempt to normalize them based on their sum.
204///
205/// This method works as long as the type inside the vec supports the [`std::ops::Div`] and [`std::iter::Sum`] traits. 
206///
207/// The algorithm "works" by summing all the values together, and then normalizes each value by calculating `val / sum`.
208///
209/// # Example
210/// ```
211/// use wagon_utils::normalize_to_probabilities;
212///
213/// let v = vec![1.0, 1.0, 2.0];
214/// assert_eq!(vec![0.25, 0.25, 0.5], normalize_to_probabilities(&v))
215#[must_use]
216pub fn normalize_to_probabilities<T: std::ops::Div<Output = T> + for<'a> std::iter::Sum<&'a T>>(input: &Vec<T>) -> Vec<T> where for<'a> &'a T: std::ops::Div<Output = T> {
217    let mut output = Vec::with_capacity(input.len());
218    let sum: T = input.iter().sum();
219
220    for val in input {
221        output.push(val / &sum);
222    }
223    output
224}
225
226/// Same as [`vec!`] but calls `to_string()` on all the elements.
227#[macro_export]
228macro_rules! string_vec {
229    ( $( $x:expr ),* ) => {
230        vec![$($x.to_string(),)*]
231    };
232}
233
234/// Quickly get a result from an iterator of [`Result`]s.
235///
236/// This trait is automatically implemented for any iterator of `Result`s that has an error type
237/// that implements [`From<UnexpectedEnd>`].
238pub trait ResultNext<T, E>: Iterator<Item = Result<T,E>> {
239    /// If you have an iterator that holds `Result` items, you start having to deal with nested `Some(Ok(...))` patterns,
240    /// which gets annoying quickly. This trait is intended so that the iterator always returns some sort of `Result`, which can then be unwrapped as needed (probably using `?`).
241    ///
242    /// # Example
243    /// ```
244    /// # use wagon_utils::ResultNext;
245    /// # use wagon_utils::UnexpectedEnd;
246    /// struct IterVec<T, E>(Vec<Result<T, E>>);
247    /// #[derive(Debug, Eq, PartialEq)]
248    /// enum IterErr {
249    ///      SomeErr,
250    ///      EndErr
251    /// }
252    /// impl From<UnexpectedEnd> for IterErr {
253    /// #    fn from(value: UnexpectedEnd) -> Self {
254    /// #        Self::EndErr
255    /// #    }
256    /// }
257    /// impl<T, E> Iterator for IterVec<T, E> {
258    ///     # type Item = Result<T, E>;
259    ///     # fn next(&mut self) -> Option<Self::Item> {
260    ///     #     self.0.pop()
261    ///     # }
262    /// }
263    /// 
264    /// let mut iter: IterVec<i32, IterErr> = IterVec(vec![Ok(1)]);
265    /// assert_eq!(Ok(1), iter.next_result());
266    /// assert_eq!(Err(IterErr::EndErr), iter.next_result());
267    /// ```
268    ///
269    /// # Errors
270    /// Should return `Err` if the underlying item is an `Err`, or there are no more items in the iterator.
271    fn next_result(&mut self) -> Result<T, E>;
272}
273
274/// Same as [`ResultNext`] but for things that implement [`Peek`].
275///
276/// This trait is automatically implemented for any iterator of `Result`s that has an error type `E`
277/// such that `&E: From<UnexpectedEnd>`.
278pub trait ResultPeek<T, E>: Peek + Iterator<Item = Result<T, E>> {
279    /// See [`next_result`](`ResultNext::next_result`).
280    ///
281    /// # Errors
282    /// Should return `Err` if the underlying item is an `Err`, or there are no more items in the iterator.
283    fn peek_result(&mut self) -> Result<&T, E>;
284}
285
286/// Error struct to represent we've reached the end of an iterator. Used for [`ResultNext`].
287pub struct UnexpectedEnd;
288
289impl<T, E: From<UnexpectedEnd>, U: Iterator<Item = Result<T, E>>> ResultNext<T, E> for U {
290    fn next_result(&mut self) -> Result<T, E> {
291        match self.next() {
292            Some(Ok(x)) => Ok(x),
293            Some(Err(e)) => Err(e),
294            None => Err(UnexpectedEnd.into())
295        }
296    }
297}
298
299impl<T, E, U: Peek + Iterator<Item = Result<T, E>>> ResultPeek<T, E> for U 
300    where for<'a> E: From<UnexpectedEnd> + Clone + 'a
301    {
302        fn peek_result(&mut self) -> Result<&T, E> {
303            match self.peek() {
304                None => Err(UnexpectedEnd.into()),
305                Some(Ok(ref x)) => Ok(x),
306                Some(Err(e)) => Err(e.clone())
307            }
308        }
309    }
310
311/// Forcibly extract an item out of an iterator of [`Result`]s.
312///
313/// If you have an iterator that holds `Result` items, it quickly becomes annoying to constantly unwrap.
314/// This trait provides the method `next_unwrap` to quickly extract the inner item.
315pub trait UnsafeNext<T, E: std::fmt::Debug>: Iterator<Item = Result<T, E>> {
316    /// # Example
317    /// ```should_panic
318    /// # use wagon_utils::UnsafeNext;
319    /// struct IterVec<T, E>(Vec<Result<T, E>>);
320    /// impl<T, E: std::fmt::Debug> Iterator for IterVec<T, E> {
321    ///     # type Item = Result<T, E>;
322    ///     # fn next(&mut self) -> Option<Self::Item> {
323    ///     #     self.0.pop()
324    ///     # }
325    /// }
326    /// impl<T, E: std::fmt::Debug> UnsafeNext<T, E> for IterVec<T, E> {}
327    /// 
328    /// let mut iter: IterVec<i32, ()> = IterVec(vec![Ok(1)]);
329    /// assert_eq!(1, iter.next_unwrap());
330    /// iter.next_unwrap(); // panic!
331    /// ```
332    ///
333    /// # Panics
334    /// Panics if the next element is either `None` or an `Err`.
335    #[allow(clippy::panic)]
336    fn next_unwrap(&mut self) -> T {
337        match self.next() {
338            Some(Ok(x)) => x,
339            Some(Err(e)) => panic!("Got error: {e:?}"),
340            None => panic!("Expected a value, but failed")
341        }
342    }
343}
344
345/// Same as [`UnsafeNext`] but intended for iterators that allow peeking (such as [`Peekable`](`std::iter::Peekable`)).
346pub trait UnsafePeek<'a, T, E: std::fmt::Debug + 'a>: Peek + Iterator<Item = Result<T, E>> {
347    /// See [`next_unwrap`](`UnsafeNext::next_unwrap`).
348    #[allow(clippy::panic)]
349    fn peek_unwrap(&'a mut self) -> &T {
350        match self.peek() {
351            Some(Ok(ref x)) => x,
352            Some(Err(ref e)) => panic!("Got error: {e:?}"),
353            None => panic!("Expected a value, but failed")
354        }
355    }
356}
357
358#[derive(Debug)]
359/// We failed to convert from some thing to another thing.
360///
361/// A generic error for when doing any [`TryFrom`] type implementations.
362///
363/// # Example
364/// ```
365/// use wagon_utils::ConversionError;
366///
367/// struct A;
368/// impl TryFrom<i32> for A {
369///     type Error = ConversionError<i32, Self>;
370///
371///     fn try_from(value: i32) -> Result<Self, Self::Error> {
372///         Err(ConversionError::new(value))
373///     }
374/// }
375/// ```
376/// 
377pub struct ConversionError<T, U> {
378    /// The thing we want to convert.
379    subject: T,
380    /// Used to hold the type info for the type `U` we are trying to convert to.
381    to: PhantomData<U>
382}
383
384impl<T: Display, U> Display for ConversionError<T, U> {
385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386        write!(f, "Failed to convert {} from type {} to value variant {}", self.subject, std::any::type_name::<T>(), std::any::type_name::<U>())
387    }
388}
389
390impl<T: Display + std::fmt::Debug, U: std::fmt::Debug> Error for ConversionError<T, U> {}
391
392impl<T, U> ConversionError<T, U> {
393    /// Create a new `ConversionError`.
394    pub const fn new(subject: T) -> Self {
395        Self {subject, to: PhantomData}
396    }
397
398    /// Convert one implementation of `ConversionError` to another.
399    ///
400    /// More specifically, if we have a type `V` which implements `From<U>`, we can
401    /// construct a new `ConversionError<T,V>`.
402    ///
403    /// This exists for the case "We tried converting T to U, but actually we were converting T to V in this case".
404    ///
405    /// Because specialization is still not stabilized, this can not be done by a generic implementation of [`From`].
406    pub fn convert<V: From<U>>(self) -> ConversionError<T, V> {
407        ConversionError::<T, V>::new(self.subject)
408    }
409}
410
411/// The definition of a Span as used throughout WAGon.
412pub type Span = Range<usize>;
413
414/// A trait for [`Error`]s that return a specific message and span structure.
415pub trait ErrorReport: Error {
416    /// Return the full error report
417    fn report(self) -> ((String, String), Span, Option<String>) where Self: Sized {
418        let msg = self.msg();
419        let source = ErrorReport::source(&self);
420        let span = self.span();
421        (msg, span, source)
422    }
423
424    /// Return span information for this error.
425    fn span(self) -> Span;
426
427    /// Return a tuple description of the error message.
428    ///
429    /// The first element is a "header" (for example: 'Fatal Exception!').
430    /// The second element is the actual message.
431    fn msg(&self) -> (String, String);
432
433    /// Return the text source for this error message.
434    ///
435    /// Usually the source of the error is just the input file, in which case we return `None`.
436    /// Sometimes, however, the source of the error may be a `TokenStream` or some other text. In this case,
437    /// the output of `source` should be that stream of text.
438    fn source(&self) -> Option<String> {
439        None
440    }
441}
442
443/// Trait for objects that provide [`Span`] information. Used for error messaging.
444pub trait Spannable {
445    /// Get the [`Span`] of the object
446    fn span(&self) -> Span;
447    /// Set the [`Span`] of the object. Possibly does nothing as implementation is optional.
448    fn set_span(&mut self, _span: Span) {}
449}
450
451#[cfg(feature = "error_printing")]
452/// Prints out a nice error message to stderr using [`ariadne`].
453///
454/// See [`ariadne`] for more information. The `file_path` **must** be static because of requirements in that library. I recommend simply,
455/// leaking that string to make it static, given that you likely end the program immediately afterwards anyway.
456///
457/// # Errors
458/// Errors when unable to print to stderr.
459pub fn handle_error<T: ErrorReport>(err: Vec<T>, file_path: &'static str, file: &str, offset: usize) -> Result<(), std::io::Error> {
460    use ariadne::{ColorGenerator, Label, Report, ReportKind};
461    let mut colors = ColorGenerator::new();
462    let a = colors.next();
463    let mut builder = Report::build(ReportKind::Error, file_path, offset);
464    let mut sources = std::collections::HashMap::new();
465    for e in err {
466        let ((head, msg), span, source) = e.report();
467        let data = source.map_or(file.to_string(), |data| data);
468        sources.insert(file_path, data);
469        builder = builder.with_message(head)
470            .with_label(
471                Label::new((file_path, span))
472                    .with_message(msg)
473                    .with_color(a),
474            );
475    }
476    builder.finish().eprint(ariadne::sources(sources))
477}