buup/transformers/
slugify.rs1use crate::{Transform, TransformError, TransformerCategory};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Slugify;
6
7impl Transform for Slugify {
8 fn name(&self) -> &'static str {
9 "Slugify"
10 }
11
12 fn id(&self) -> &'static str {
13 "slugify"
14 }
15
16 fn description(&self) -> &'static str {
17 "Converts text into a URL-friendly slug (lowercase, dashes, removes special chars)"
18 }
19
20 fn category(&self) -> TransformerCategory {
21 TransformerCategory::Other
22 }
23
24 fn transform(&self, input: &str) -> Result<String, TransformError> {
25 if input.is_empty() {
26 return Ok(String::new());
27 }
28
29 let mut slug = String::with_capacity(input.len());
30 let mut last_char_was_dash = true; for c in input.chars() {
33 if c.is_ascii_alphanumeric() {
34 slug.push(c.to_ascii_lowercase());
35 last_char_was_dash = false;
36 } else if c.is_whitespace() || c == '-' || c == '_' {
37 if !last_char_was_dash {
38 slug.push('-');
39 last_char_was_dash = true;
40 }
41 } else {
42 }
46 }
47
48 if slug.ends_with('-') && slug.len() > 1 {
50 slug.pop();
51 }
52
53 if slug.is_empty() || slug == "-" {
56 return Ok(String::new()); }
58
59 Ok(slug)
60 }
61
62 fn default_test_input(&self) -> &'static str {
63 "This is a Test String! 123?"
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_slugify_basic() {
73 let transformer = Slugify;
74 assert_eq!(
75 transformer.transform(transformer.default_test_input()),
76 Ok("this-is-a-test-string-123".to_string())
77 );
78 assert_eq!(
79 transformer.transform("Hello World"),
80 Ok("hello-world".to_string())
81 );
82 }
83
84 #[test]
85 fn test_slugify_punctuation() {
86 let transformer = Slugify;
87 assert_eq!(
88 transformer.transform("Test! String?"),
89 Ok("test-string".to_string())
90 );
91 }
92
93 #[test]
94 fn test_slugify_uppercase() {
95 let transformer = Slugify;
96 assert_eq!(
97 transformer.transform("UPPERCASE"),
98 Ok("uppercase".to_string())
99 );
100 }
101
102 #[test]
103 fn test_slugify_consecutive_spaces() {
104 let transformer = Slugify;
105 assert_eq!(
106 transformer.transform("Multiple Spaces"),
107 Ok("multiple-spaces".to_string())
108 );
109 }
110
111 #[test]
112 fn test_slugify_leading_trailing_spaces() {
113 let transformer = Slugify;
114 assert_eq!(
115 transformer.transform(" Leading and Trailing "),
116 Ok("leading-and-trailing".to_string())
117 );
118 }
119
120 #[test]
121 fn test_slugify_leading_trailing_hyphens() {
122 let transformer = Slugify;
123 assert_eq!(
124 transformer.transform("-Hyphens-"),
125 Ok("hyphens".to_string())
126 ); assert_eq!(
128 transformer.transform(" Leading-Hyphen"),
129 Ok("leading-hyphen".to_string())
130 );
131 assert_eq!(
132 transformer.transform("Trailing-Hyphen- "),
133 Ok("trailing-hyphen".to_string())
134 );
135 }
136
137 #[test]
138 fn test_slugify_underscores() {
139 let transformer = Slugify;
140 assert_eq!(
141 transformer.transform("snake_case_string"),
142 Ok("snake-case-string".to_string())
143 );
144 }
145
146 #[test]
147 fn test_slugify_mixed() {
148 let transformer = Slugify;
149 assert_eq!(
150 transformer.transform(" Mixed CASE with Punctuations! and _underscores- "),
151 Ok("mixed-case-with-punctuations-and-underscores".to_string())
152 );
153 }
154
155 #[test]
156 fn test_slugify_empty() {
157 let transformer = Slugify;
158 assert_eq!(transformer.transform(""), Ok("".to_string()));
159 }
160
161 #[test]
162 fn test_slugify_only_special_chars() {
163 let transformer = Slugify;
164 assert_eq!(transformer.transform("!@#$%^"), Ok("".to_string()));
165 assert_eq!(transformer.transform(" - _ - "), Ok("".to_string()));
166 }
167
168 #[test]
169 fn test_slugify_non_ascii() {
170 let transformer = Slugify;
171 assert_eq!(
173 transformer.transform("Héllö Wörld"),
174 Ok("hll-wrld".to_string())
175 );
176 }
177}