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}