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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#![deny(missing_docs)]
//! Functions and macros to compare strings, ignoring whitespace

#[macro_export]
/// Compare two strings, collapse consecutive whitespace, and assert their equality.
/// Panics if two strings are not equivalent after collapsing whitespace.
///
/// ```
/// #[macro_use]
/// use collapse::*;
///
/// collapsed_eq!("one space", "one space");
/// 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));
    };
    ($input:expr, $output:expr, $($arg:tt)+) => {
        assert_eq!(collapse($input), collapse($output), $($arg)+);
    };
}

#[macro_export]
/// Compare two strings, collapse consecutive whitespace, and assert their inequality.
/// Panics if two strings are equivalent after collapsing whitespace.
///
/// ```
/// #[macro_use]
/// use collapse::*;
///
/// collapsed_ne!("one space", "ONE SPACE");
/// collapsed_ne!("two  spaces", "TWO SPACES");
/// collapsed_ne!("new\r\nlines", "NEW\nLINES");
/// collapsed_ne!(" lead \t tail \r", "LEAD TAIL");
/// ```
macro_rules! collapsed_ne {
    ($input:expr, $output:expr) => {
        assert_ne!(collapse($input), collapse($output));
    };
    ($input:expr, $output:expr, $($arg:tt)+) => {
        assert_ne!(collapse($input), collapse($output), $($arg)+);
    };
}

/// 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 )"#,
            "This is a panic message you should never see"
        );

        collapsed_ne!(
            "This is not the same...",
            "...as this string.",
            "You should never see this panic message either."
        );
    }

    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!(empty1, " ", "");
    test!(empty2, " ", "\n");
    test!(empty3, " ", "\r\n");
    test!(empty4, " ", " \t\t ");
    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");
}