1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#![deny(missing_docs)]
//! Functions and macros to compare strings, ignoring whitespace

#[macro_export]
/// Compare two strings, ignoring whitespace
/// ```
/// #[macro_use]
/// use collapse::*;
///
/// collapsed_eq!("two  spaces", "two spaces");
/// collapsed_eq!("new\r\nlines", "new\nlines");
/// collapsed_eq!(" lead \t tail \r", "lead tail");
/// ```
macro_rules! collapsed_eq {
    ($input:expr, $output:expr) => {
        assert_eq!(collapse($input), collapse($output));
    }
}

/// Trim leading and trailing whitespace and collapse all consecutive whitespace to a single space
/// character
/// ```
/// use collapse::collapse;
///
/// assert_eq!(collapse("two  spaces"), "two spaces");
/// assert_eq!(collapse("new\r\nlines"), "new lines");
/// assert_eq!(collapse("\t lead\t tail \r"), "lead tail");
/// ```
pub fn collapse(s: &str) -> String {
    let s = s.trim();
    let mut collapsed = String::new();

    for c in s.chars() {
        if let Some(last) = collapsed.chars().last() {
            if c.is_whitespace() {
                if !last.is_whitespace() {
                    collapsed.push(' ');
                }
            } else {
                collapsed.push(c);
            }
        } else {
            collapsed.push(c);
        }
    }

    collapsed
}

#[cfg(test)]
mod test {
    use crate::*;

    #[test]
    fn macro_test() {
        // https://www.reddit.com/r/learnrust/comments/yilsa1/a_macro_to_collapse_whitespace_into_single_space/
        collapsed_eq! (
            r#"CREATE TABLE "test" (
                "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT
            )"#,
            r#"CREATE TABLE "test" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT )"#
        );
    }

    macro_rules! test {
        ($(#[$attr:meta])* $name:ident, $input:expr, $output:expr) => {
            #[test]
            $(#[$attr])*
            fn $name() {
                collapsed_eq!($input, $output);
            }
        }
    }

    test!(two_spaces, "two  spaces", "two spaces");
    test!(space_tab, "space tab", "space	tab");
    test!(line_space, "line\r\nspace", "line    space");
    test!(new_lines, "new\r\n
        lines", "new lines");
    test!(tabs, "some	tabs\there", "some tabs here");
    test!(no_change, "no change", "no change");
    test!(all_whitespace, "\r\n\t ", "	\n\r\t\n ");
    test!(empty, "", "");
    test!(lead_tail, " lead \t tail \r", "lead tail");

    test!(#[should_panic] should_fail1, "should fail", "SHOULD FAIL");
    test!(#[should_panic] should_fail2, "should  fail", "SHOULD FAIL");
    test!(#[should_panic] should_fail3, "should\n fail", "SHOULD\n FAIL");
}