protest_extras/generators/
text.rs1use protest::{Generator, GeneratorConfig};
13use rand::Rng;
14
15#[derive(Debug, Clone)]
21pub struct AlphabeticGenerator {
22 min_len: usize,
23 max_len: usize,
24 lowercase_only: bool,
25 uppercase_only: bool,
26}
27
28impl AlphabeticGenerator {
29 pub fn new(min_len: usize, max_len: usize) -> Self {
31 Self {
32 min_len,
33 max_len,
34 lowercase_only: false,
35 uppercase_only: false,
36 }
37 }
38
39 pub fn lowercase(min_len: usize, max_len: usize) -> Self {
41 Self {
42 min_len,
43 max_len,
44 lowercase_only: true,
45 uppercase_only: false,
46 }
47 }
48
49 pub fn uppercase(min_len: usize, max_len: usize) -> Self {
51 Self {
52 min_len,
53 max_len,
54 lowercase_only: false,
55 uppercase_only: true,
56 }
57 }
58
59 fn generate_char(&self, rng: &mut dyn rand::RngCore) -> char {
60 let lowercase = b"abcdefghijklmnopqrstuvwxyz";
61 let uppercase = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
62
63 if self.lowercase_only {
64 lowercase[rng.r#gen_range(0..lowercase.len())] as char
65 } else if self.uppercase_only {
66 uppercase[rng.r#gen_range(0..uppercase.len())] as char
67 } else {
68 let all = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
70 all[rng.r#gen_range(0..all.len())] as char
71 }
72 }
73}
74
75impl Generator<String> for AlphabeticGenerator {
76 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
77 let len = rng.r#gen_range(self.min_len..=self.max_len);
78 (0..len).map(|_| self.generate_char(rng)).collect()
79 }
80
81 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
82 let mut shrinks = Vec::new();
83
84 if !value.is_empty() && self.min_len == 0 {
86 shrinks.push(String::new());
87 }
88
89 if value.len() > self.min_len {
91 shrinks.push(value.chars().take(self.min_len).collect());
92 }
93
94 if value.len() > self.min_len {
96 for i in 0..value.len().min(3) {
97 let mut chars: Vec<char> = value.chars().collect();
98 if !chars.is_empty() {
99 chars.remove(i);
100 let shrunk: String = chars.into_iter().collect();
101 if shrunk.len() >= self.min_len {
102 shrinks.push(shrunk);
103 }
104 }
105 }
106 }
107
108 Box::new(shrinks.into_iter())
109 }
110}
111
112#[derive(Debug, Clone)]
118pub struct AlphanumericGenerator {
119 min_len: usize,
120 max_len: usize,
121}
122
123impl AlphanumericGenerator {
124 pub fn new(min_len: usize, max_len: usize) -> Self {
126 Self { min_len, max_len }
127 }
128
129 fn generate_char(&self, rng: &mut dyn rand::RngCore) -> char {
130 let chars = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
131 chars[rng.r#gen_range(0..chars.len())] as char
132 }
133}
134
135impl Generator<String> for AlphanumericGenerator {
136 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
137 let len = rng.r#gen_range(self.min_len..=self.max_len);
138 (0..len).map(|_| self.generate_char(rng)).collect()
139 }
140
141 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
142 let mut shrinks = Vec::new();
143
144 if !value.is_empty() && self.min_len == 0 {
145 shrinks.push(String::new());
146 }
147
148 if value.len() > self.min_len {
149 shrinks.push(value.chars().take(self.min_len).collect());
150 }
151
152 if value.len() > self.min_len {
153 for i in 0..value.len().min(3) {
154 let mut chars: Vec<char> = value.chars().collect();
155 if !chars.is_empty() {
156 chars.remove(i);
157 let shrunk: String = chars.into_iter().collect();
158 if shrunk.len() >= self.min_len {
159 shrinks.push(shrunk);
160 }
161 }
162 }
163 }
164
165 Box::new(shrinks.into_iter())
166 }
167}
168
169#[derive(Debug, Clone)]
180pub struct IdentifierGenerator {
181 min_len: usize,
182 max_len: usize,
183}
184
185impl IdentifierGenerator {
186 pub fn new(min_len: usize, max_len: usize) -> Self {
188 Self { min_len, max_len }
189 }
190
191 fn generate_first_char(&self, rng: &mut dyn rand::RngCore) -> char {
192 let chars = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
194 chars[rng.r#gen_range(0..chars.len())] as char
195 }
196
197 fn generate_char(&self, rng: &mut dyn rand::RngCore) -> char {
198 let chars = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
200 chars[rng.r#gen_range(0..chars.len())] as char
201 }
202}
203
204impl Generator<String> for IdentifierGenerator {
205 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
206 let len = rng.r#gen_range(self.min_len.max(1)..=self.max_len.max(1));
207 let mut result = String::with_capacity(len);
208
209 result.push(self.generate_first_char(rng));
211
212 for _ in 1..len {
214 result.push(self.generate_char(rng));
215 }
216
217 result
218 }
219
220 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
221 let mut shrinks = Vec::new();
222
223 if value != "a" && self.min_len <= 1 {
225 shrinks.push("a".to_string());
226 }
227
228 if value != "x" && self.min_len <= 1 {
229 shrinks.push("x".to_string());
230 }
231
232 if value.len() > self.min_len {
234 shrinks.push(value.chars().take(self.min_len.max(1)).collect());
235 }
236
237 if value.len() > self.min_len.max(1) {
239 for i in 1..value.len().min(4) {
240 let mut chars: Vec<char> = value.chars().collect();
241 chars.remove(i);
242 let shrunk: String = chars.into_iter().collect();
243 if shrunk.len() >= self.min_len.max(1) {
244 shrinks.push(shrunk);
245 }
246 }
247 }
248
249 Box::new(shrinks.into_iter())
250 }
251}
252
253#[derive(Debug, Clone)]
264pub struct SentenceGenerator {
265 min_words: usize,
266 max_words: usize,
267}
268
269impl SentenceGenerator {
270 pub fn new(min_words: usize, max_words: usize) -> Self {
272 Self {
273 min_words,
274 max_words,
275 }
276 }
277
278 fn generate_word(&self, rng: &mut dyn rand::RngCore, is_first: bool) -> String {
279 let len = rng.r#gen_range(2..8);
280 let mut word = String::with_capacity(len);
281
282 for i in 0..len {
283 let c = if i == 0 && is_first {
284 (b'A' + rng.r#gen_range(0..26)) as char
286 } else {
287 (b'a' + rng.r#gen_range(0..26)) as char
289 };
290 word.push(c);
291 }
292
293 word
294 }
295}
296
297impl Generator<String> for SentenceGenerator {
298 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
299 let num_words = rng.r#gen_range(self.min_words.max(1)..=self.max_words.max(1));
300 let mut sentence = String::new();
301
302 for i in 0..num_words {
303 if i > 0 {
304 sentence.push(' ');
305 }
306 sentence.push_str(&self.generate_word(rng, i == 0));
307 }
308
309 sentence.push('.');
310 sentence
311 }
312
313 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
314 let mut shrinks = Vec::new();
315
316 if value != "A." && self.min_words <= 1 {
318 shrinks.push("A.".to_string());
319 }
320
321 let words: Vec<&str> = value.trim_end_matches('.').split_whitespace().collect();
323 if words.len() > self.min_words {
324 let shrunk_words = &words[..self.min_words.max(1)];
326 let mut shrunk = shrunk_words.join(" ");
327 if let Some(first_char) = shrunk.chars().next()
329 && first_char.is_lowercase()
330 {
331 let capitalized = first_char.to_uppercase().collect::<String>() + &shrunk[1..];
332 shrunk = capitalized;
333 }
334 shrunk.push('.');
335 shrinks.push(shrunk);
336 }
337
338 Box::new(shrinks.into_iter())
339 }
340}
341
342#[derive(Debug, Clone)]
350pub struct ParagraphGenerator {
351 min_sentences: usize,
352 max_sentences: usize,
353}
354
355impl ParagraphGenerator {
356 pub fn new(min_sentences: usize, max_sentences: usize) -> Self {
358 Self {
359 min_sentences,
360 max_sentences,
361 }
362 }
363}
364
365impl Generator<String> for ParagraphGenerator {
366 fn generate(&self, rng: &mut dyn rand::RngCore, config: &GeneratorConfig) -> String {
367 let num_sentences = rng.r#gen_range(self.min_sentences.max(1)..=self.max_sentences.max(1));
368 let sentence_gen = SentenceGenerator::new(3, 10);
369
370 let sentences: Vec<String> = (0..num_sentences)
371 .map(|_| sentence_gen.generate(rng, config))
372 .collect();
373
374 sentences.join(" ")
375 }
376
377 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
378 let mut shrinks = Vec::new();
379
380 if value != "A." && self.min_sentences <= 1 {
382 shrinks.push("A.".to_string());
383 }
384
385 let sentences: Vec<&str> = value.split(". ").collect();
387 if sentences.len() > self.min_sentences {
388 let shrunk_sentences = &sentences[..self.min_sentences.max(1)];
389 let mut shrunk = shrunk_sentences.join(". ");
390 if !shrunk.ends_with('.') {
391 shrunk.push('.');
392 }
393 shrinks.push(shrunk);
394 }
395
396 Box::new(shrinks.into_iter())
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use rand::thread_rng;
404
405 #[test]
406 fn test_alphabetic_generator() {
407 let generator = AlphabeticGenerator::new(5, 10);
408 let mut rng = thread_rng();
409 let config = GeneratorConfig::default();
410
411 for _ in 0..10 {
412 let text = generator.generate(&mut rng, &config);
413 assert!(text.len() >= 5 && text.len() <= 10);
414 assert!(text.chars().all(|c| c.is_alphabetic()));
415 }
416 }
417
418 #[test]
419 fn test_alphabetic_lowercase_only() {
420 let generator = AlphabeticGenerator::lowercase(5, 10);
421 let mut rng = thread_rng();
422 let config = GeneratorConfig::default();
423
424 for _ in 0..10 {
425 let text = generator.generate(&mut rng, &config);
426 assert!(text.chars().all(|c| c.is_lowercase()));
427 }
428 }
429
430 #[test]
431 fn test_alphanumeric_generator() {
432 let generator = AlphanumericGenerator::new(5, 10);
433 let mut rng = thread_rng();
434 let config = GeneratorConfig::default();
435
436 for _ in 0..10 {
437 let text = generator.generate(&mut rng, &config);
438 assert!(text.len() >= 5 && text.len() <= 10);
439 assert!(text.chars().all(|c| c.is_alphanumeric()));
440 }
441 }
442
443 #[test]
444 fn test_identifier_generator() {
445 let generator = IdentifierGenerator::new(3, 15);
446 let mut rng = thread_rng();
447 let config = GeneratorConfig::default();
448
449 for _ in 0..10 {
450 let id = generator.generate(&mut rng, &config);
451 assert!(id.len() >= 3 && id.len() <= 15);
452
453 let first = id.chars().next().unwrap();
455 assert!(first.is_alphabetic() || first == '_');
456
457 assert!(id.chars().all(|c| c.is_alphanumeric() || c == '_'));
459 }
460 }
461
462 #[test]
463 fn test_sentence_generator() {
464 let generator = SentenceGenerator::new(3, 8);
465 let mut rng = thread_rng();
466 let config = GeneratorConfig::default();
467
468 for _ in 0..10 {
469 let sentence = generator.generate(&mut rng, &config);
470
471 assert!(sentence.ends_with('.'));
473
474 assert!(sentence.chars().next().unwrap().is_uppercase());
476
477 let words: Vec<&str> = sentence.trim_end_matches('.').split_whitespace().collect();
479 assert!(words.len() >= 3 && words.len() <= 8);
480 }
481 }
482
483 #[test]
484 fn test_paragraph_generator() {
485 let generator = ParagraphGenerator::new(2, 5);
486 let mut rng = thread_rng();
487 let config = GeneratorConfig::default();
488
489 for _ in 0..5 {
490 let paragraph = generator.generate(&mut rng, &config);
491
492 let period_count = paragraph.matches('.').count();
494 assert!((2..=5).contains(&period_count));
495 }
496 }
497}