1extern crate yaml_rust;
2
3use yaml_rust::{Yaml, YamlLoader};
4
5pub struct YogurtYaml<'a> {
8 ident_checks: Vec<IdentChecker<'a>>,
9 results: Vec<Result>,
10}
11
12pub struct Result {
14 text: String,
15 start: usize,
16 end: usize,
17}
18
19impl Result {
21 pub fn get_text(&self) -> &String {
23 &self.text
24 }
25 pub fn get_print(&self) -> String {
27 let mut result = self.text.clone();
28 result.push_str(" at ");
29 result.push_str(&self.start.to_string());
30 result.push_str(" -> ");
31 result.push_str(&self.end.to_string());
32 result
33 }
34 pub fn get_yaml(&self) -> Vec<Yaml> {
36 YamlLoader::load_from_str(&self.text).unwrap()
37 }
38
39 pub fn get_start(&self) -> usize {
40 self.start
41 }
42
43 pub fn get_end(&self) -> usize {
44 self.end
45 }
46
47 pub fn new(text: String, start: usize, end: usize) -> Result {
48 Result { text, start, end }
49 }
50}
51
52pub struct Indicators<'a> {
53 ident_strings: &'a [&'a str],
54 range: IdentRange,
55}
56
57impl<'a> Indicators<'a> {
58 pub fn new(ident_strings: &'a [&'a str], range: IdentRange) -> Indicators<'a> {
59 Indicators {
60 ident_strings,
61 range,
62 }
63 }
64}
65
66impl<'a> YogurtYaml<'a> {
68 pub fn new(indicator_lists: &'a [Indicators]) -> YogurtYaml<'a> {
70 let mut ident_checks = Vec::new();
71 for indicator_list in indicator_lists {
72 ident_checks.extend(create_ident_checks(
73 indicator_list.ident_strings,
74 indicator_list.range,
75 ));
76 }
77 let results = Vec::new();
78 YogurtYaml {
79 ident_checks,
80 results,
81 }
82 }
83
84 pub fn new_from_str(indicators: &'a [&'a str]) -> YogurtYaml<'a> {
86 let ident_checks = create_ident_checks(indicators, IdentRange::Brackets);
87 let results = Vec::new();
88 YogurtYaml {
89 ident_checks,
90 results,
91 }
92 }
93
94 pub fn curt(&mut self, s: &str) {
97 self.results
98 .extend(cut_yaml_unchecked(&mut self.ident_checks, s));
99 }
100
101 pub fn curt_clear(&mut self, s: &mut String) {
103 self.results.extend(cut_yaml(&mut self.ident_checks, s));
104 if !self.reset_open() {
105 s.clear();
106 }
107 }
108
109 pub fn get_results(&self) -> &Vec<Result> {
111 &self.results
112 }
113
114 pub fn clear_results(&mut self) {
116 self.results.clear();
117 }
118
119 pub fn is_open(&self) -> bool {
121 for ident_check in &self.ident_checks {
122 if ident_check.semantic_position != SemanticPosition::Out {
123 return true;
124 }
125 }
126 false
127 }
128
129 pub fn reset(&mut self) {
131 for ident_check in &mut self.ident_checks {
132 reset(ident_check);
133 }
134 self.clear_results();
135 }
136
137 pub fn reset_open(&mut self) -> bool {
139 let mut result = false;
140 for ident_check in &mut self.ident_checks {
141 if ident_check.semantic_position != SemanticPosition::Out {
142 reset(ident_check);
143 result = true;
144 }
145 }
146 result
147 }
148}
149
150struct IdentChecker<'a> {
152 range: IdentRange,
153 ident: &'a str,
154 first_char: char,
155 begin_char: char,
156 end_char: char,
157 semantic_position: SemanticPosition,
159 length: usize,
160 closures: i32,
161}
162
163#[derive(PartialEq)]
164enum SemanticPosition {
165 Out,
166 Ident,
167 In,
168 InSingleQuote,
169 InDoubleQuote,
170 InSingleQuoteEscaped,
171 InDoubleQuoteEscaped,
172 Done,
173}
174
175fn check_out(ident_check: &mut IdentChecker, c: char) {
176 if c == ident_check.first_char {
177 ident_check.length = 1;
178 ident_check.semantic_position = SemanticPosition::Ident;
179 }
180}
181
182fn clean_up(ident_check: &mut IdentChecker, c: char) {
183 reset(ident_check);
184 check_out(ident_check, c); }
186
187fn reset(ident_check: &mut IdentChecker) {
188 ident_check.length = 0;
189 ident_check.closures = 0;
190 ident_check.semantic_position = SemanticPosition::Out;
191}
192
193fn check_ident(ident_check: &mut IdentChecker, c: char) {
194 let check_size = ident_check.ident.len() < ident_check.length;
195 if check_size {
196 if ident_check.begin_char == c {
197 ident_check.semantic_position = SemanticPosition::In;
198 ident_check.closures = 1;
199 } else {
200 clean_up(ident_check, c);
201 }
202 } else if c
203 != ident_check
204 .ident
205 .chars()
206 .nth(ident_check.length - 1)
207 .unwrap()
208 {
209 clean_up(ident_check, c);
210 }
211}
212
213fn check_ident_tag(ident_check: &mut IdentChecker, c: char) {
214 if c == ident_check.begin_char {
215 ident_check.semantic_position = SemanticPosition::In;
216 } else if c == ' ' || c == '\n' || c == ',' || c == '.' {
217 if ident_check.length > 2 {
218 ident_check.semantic_position = SemanticPosition::Done;
219 } else {
220 reset(ident_check);
221 }
222 } else if c == ident_check.first_char {
223 ident_check.length = 1;
224 ident_check.semantic_position = SemanticPosition::Ident;
225 }
226}
227
228fn check_in(ident_check: &mut IdentChecker, c: char) {
229 let begin = ident_check.begin_char;
230 let end = ident_check.end_char;
231 if c == end {
232 ident_check.closures -= 1;
233 check_end(ident_check);
234 } else if c == begin {
235 ident_check.closures += 1;
236 } else if c == '\'' {
237 ident_check.semantic_position = SemanticPosition::InSingleQuote;
238 } else if c == '"' {
239 ident_check.semantic_position = SemanticPosition::InDoubleQuote;
240 }
241}
242
243fn check_single_quote(ident_check: &mut IdentChecker, c: char) {
244 if c == '\'' {
245 ident_check.semantic_position = SemanticPosition::In;
246 } else if c == '\\' {
247 ident_check.semantic_position = SemanticPosition::InSingleQuoteEscaped;
248 }
249}
250
251fn check_double_quote(ident_check: &mut IdentChecker, c: char) {
252 if c == '"' {
253 ident_check.semantic_position = SemanticPosition::In;
254 } else if c == '\\' {
255 ident_check.semantic_position = SemanticPosition::InDoubleQuoteEscaped;
256 }
257}
258
259fn check_end(ident_check: &mut IdentChecker) {
260 if ident_check.closures == 0 {
261 ident_check.semantic_position = SemanticPosition::Done;
262 }
263}
264
265fn add_result(results: &mut Vec<Result>, ident_check: &mut IdentChecker, s: &str, i: usize) {
266 let end = i - 1;
267 let length;
268 match ident_check.length.checked_sub(2) {
269 Some(a) => length = a,
270 None => length = 0,
271 }; let start = end.checked_sub(length).unwrap();
273
274 let mut text: String = s.chars().skip(start).take(length).collect();
275 text = text.replacen(ident_check.begin_char, ": ", 1);
276 text.insert(0, '{');
277 text.push('}');
278 results.push(Result { text, start, end });
279}
280
281pub fn cut_yaml_ident_strings(ident_strings: &[&str], s: &str) -> Vec<Result> {
282 let mut ident_checks = create_ident_checks(ident_strings, IdentRange::Brackets);
283 cut_yaml(&mut ident_checks, s)
284}
285
286fn check_ident_checks(ident_checks: &mut Vec<IdentChecker>, s: &str, results: &mut Vec<Result>) {
287 for ident_check in ident_checks {
288 ident_check.length += 1;
289 if ident_check.semantic_position == SemanticPosition::Done
290 || (ident_check.range == IdentRange::Tag
291 && ident_check.semantic_position != SemanticPosition::Out)
292 {
293 add_result(results, ident_check, s, s.len());
294 }
295 }
296}
297
298#[derive(Copy, Clone, PartialEq)]
299pub enum IdentRange {
300 Tag,
301 Brackets,
302 Closures,
303 Crickets,
304 Rounds,
305}
306
307fn create_ident_checks<'a>(ident_strings: &'a [&'a str], range: IdentRange) -> Vec<IdentChecker> {
308 let mut ident_checks = Vec::new();
309 let begin_char;
310 let end_char;
311
312 match range {
313 IdentRange::Closures => {
314 begin_char = '{';
315 end_char = '}';
316 }
317 IdentRange::Brackets => {
318 begin_char = '[';
319 end_char = ']';
320 }
321 IdentRange::Crickets => {
322 begin_char = '<';
323 end_char = '>';
324 }
325 IdentRange::Rounds => {
326 begin_char = '(';
327 end_char = ')';
328 }
329 IdentRange::Tag => {
330 begin_char = ':';
331 end_char = '\n';
332 }
333 }
334
335 for ident in ident_strings {
336 ident_checks.push(IdentChecker {
337 range,
338 ident,
339 first_char: ident.chars().nth(0).unwrap(),
340 begin_char,
341 end_char,
342 semantic_position: SemanticPosition::Out,
343 length: 0,
344 closures: 0,
345 });
346 }
347 ident_checks
348}
349
350fn cut_yaml(ident_checks: &mut Vec<IdentChecker>, s: &str) -> Vec<Result> {
351 let mut results = cut_yaml_unchecked(ident_checks, s);
352 check_ident_checks(ident_checks, s, &mut results);
353 results
354}
355
356fn cut_yaml_unchecked(ident_checks: &mut Vec<IdentChecker>, s: &str) -> Vec<Result> {
357 let mut results = Vec::new();
358 for (i, c) in s.chars().enumerate() {
359 for ident_check in &mut *ident_checks {
360 ident_check.length += 1;
361 match ident_check.semantic_position {
362 SemanticPosition::Out => {
363 check_out(ident_check, c);
364 }
365 SemanticPosition::Ident => {
366 if ident_check.range == IdentRange::Tag {
367 check_ident_tag(ident_check, c);
368 } else {
369 check_ident(ident_check, c);
370 }
371 }
372 SemanticPosition::In => {
373 if c == ident_check.end_char && ident_check.range == IdentRange::Tag {
374 ident_check.semantic_position = SemanticPosition::Done;
375 } else {
376 check_in(ident_check, c);
377 }
378 }
379 SemanticPosition::InSingleQuote => {
380 check_single_quote(ident_check, c);
381 }
382 SemanticPosition::InDoubleQuote => {
383 check_double_quote(ident_check, c);
384 }
385 SemanticPosition::InSingleQuoteEscaped => {
386 ident_check.semantic_position = SemanticPosition::InSingleQuote;
387 }
388 SemanticPosition::InDoubleQuoteEscaped => {
389 ident_check.semantic_position = SemanticPosition::InDoubleQuote;
390 }
391 SemanticPosition::Done => {
392 add_result(&mut results, ident_check, s, i);
393 reset(ident_check);
394 check_out(ident_check, c);
395 }
396 }
397 }
398 }
399 results
400}
401
402#[cfg(test)]
403mod tests {
404 use crate::cut_yaml_ident_strings;
405
406 #[test]
407 fn test_cut_yaml() {
408 let result = cut_yaml_ident_strings(&["ID"], &"ID[Test]".to_string());
409 assert_eq!(result.len(), 1);
410 }
411
412 #[test]
413 fn test_cut_yaml_distraction() {
414 let result = cut_yaml_ident_strings(
415 &["ID"],
416 &"other stuff ID[Test, TestContent: 3] more stuff".to_string(),
417 );
418 assert_eq!(result.len(), 1);
419 assert_eq!(result[0].text, "{ID: Test, TestContent: 3}");
420 assert_eq!(result[0].start, 12);
421 assert_eq!(result[0].end, 35);
422 }
423
424 #[test]
425 fn test_cut_yaml_ident_strings_distraction() {
426 let result = cut_yaml_ident_strings(
427 &["ID"],
428 &"other stuff ID[Test, TestContent: 3] more stuff".to_string(),
429 );
430 assert_eq!(result.len(), 1);
431 assert_eq!(result[0].text, "{ID: Test, TestContent: 3}");
432 assert_eq!(result[0].start, 12);
433 assert_eq!(result[0].end, 35);
434 }
435
436 #[test]
437 fn test_cut_yaml_multiple_entries() {
438 let result = cut_yaml_ident_strings(&["ID"], &"other stuff ID[Test, TestContent: 3] more\n ID[Test2, TestContent: 4] stuID[Test3, TestContent: a7ad]ff".to_string());
439 assert_eq!(result.len(), 3);
440 assert_eq!(result[0].text, "{ID: Test, TestContent: 3}");
441 assert_eq!(result[1].text, "{ID: Test2, TestContent: 4}");
442 assert_eq!(result[2].text, "{ID: Test3, TestContent: a7ad}");
443 }
444
445 #[test]
446 fn test_cut_yaml_multiple_entries2() {
447 let result = cut_yaml_ident_strings(&["ID"], &"other stuff ID[Test, TestContent: 3] more\n ID[Test2, TestContent: 4] stuID[Test3, TestContent: a7ad]ff".to_string());
448 assert_eq!(result.len(), 3);
449 assert_eq!(result[0].text, "{ID: Test, TestContent: 3}");
450 assert_eq!(result[1].text, "{ID: Test2, TestContent: 4}");
451 assert_eq!(result[2].text, "{ID: Test3, TestContent: a7ad}");
452 }
453
454 #[test]
455 fn test_cut_yaml_multiple_lines() {
456 let result = cut_yaml_ident_strings(&["ID"], &"other stuff ID[Test, \nTestContent: 3] more\n ID[Test2, \nTestContent: 4\n] stuID[Test3, TestContent: a7ad]ff".to_string());
457 assert_eq!(result.len(), 3);
458 assert_eq!(result[0].text, "{ID: Test, \nTestContent: 3}");
459 assert_eq!(result[0].start, 12);
460 assert_eq!(result[0].end, 36);
461 assert_eq!(result[1].text, "{ID: Test2, \nTestContent: 4\n}");
462 assert_eq!(result[2].text, "{ID: Test3, TestContent: a7ad}");
463 }
464
465 #[test]
466 fn test_cut_yaml_many_id_multiple_entries() {
467 let result = cut_yaml_ident_strings(&["ID", "REF", "ADD"], &"other stuff ID[Test, TestContent: 3] more\n REF[Test, TestContent: 4] stuADD[Test3, TestContent: a7ad]ff".to_string());
468 assert_eq!(result.len(), 3);
469 assert_eq!(result[0].text, "{ID: Test, TestContent: 3}");
470 assert_eq!(result[1].text, "{REF: Test, TestContent: 4}");
471 assert_eq!(result[2].text, "{ADD: Test3, TestContent: a7ad}");
472 }
473
474 #[test]
475 fn test_cut_yaml_nested() {
476 let result = cut_yaml_ident_strings(&["ID", "REF", "ADD"], &"other stuff ID[Test, \nTestContent: 3] more\n REF[Test2, \nTestContent: [4]\n] stuADD[Test3, TestContent: [[a,7],[a,d]]]ff".to_string());
477 assert_eq!(result.len(), 3);
478 assert_eq!(result[0].text, "{ID: Test, \nTestContent: 3}");
479 assert_eq!(result[1].text, "{REF: Test2, \nTestContent: [4]\n}");
480 assert_eq!(result[2].text, "{ADD: Test3, TestContent: [[a,7],[a,d]]}");
481 }
482
483 #[test]
484 fn test_cut_yaml_escaped() {
485 let result = cut_yaml_ident_strings(&["ID", "REF", "ADD"], &r#"other stuff ID[Test, \nTestContent: ']3]]'] more\n REF[Test2, \nTestContent: [4]\n] stuADD[Test3, TestContent: [[a,7],[a,d]]]ff"#.to_string());
486 assert_eq!(result.len(), 3);
487 assert_eq!(result[0].text, r#"{ID: Test, \nTestContent: ']3]]'}"#);
488 assert_eq!(result[1].text, r#"{REF: Test2, \nTestContent: [4]\n}"#);
489 assert_eq!(
490 result[2].text,
491 r#"{ADD: Test3, TestContent: [[a,7],[a,d]]}"#
492 );
493 }
494
495 #[test]
496 fn test_cut_yaml_ident_strings_escaped() {
497 let result = cut_yaml_ident_strings(&["ID", "REF", "ADD"], &"other stuff ID[Test, \nTestContent: ']3]]'] more\n REF[Test2, \nTestContent: [\"4\"]\n] stuADD[Test3, TestContent: [[a,7],[a,d]]]ff".to_string());
498 assert_eq!(result.len(), 3);
499 assert_eq!(result[0].text, "{ID: Test, \nTestContent: ']3]]'}");
500 assert_eq!(result[1].text, "{REF: Test2, \nTestContent: [\"4\"]\n}");
501 assert_eq!(
502 result[2].text,
503 r#"{ADD: Test3, TestContent: [[a,7],[a,d]]}"#
504 );
505 }
506
507 #[test]
508 fn test_cut_yaml_ident_strings_fix() {
509 let result = cut_yaml_ident_strings(
510 &["ID", "REF"],
511 &r#"- ID[REQ, caption: "Requirements"]"#.to_string(),
512 );
513 assert_eq!(result.len(), 1);
514 assert_eq!(result[0].text, r#"{ID: REQ, caption: "Requirements"}"#);
515 }
516
517 use crate::YogurtYaml;
518 #[test]
519 fn test_curt() {
520 let test_data = &mut r#"other stuff ID[Test, \nTestContent: ']3]]'] more\n REF[Test2, \nTestContent: ["4"]\n] stuADD[Test3, TestContent: [[a,7],[a,d]]]"#.to_string();
521 let mut curt = YogurtYaml::new_from_str(&["ID", "REF", "ADD"]);
522 let result = curt.get_results();
523 assert_eq!(result.len(), 0);
524 curt.curt_clear(test_data);
525 let result = curt.get_results();
526 assert_eq!(result[0].text, r#"{ID: Test, \nTestContent: ']3]]'}"#);
527 assert_eq!(result[1].text, r#"{REF: Test2, \nTestContent: ["4"]\n}"#);
528 assert_eq!(
529 result[2].text,
530 r#"{ADD: Test3, TestContent: [[a,7],[a,d]]}"#
531 );
532 }
533
534 use crate::IdentRange;
535 use crate::Indicators;
536 #[test]
537 fn test_tags() {
538 let test_data =
539 &mut "other stuff #Test,\n @more\n\n #Test2 @TestContent: more content\n".to_string();
540 let mut indicator_lists = Vec::new();
541 indicator_lists.push(Indicators::new(&["#", "@"], IdentRange::Tag));
542 let mut curt = YogurtYaml::new(&indicator_lists);
543 let result = curt.get_results();
544 assert_eq!(result.len(), 0);
545 curt.curt_clear(test_data);
546 let result = curt.get_results();
547 assert_eq!(result.len(), 4);
548 assert_eq!(result[0].text, r#"{#Test}"#);
549 assert_eq!(result[1].text, r#"{@more}"#);
550 assert_eq!(result[2].text, r#"{#Test2}"#);
551 assert_eq!(result[3].text, r#"{@TestContent: more content}"#);
552 }
553
554 #[test]
555 fn test_tags_empty() {
556 let test_data =
557 &mut "other stuff # Test,\n @ more\n\n ## Test2 @@ TestContent: more content\n"
558 .to_string();
559 let mut indicator_lists = Vec::new();
560 indicator_lists.push(Indicators::new(&["#", "@"], IdentRange::Tag));
561 let mut curt = YogurtYaml::new(&indicator_lists);
562 curt.curt_clear(test_data);
563 let result = curt.get_results();
564 assert_eq!(result.len(), 0);
565 }
566
567 #[test]
568 fn test_curt_aggregate() {
569 let test_data_part_a =
570 &mut r#"other stuff ID[Test, \nTestContent: ']3]]'] more\n"#.to_string();
571 let test_data_part_b = &mut r#"REF[Test2, \nTestContent: ["4"]\n] stuADD[Test3, TestContent: [[a,7],[a,d]]]ff"#.to_string();
572 let mut curt = YogurtYaml::new_from_str(&["ID", "REF", "ADD"]);
573 let result = curt.get_results();
574 assert_eq!(result.len(), 0);
575 curt.curt(test_data_part_a);
576 let result = curt.get_results();
577 assert_eq!(result.len(), 1);
578 curt.curt_clear(test_data_part_b);
579 let result = curt.get_results();
580 assert_eq!(result[0].text, r#"{ID: Test, \nTestContent: ']3]]'}"#);
581 assert_eq!(result[1].text, r#"{REF: Test2, \nTestContent: ["4"]\n}"#);
582 assert_eq!(
583 result[2].text,
584 r#"{ADD: Test3, TestContent: [[a,7],[a,d]]}"#
585 );
586 }
587
588 #[test]
590 fn test_curt_aggregate_multiline_id() {
591 let test_data_part_a = &mut r#"other stuff ID[Test, \n"#.to_string();
592 let test_data_part_b = &mut r#"TestContent: ']3]]'] more\n"#.to_string();
593 let test_data_part_c = &mut r#"REF[Test2, \nTestContent: ["4"]\n] stuADD[Test3, TestContent: [[a,7],[a,d]]]ff"#.to_string();
594 let mut curt = YogurtYaml::new_from_str(&["ID", "REF", "ADD"]);
595 let result = curt.get_results();
596 assert_eq!(result.len(), 0);
597 curt.curt_clear(test_data_part_a);
598 let mut data = test_data_part_a.to_owned() + test_data_part_b;
599 curt.curt_clear(&mut data);
600 let result = curt.get_results();
601 assert_eq!(result.len(), 1);
602 curt.curt_clear(test_data_part_c);
603 let result = curt.get_results();
604 assert_eq!(result[0].text, r#"{ID: Test, \nTestContent: ']3]]'}"#);
605 assert_eq!(result[1].text, r#"{REF: Test2, \nTestContent: ["4"]\n}"#);
606 assert_eq!(
607 result[2].text,
608 r#"{ADD: Test3, TestContent: [[a,7],[a,d]]}"#
609 );
610 }
611}