opencv_binding_generator/
comment.rs1use crate::string_ext::Indent;
2use crate::StrExt;
3
4pub fn strip_doxygen_comment_markers(comment: &str) -> String {
5 const MULTILINE_PREFIX: &str = "/*";
7 const MULTILINE_CONT: &str = "*";
8 const MULTILINE_SUFFIX: &str = "*/";
9 const SINGLELINE: &str = "//";
10 const DETAIL: &str = "!";
11 const SINGLELINE_DETAIL: &str = "/";
12 const SINGLELINE_SIDE: &str = "<";
13
14 fn trim_last_empty_lines(lines: &mut Vec<&str>) {
15 while lines.last().is_some_and(|line| line.is_empty()) {
16 lines.pop();
17 }
18 }
19
20 let mut comment_type = CommentType::SingleLineDelimited;
21 let mut lines = Vec::with_capacity(128);
22 for (i, mut line) in comment.lines_with_nl().enumerate() {
28 let mut line_trimmed = line.trim_start();
29 if i == 0 {
30 if let Some(new_line) = line_trimmed.strip_prefix(MULTILINE_PREFIX) {
31 line = new_line;
32 if let Some(new_line) = line.strip_prefix(MULTILINE_CONT) {
33 line = new_line;
34 comment_type = CommentType::MultilineAsteriskPrefixed;
35 } else {
36 comment_type = CommentType::MultilineWithoutAsteriskPrefix;
37 }
38 line_trimmed = line.trim_start();
39 }
40 } else if let Some(line_clean) = line_trimmed.strip_prefix(MULTILINE_PREFIX) {
41 line = line_clean
42 .trim_start_matches(DETAIL)
43 .trim_start_matches(MULTILINE_CONT)
44 .trim_start();
45 }
46 if comment_type == CommentType::SingleLineDelimited {
47 if let Some(new_line) = line_trimmed.strip_prefix(SINGLELINE) {
48 line = new_line
49 .strip_prefix(SINGLELINE_DETAIL)
50 .or_else(|| new_line.strip_prefix(DETAIL))
51 .unwrap_or(new_line);
52 line = line.strip_prefix(SINGLELINE_SIDE).unwrap_or(line);
53 }
54 } else if i == 1 && comment_type == CommentType::MultilineAsteriskPrefixed && !line_trimmed.starts_with(MULTILINE_CONT) {
55 comment_type = CommentType::MultilineWithoutAsteriskPrefix;
56 }
57 lines.push(line);
58 }
59 trim_last_empty_lines(&mut lines);
60 if let Some(last_line) = lines.last_mut() {
62 if comment_type != CommentType::SingleLineDelimited {
63 *last_line = last_line.trim_end();
64 if let Some(new_line) = last_line.strip_suffix(MULTILINE_SUFFIX) {
65 *last_line = new_line.trim_end();
66 }
67 }
68 }
69 trim_last_empty_lines(&mut lines);
70 let mut first_line_indent = None;
74 let mut common_indent: Option<Indent> = None;
75 for line in &mut lines {
76 if comment_type == CommentType::MultilineAsteriskPrefixed {
77 let line_trimmed = line.trim_start();
78 if let Some(line_trimmed) = line_trimmed.strip_prefix(MULTILINE_CONT) {
79 *line = line_trimmed;
80 } else {
81 let trim_start = line.trim_start_idx().min(2);
82 *line = &line[trim_start..];
83 }
84 }
85 if first_line_indent.is_none() {
86 first_line_indent = Some(line.detect_indent());
87 } else {
88 let detected_indent = line.detect_indent();
89 if !line[detected_indent.len..].trim_start().is_empty() {
90 if let Some(common_indent) = common_indent.as_mut() {
91 *common_indent = (*common_indent).min(detected_indent);
92 } else {
93 common_indent = Some(line.detect_indent());
94 }
95 }
96 }
97 }
98 let mut out = String::with_capacity(comment.len());
99 for (i, mut line) in lines.into_iter().enumerate() {
100 if i == 0 {
101 line = &line[first_line_indent.unwrap_or_default().len..];
102 if line.trim().is_empty() {
103 continue;
104 }
105 } else {
106 let indent_len = common_indent.unwrap_or_default().len;
107 if line.len() > indent_len {
108 line = &line[indent_len..];
109 } else {
110 line = "\n";
111 }
112 }
113 let line_clean_end = line.trim_end();
114 if let Some(suffix) = line_clean_end.strip_suffix(MULTILINE_SUFFIX) {
115 out += suffix.trim_end();
116 out.push('\n');
117 } else {
118 out += line;
119 }
120 }
121
122 let out_trim_end = out.trim_end_idx();
123 out.drain(out_trim_end..);
124 out
125}
126
127#[derive(PartialEq)]
128enum CommentType {
129 SingleLineDelimited,
131 MultilineAsteriskPrefixed,
133 MultilineWithoutAsteriskPrefix,
135}
136
137#[cfg(test)]
138mod test {
139 use super::strip_doxygen_comment_markers;
140
141 #[test]
142 fn test_strip_comment_markers() {
143 assert_eq!("test", &strip_doxygen_comment_markers("/** test */"));
144 assert_eq!("test", &strip_doxygen_comment_markers("// test"));
145 assert_eq!("test", &strip_doxygen_comment_markers("/*test */"));
146
147 {
148 let comment = "/** @overload
149 * @brief It's the same function as #calibrateCameraAruco but without calibration error estimation.
150 */";
151
152 let expected = "@overload
153@brief It's the same function as #calibrateCameraAruco but without calibration error estimation.";
154 assert_eq!(expected, &strip_doxygen_comment_markers(comment));
155 }
156
157 {
158 let comment = "// @overload
159// @brief It's the same function as #calibrateCameraAruco but without calibration error estimation.
160//";
161
162 let expected = "@overload
163@brief It's the same function as #calibrateCameraAruco but without calibration error estimation.";
164 assert_eq!(expected, &strip_doxygen_comment_markers(comment));
165 }
166 }
167}