1extern crate unidecode;
106use unidecode::unidecode;
107
108
109#[macro_export]
110macro_rules! slugify {
111 ($text:expr) => (
112 {
113 slugify($text, "", "-", None)
114 }
115 );
116
117 ($text:expr, stop_words=$stopwords:expr) => (
118 {
119 slugify($text, $stopwords, "-", None)
120 }
121 );
122
123 ($text:expr, separator=$sep:expr) => (
124 {
125 slugify($text, "", $sep, None)
126 }
127 );
128
129 ($text:expr, max_length=$len:expr) => (
130 {
131 slugify($text, "", "-", Some($len))
132 }
133 );
134
135 ($text:expr, stop_words=$stopwords:expr, separator=$sep:expr) => (
136 {
137 slugify($text, $stopwords, $sep, None)
138 }
139 );
140
141 ($text:expr, stop_words=$stopwords:expr, max_length=$len:expr) => (
142 {
143 slugify($text, $stopwords, "-", Some($len))
144 }
145 );
146
147 ($text:expr, separator=$sep:expr, max_length=$len:expr) => (
148 {
149 slugify($text, "", $sep, Some($len))
150 }
151 );
152
153 ($text:expr, stop_words=$stopwords:expr, separator=$sep:expr, max_length=$len:expr) => (
154 {
155 slugify($text, $stopwords, $sep, Some($len))
156 }
157 );
158
159
160}
161
162pub fn slugify(string: &str, stop_words: &str, sep: &str, max_length: Option<usize>) -> String {
163 let char_vec: Vec<char> = sep.chars().collect();
164 let mut string: String = unidecode(string.into())
165 .to_lowercase()
166 .trim()
167 .trim_matches(char_vec[0])
168 .replace(' ', &sep.to_string());
169
170 for word in stop_words.split(",") {
172 if !word.is_empty() {
173 string = string.replace(word, &sep.to_string());
174 }
175
176 }
177
178 let mut slug = Vec::with_capacity(string.len());
179
180 let mut is_sep = true;
181
182 for x in string.chars() {
183 match x {
184 'a'...'z' | '0'...'9' => {
185 is_sep = false;
186 slug.push(x as u8);
187 }
188 _ => {
189 if !is_sep {
190 is_sep = true;
191 slug.push(char_vec[0] as u8);
192 } else {
193 }
194 }
195 }
196 }
197
198 if slug.last() == Some(&(char_vec[0] as u8)) {
199 slug.pop();
200 }
201
202 let mut s = String::from_utf8(slug).unwrap();
203
204 match max_length {
205 Some(x) => {
206 s.truncate(x);
207 s = s.trim_right_matches(char_vec[0]).to_string();
208 }
209 None => {}
210 }
211
212 s
213
214}
215
216
217
218#[cfg(test)]
219mod tests {
220 use slugify;
221 #[test]
222 fn basic() {
223 assert_eq!(slugify("hello world", "", "-", None), "hello-world");
224 assert_eq!(slugify("hello world-", "", "-", None), "hello-world");
225 assert_eq!(slugify("hello world ", "", "-", None), "hello-world");
226 }
227
228 #[test]
229 fn test_email() {
230 assert_eq!(slugify!("alice@bob.com"), "alice-bob-com");
231 }
232
233 #[test]
234 fn test_starts_with_number() {
235 assert_eq!(slugify!("10 amazing secrets"), "10-amazing-secrets");
236 }
237
238 #[test]
239 fn test_contains_numbers() {
240 assert_eq!(slugify!("the 101 dalmatians"), "the-101-dalmatians");
241 }
242
243 #[test]
244 fn test_ends_with_number() {
245 assert_eq!(slugify!("lucky number 7"), "lucky-number-7");
246 }
247
248 #[test]
249 fn test_numbers_only() {
250 assert_eq!(slugify!("101"), "101");
251 }
252
253 #[test]
254 fn test_numbers_and_symbols() {
255 assert_eq!(slugify!("1000 reasons you are #1"),
256 "1000-reasons-you-are-1");
257 }
258
259 #[test]
260 fn test_stop_words() {
261 assert_eq!(slugify("hello world", "world", "-", None), "hello");
262 assert_eq!(slugify!("hello world", stop_words = "world"), "hello");
263 }
264
265 #[test]
266 fn test_differently_cased_stopword_match() {
267 assert_eq!(slugify("Foo A FOO B foo C", "foo", "-", None), "a-b-c");
268 }
269
270 #[test]
271 fn test_multiple_stop_words() {
272 assert_eq!(slugify("the quick brown fox jumps over the lazy dog",
273 "the",
274 "-",
275 None),
276 "quick-brown-fox-jumps-over-lazy-dog");
277 assert_eq!(slugify("the quick brown fox jumps over the lazy dog",
278 "the,fox",
279 "-",
280 None),
281 "quick-brown-jumps-over-lazy-dog");
282 assert_eq!(slugify!("the quick brown fox jumps over the lazy dog",
283 stop_words = "the,fox"),
284 "quick-brown-jumps-over-lazy-dog");
285 }
286
287 #[test]
288 fn test_stopwords_with_different_separator() {
289 assert_eq!(slugify("the quick brown fox jumps over the lazy dog",
290 "the",
291 " ",
292 None),
293 "quick brown fox jumps over lazy dog");
294 assert_eq!(slugify!("the quick brown fox jumps over the lazy dog",
295 stop_words = "the",
296 separator = " "),
297 "quick brown fox jumps over lazy dog");
298 }
299
300 #[test]
301 fn test_separator() {
302 assert_eq!(slugify("hello world", "", ".", None), "hello.world");
303 assert_eq!(slugify("hello world", "", "_", None), "hello_world");
304 assert_eq!(slugify!("hello world", separator = "_"), "hello_world");
305 }
306
307 #[test]
308 fn test_phonetic_conversion() {
309 assert_eq!(slugify("影師嗎", "", "-", None), "ying-shi-ma");
310 }
311
312 #[test]
313 fn test_accented_text() {
314 assert_eq!(slugify("Æúű--cool?", "", "-", None), "aeuu-cool");
315 assert_eq!(slugify("Nín hǎo. Wǒ shì zhōng guó rén", "", "-", None),
316 "nin-hao-wo-shi-zhong-guo-ren");
317 }
318
319 #[test]
320 fn test_accented_text_non_word_chars() {
321 assert_eq!(slugify!("jaja---lol-méméméoo--a"), "jaja-lol-mememeoo-a");
322 }
323
324 #[test]
325 fn test_cyrillic_text() {
326 assert_eq!(slugify!("Компьютер"), "komp-iuter");
327 }
328
329 #[test]
330 fn test_macro() {
331 assert_eq!(slugify!("Компьютер"), "komp-iuter");
332 assert_eq!(slugify!("hello world", separator = "-"), "hello-world");
333 assert_eq!(slugify!("hello world", separator = " "), "hello world");
334 assert_eq!(slugify!("hello world", max_length = 5), "hello");
335 assert_eq!(slugify!("hello world", max_length = 6), "hello");
336 assert_eq!(slugify!("hello world", separator = " ", max_length = 8),
337 "hello wo");
338 assert_eq!(slugify!("hello world", separator = "x", max_length = 8),
339 "helloxwo");
340 assert_eq!(slugify!("the hello world", stop_words = "the", separator = "-"),
341 "hello-world");
342 assert_eq!(slugify!("the hello world", stop_words = "the", max_length = 5),
343 "hello");
344 assert_eq!(slugify!("the hello world",
345 stop_words = "the",
346 separator = "-",
347 max_length = 10),
348 "hello-worl");
349 assert_eq!(slugify!("the hello world",
350 stop_words = "the",
351 separator = "-",
352 max_length = 20),
353 "hello-world");
354 }
355}