parse_up/
util.rs

1use std::fmt::Display;
2
3use itertools::Itertools as _;
4
5use crate::{UpError, UpResult, YesAnd};
6
7pub fn yes_and<T>(yes: T, and: &str) -> UpResult<T> {
8    Ok(YesAnd {
9        yes,
10        and,
11        could_also: vec![],
12    })
13}
14
15pub fn yes_and_also<T, AlsoT: Display>(
16    yes: T,
17    and: &str,
18    also: impl IntoIterator<Item = AlsoT>,
19) -> UpResult<T> {
20    Ok(YesAnd {
21        yes,
22        and,
23        could_also: also.into_iter().map(|a| a.to_string()).collect(),
24    })
25}
26
27pub fn go_on<'any, AnyT, GoOnT: Display>(
28    suggestions: impl IntoIterator<Item = GoOnT>,
29) -> UpResult<'any, AnyT> {
30    Err(UpError::GoOn {
31        go_on: suggestions.into_iter().map(|g| g.to_string()).collect(),
32    })
33}
34
35pub fn oops<'any, AnyT, MessageT: Display>(message: MessageT) -> UpResult<'any, AnyT> {
36    Err(UpError::Oops {
37        message: message.to_string(),
38    })
39}
40
41macro_rules! oops {
42    ($tt:tt) => {
43        Err(UpError::Oops {
44            message: format!($tt),
45        })
46    };
47}
48
49/// ```
50/// use parse_up::util::strip_matching_prefixes;
51/// assert_eq!(
52///     strip_matching_prefixes("hel", ["hello", "helsinki"]),
53///     ["lo", "sinki"],
54/// );
55/// assert_eq!(
56///     strip_matching_prefixes("bar", ["bartender", "bar"]),
57///     ["tender"],
58/// );
59/// ```
60pub fn strip_matching_prefixes<'haystack>(
61    needle: &str,
62    haystack: impl IntoIterator<Item = &'haystack str>,
63) -> Vec<String> {
64    haystack
65        .into_iter()
66        .filter_map(|hay| {
67            hay.strip_prefix(needle).and_then(|s| match s {
68                "" => None,
69                _ => Some(s),
70            })
71        })
72        .map(String::from)
73        .collect()
74}
75
76/// What characters would need to be added to `haystack` to get to `needle`?
77/// returns [None] if wouldn't be possible.
78/// ```
79/// use parse_up::util::chars_needed_to_complete;
80/// assert_eq!(chars_needed_to_complete("tag", "ta" ), Some("g"));
81/// assert_eq!(chars_needed_to_complete("tag", ""   ), Some("tag"));
82/// assert_eq!(chars_needed_to_complete("tag", "tag"), Some("")); // Special case
83/// assert_eq!(chars_needed_to_complete("tag", "tar"), None);
84/// assert_eq!(chars_needed_to_complete(""   , "foo"), None);
85/// ```
86pub fn chars_needed_to_complete<'needle>(
87    needle: &'needle str,
88    haystack: &str,
89) -> Option<&'needle str> {
90    use itertools::EitherOrBoth::{Both, Left, Right};
91    for chars in needle.char_indices().zip_longest(haystack.chars()) {
92        match chars {
93            Both((_, l), r) if l == r => continue, // matched
94            Both(..) => return None,               // diverged
95            Left((ix, _)) => return Some(&needle[ix..]),
96            Right(_) => return None,
97        }
98    }
99    Some("") // all chars have matched
100}
101
102#[test]
103fn test_chars_needed_to_complete() {
104    assert_eq!(chars_needed_to_complete("tag", "ta"), Some("g"));
105    assert_eq!(chars_needed_to_complete("foo", ""), Some("foo"));
106    assert_eq!(chars_needed_to_complete("foo", "bar"), None);
107    assert_eq!(chars_needed_to_complete("foo", "food"), None);
108}