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 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}