comment_strip/
lib.rs

1#[macro_use] 
2extern crate quick_error;
3
4mod c;
5mod shell;
6mod xml;
7mod blanklines;
8
9use std::io;
10
11quick_error! {
12    #[derive(Debug)]
13    pub enum AppError {
14        Io(err: io::Error) {
15            from()
16            cause(err)
17            description(err.description())
18        }
19        Other(s: &'static str) {
20            from()
21            description(s)
22        }
23    }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum CommentStyle {
28    C,
29    XML,
30    Shell
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct CommentMatch {
35    pub from: usize,
36    pub to: usize
37}
38
39pub trait Start {
40    fn start() -> Self;
41}
42
43pub trait End {
44    fn end() -> Self;
45}
46
47pub fn find_comments_impl<P, A, C, FT, FA>(input: &str, state_transition: FT, do_action: FA) 
48    -> Result<Vec<CommentMatch>, &'static str> 
49    where   P: Start + End + Copy + Eq,
50            A: Copy + Eq,
51            C: Start + Copy + Eq,
52            FT: Fn(P, Option<char>) -> (P, A),
53            FA: Fn(A, C, usize, Vec<CommentMatch>) 
54                -> Result<(C, Vec<CommentMatch>), &'static str> {
55    let mut matches = Vec::new();
56    let mut current_parse_state = P::start();
57    let mut current_comment_state = C::start();
58    let mut chars = input.chars();
59    let mut position = 0;
60    while current_parse_state != P::end() {
61        let current_char = chars.next();
62        let (next_parse_state, action) = 
63            state_transition(current_parse_state, current_char);
64        let (next_comment_state, next_matches) = 
65            do_action(action, current_comment_state, position, matches)?;
66        current_parse_state = next_parse_state;
67        current_comment_state = next_comment_state;
68        matches = next_matches;
69        position += 1;
70    }
71    Ok(matches)
72}
73
74fn find_comments(input: &str, style: &CommentStyle) -> Result<Vec<CommentMatch>, &'static str> {
75    match style {
76        &CommentStyle::C => c::find_comments(input),
77        &CommentStyle::Shell => shell::find_comments(input),
78        &CommentStyle::XML => xml::find_comments(input)
79    }
80}
81
82fn remove_matches(input: String, matches: Vec<CommentMatch>) -> Result<String, &'static str> {
83    let mut input = input;
84    let mut matches = matches;
85    matches.sort_by_key(|m| m.from);
86    /* must come before reversing */
87    check_sorted_matches(input.as_str(), &matches)?;
88    matches.reverse();
89    for m in matches {
90        input.drain((m.from)..(m.to));
91    }
92    Ok(input.to_owned())
93}
94
95fn check_sorted_matches(input: &str, matches: &Vec<CommentMatch>) -> Result<(), &'static str> {
96    if matches.iter().any(|m| m.from >= input.len() || m.to > input.len()) {
97        return Err("match out of range");
98    }
99    if matches.iter().zip(matches.iter().skip(1)).any(|(m, n)| m.to > n.from) {
100        return Err("matches overlapping");
101    }
102    Ok(())
103}
104
105pub fn strip_comments(data: String, style: CommentStyle, remove_blanks: bool) -> Result<String, &'static str> {
106    let comment_matches = find_comments(data.as_str(), &style)?;
107    let mut stripped = remove_matches(data, comment_matches)?;
108    if remove_blanks {
109        let blank_matches = blanklines::find_blanklines(stripped.as_str())?;
110        stripped = remove_matches(stripped, blank_matches)?;
111    }
112    Ok(stripped)
113}
114
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn removes_correctly() {
122        let s = "012345#789\n#abcd\nefghi#jkl\n".to_owned();
123        let matches = vec![
124            CommentMatch{from:6, to:10},
125            CommentMatch{from:11, to:16},
126            CommentMatch{from:22, to:26}];
127        let stripped = remove_matches(s, matches);
128        assert_eq!(Ok("012345\n\nefghi\n".to_owned()), stripped);
129    }
130
131    #[test]
132    fn remove_finds_overlapping() {
133        let s = "1234567890".to_owned();
134        let matches = vec![
135            CommentMatch{from:0, to:5},
136            CommentMatch{from:3, to:7}];
137        let checked = check_sorted_matches(s.as_str(), &matches);
138        assert!(checked.is_err());
139        let stripped = remove_matches(s, matches);
140        assert!(stripped.is_err());
141    }
142
143    #[test]
144    fn remove_finds_out_of_range() {
145        let s = "12345".to_owned();
146        let matches = vec![
147            CommentMatch{from:3, to:10},
148            CommentMatch{from:11, to:16}];
149        let checked = check_sorted_matches(s.as_str(), &matches);
150        assert!(checked.is_err());
151        let stripped = remove_matches(s, matches);
152        assert!(stripped.is_err());
153    }
154
155}