opencv_binding_generator/
comment.rs

1use crate::string_ext::Indent;
2use crate::StrExt;
3
4pub fn strip_doxygen_comment_markers(comment: &str) -> String {
5	// todo, simplify/optimize this function, spec is here https://www.doxygen.nl/manual/docblocks.html
6	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	// first pass:
23	// 1. checks whether a comment is single-line or multi-line delimited
24	// 2. checks whether a multiline comment is asterisk prefixed
25	// 3. strips comment delimiters for multiline and single line comments
26	// 4. collects resulting stripped lines into `lines` Vec
27	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	// trim ending multiline delimiter
61	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	// second pass:
71	// 1. calculates common indent
72	// 2. for multiline asterisk prefixed comments, strips this prefix modifying `lines`
73	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	/// Each line starts with `//`
130	SingleLineDelimited,
131	/// Comment starting with `/*` or `/**` and every remaining line within starts with `*`
132	MultilineAsteriskPrefixed,
133	/// Comment starting with `/*` or `/**`, but without any prefix for the remaining lines
134	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		// multiline with prefix
148		{
149			let comment = "\
150/**
151 * line1
152 * line2
153 */
154";
155			let expected = "\
156line1
157line2";
158			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
159		}
160
161		// multiline without prefix
162		{
163			let comment = "\
164/*
165line1
166 line2
167*/
168";
169			let expected = "\
170line1
171 line2";
172			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
173		}
174
175		// multiline with indent and no end marker
176		{
177			let comment = "\
178/** line1
179 *     line2
180 *     line3
181";
182			let expected = "\
183line1
184line2
185line3";
186			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
187		}
188
189		// multiline with MD unordered list
190		{
191			let comment = "\
192/* line1
193      * line2
194        line3
195      * line4
196*/
197";
198			let expected = "\
199line1
200* line2
201  line3
202* line4";
203			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
204		}
205
206		// multiline with indent on a single line
207		{
208			let comment = "\
209/**
210 * line1
211 * line2
212                          line3
213 */
214";
215			let expected = "\
216line1
217line2
218                       line3";
219			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
220		}
221
222		// multiline, end marker on the same line
223		{
224			let comment = "\
225/** line1
226    line2
227    line3*/
228";
229			let expected = "\
230line1
231line2
232line3";
233			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
234		}
235
236		// two multiline glued
237		{
238			let comment = "\
239/** line1
240  line2*/
241/** line3 */
242";
243			let expected = "\
244line1
245  line2
246line3";
247			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
248		}
249
250		// multiline with empty lines inside and at the end
251		{
252			let comment = "\
253/** line1
254
255line2
256
257*/
258";
259			let expected = "\
260line1
261
262line2";
263			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
264		}
265
266		// multiline with empty lines and indent
267		{
268			let comment = "\
269/**
270 line1
271
272 line2
273*/
274";
275			let expected = "\
276line1
277
278line2";
279			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
280		}
281
282		// multiline with indent when the first line is on the same line as the start marker
283		{
284			let comment = "\
285/** line1
286
287 line2
288 */
289";
290			let expected = "\
291line1
292
293line2";
294			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
295		}
296
297		// single-line with exclamation mark
298		{
299			let comment = "\
300//!< line1
301      //!< line2
302";
303			let expected = "\
304line1
305line2";
306			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
307		}
308
309		// single-line
310		{
311			let comment = "// @overload
312// @brief It's the same function as #calibrateCameraAruco but without calibration error estimation.
313//";
314
315			let expected = "@overload
316@brief It's the same function as #calibrateCameraAruco but without calibration error estimation.";
317			assert_eq!(expected, &strip_doxygen_comment_markers(comment));
318		}
319	}
320}