libcurt/
lib.rs

1extern crate yaml_rust;
2
3use yaml_rust::{Yaml, YamlLoader};
4
5// ID[IMPL::yaml-extraction::]
6/// Contains identifier checks and results from usage
7pub struct YogurtYaml<'a> {
8    ident_checks: Vec<IdentChecker<'a>>,
9    results: Vec<Result>,
10}
11
12/// Results found via extraction from strings
13pub struct Result {
14    text: String,
15    start: usize,
16    end: usize,
17}
18
19/// Access results via convenient functions
20impl Result {
21    /// return results as proper yaml string
22    pub fn get_text(&self) -> &String {
23        &self.text
24    }
25    /// return results with additional information
26    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    /// return results as vector of yaml struct
35    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
66/// Implements YogurtYaml functions
67impl<'a> YogurtYaml<'a> {
68    /// Create a new curt instance
69    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    /// Create a new curt instance
85    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    // ID[IMPL::Multiline_Support, implements: REQ::Multi_Line]
95    /// Extract yaml from string
96    pub fn curt(&mut self, s: &str) {
97        self.results
98            .extend(cut_yaml_unchecked(&mut self.ident_checks, s));
99    }
100
101    /// Extracts yaml and clears string if not open
102    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    /// Return results
110    pub fn get_results(&self) -> &Vec<Result> {
111        &self.results
112    }
113
114    /// Clear the list of results
115    pub fn clear_results(&mut self) {
116        self.results.clear();
117    }
118
119    /// Checks whether there is any not `SemanticPosition::Out` containing `ident_check` in the list of `ident_checks`
120    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    /// Clears results and resets all `ident_checks`
130    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    /// Resets all `ident_checks' and returns according to `self.is_open()`
138    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
150/// Enables extraction of yaml data defined by identifiers and closures
151struct IdentChecker<'a> {
152    range: IdentRange,
153    ident: &'a str,
154    first_char: char,
155    begin_char: char,
156    end_char: char,
157    // mut:
158    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); // Could be the start of a ident
185}
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    }; // TODO: Minus 2 is a bit odd ..
272    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    // ID[TEST_Multiline, tests: RQM_Multiline]
589    #[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}