merge_whitespace_utils/
lib.rs1#![forbid(unsafe_code)]
31
32use std::borrow::Cow;
33
34pub fn merge_whitespace(input: &str) -> Cow<str> {
49 merge_whitespace_with_quotes(input, None, None)
50}
51
52pub fn merge_whitespace_with_quotes(
67 input: &str,
68 quote_char: Option<char>,
69 escape_char: Option<char>,
70) -> Cow<str> {
71 let trimmed_input = input.trim();
72 let mut result = None; let mut in_quotes = false;
74 let mut prev_char_was_space = false;
75 let mut in_escape = false;
76
77 for c in trimmed_input.chars() {
78 if escape_char == Some(c) && !in_escape {
79 if prev_char_was_space {
80 result
81 .get_or_insert_with(|| String::with_capacity(trimmed_input.len()))
82 .push(' ');
83 }
84 prev_char_was_space = false;
85 in_escape = true;
86 result
87 .get_or_insert_with(|| String::with_capacity(trimmed_input.len()))
88 .push(c);
89 continue;
90 }
91 if c.is_whitespace() && !in_quotes && !in_escape {
92 prev_char_was_space = true;
93 continue;
94 }
95 if quote_char == Some(c) && !in_escape {
96 in_quotes = !in_quotes;
97 }
98 if prev_char_was_space {
99 result
100 .get_or_insert_with(|| String::with_capacity(trimmed_input.len()))
101 .push(' ');
102 }
103 result
104 .get_or_insert_with(|| String::with_capacity(trimmed_input.len()))
105 .push(c);
106 prev_char_was_space = false;
107 in_escape = false;
108 }
109
110 match result {
111 Some(resulting_string) => Cow::Owned(resulting_string),
112 None => Cow::Borrowed(trimmed_input),
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 const QUOTE: Option<char> = Some('"');
121 const ESCAPE: Option<char> = Some('\\');
122
123 #[test]
124 fn whitespace_only_is_trimmed() {
125 assert_eq!(
126 merge_whitespace_with_quotes(" ", QUOTE, None),
127 Cow::Borrowed("")
128 );
129 assert_eq!(
130 merge_whitespace_with_quotes(" \n \t ", QUOTE, None),
131 Cow::Borrowed("")
132 );
133 }
134
135 #[test]
136 fn non_whitespace_is_ignored() {
137 assert_eq!(
138 merge_whitespace_with_quotes("abcdefgh.ihkl-", QUOTE, None),
139 Cow::Borrowed("abcdefgh.ihkl-")
140 );
141 }
142
143 #[test]
144 fn single_whitespace_in_text_is_kept() {
145 assert_eq!(
146 merge_whitespace_with_quotes("foo bar baz", QUOTE, None),
147 Cow::Borrowed("foo bar baz")
148 );
149 }
150
151 #[test]
152 fn multiple_whitespace_in_text_is_merged() {
153 assert_eq!(
154 merge_whitespace_with_quotes("foo bar\nbaz", QUOTE, None),
155 Cow::<str>::Owned(String::from("foo bar baz"))
156 );
157 }
158
159 #[test]
160 fn quoted_whitespace_in_text_is_kept() {
161 assert_eq!(
162 merge_whitespace_with_quotes("foo foobar \" bar\n\" baz", QUOTE, None),
163 Cow::<str>::Owned(String::from("foo foobar \" bar\n\" baz"))
164 );
165 }
166
167 #[test]
168 fn escape_a_space() {
169 assert_eq!(
170 merge_whitespace_with_quotes("what \\ if I quote\\ spaces", QUOTE, ESCAPE),
171 Cow::<str>::Owned(String::from("what \\ if I quote\\ spaces"))
172 );
173 }
174
175 #[test]
176 fn quoted_whitespace_with_escaped_quotes() {
177 assert_eq!(
178 merge_whitespace_with_quotes(
179 r#"foo foobar " \"bar \" " baz"#,
180 QUOTE,
181 ESCAPE
182 ),
183 Cow::<str>::Owned(String::from(r#"foo foobar " \"bar \" " baz"#))
184 );
185 }
186
187 #[test]
188 fn test_complex_escaped() {
189 let result = merge_whitespace_with_quotes(
190 r#"
191 query {
192 users (limit: 1, name: "Froozle '78\"' Frobnik") {
193 id
194 name
195 todos(order_by: {created_at: desc}, limit: 5) {
196 id
197 title
198 }
199 }
200 }
201 "#,
202 QUOTE,
203 ESCAPE,
204 );
205 assert_eq!(result, "query { users (limit: 1, name: \"Froozle '78\\\"' Frobnik\") { id name todos(order_by: {created_at: desc}, limit: 5) { id title } } }");
206 }
207
208 #[test]
209 fn test_complex_unescaped() {
210 let result = merge_whitespace_with_quotes(
211 r#"
212 query {
213 users (limit: 1, name: "Froozle Frobnik") {
214 id
215 name
216 todos(order_by: {created_at: desc}, limit: 5) {
217 id
218 title
219 }
220 }
221 }
222 "#,
223 QUOTE,
224 None,
225 );
226 assert_eq!(result, "query { users (limit: 1, name: \"Froozle Frobnik\") { id name todos(order_by: {created_at: desc}, limit: 5) { id title } } }");
227 }
228}