1use consts::{END_INFO, FILE, FILE_COMMENT, MOD_COMMENT};
16use std::collections::HashMap;
17use std::fs::{remove_file, File, OpenOptions};
18use std::io::{BufRead, BufReader, Write};
19use std::iter;
20use std::ops::Deref;
21use std::path::Path;
22use strip;
23use types::{EventType, ParseResult, Type, TypeStruct};
24use utils::{join, loop_over_files, remove_macro_parent, write_comment, write_file};
25
26type Infos = HashMap<Option<String>, Vec<(Option<TypeStruct>, Vec<String>)>>;
27
28fn gen_indent(indent: usize) -> String {
29 iter::repeat(" ")
30 .take(indent)
31 .collect::<Vec<&str>>()
32 .join("")
33}
34
35fn gen_indent_from(from: &str) -> String {
36 for (i, c) in from.chars().enumerate() {
37 if c != ' ' && c != '\t' {
38 return gen_indent(i / 4);
39 }
40 }
41 String::new()
42}
43
44fn regenerate_comment(
46 is_file_comment: bool,
47 position: usize,
48 indent: usize,
49 comment: &str,
50 original_content: &mut Vec<String>,
51 need_check_ignore_doc_comment: bool,
52) -> bool {
53 let mut need_to_add_ignore_next_comment_stop = false;
54 if need_check_ignore_doc_comment && position > 0 {
55 let prev = original_content[position - 1].trim();
56 if strip::DOC_COMMENT_ID.iter().any(|d| prev.starts_with(d)) {
57 need_to_add_ignore_next_comment_stop = true;
58 }
59 }
60 let is_empty = comment.trim().is_empty();
61 let read_indent = if is_file_comment {
62 gen_indent(indent)
63 } else {
64 let tmp = original_content[position].clone();
65 gen_indent_from(&tmp)
66 };
67 original_content.insert(
68 position,
69 format!(
70 "{}{}{}{}",
71 &read_indent,
72 if is_file_comment { "//!" } else { "///" },
73 if is_empty { "" } else { " " },
74 if is_empty { "" } else { comment }
75 ),
76 );
77 if need_to_add_ignore_next_comment_stop {
78 original_content.insert(
79 position,
80 format!("{}{}", &read_indent, strip::IGNORE_NEXT_COMMENT_STOP,),
81 );
82 }
83 need_to_add_ignore_next_comment_stop
84}
85
86#[allow(clippy::useless_let_if_seq)]
87fn get_corresponding_type(
88 elements: &[(Option<TypeStruct>, Vec<String>)],
89 to_find: &Option<TypeStruct>,
90 mut line: usize,
91 decal: &mut usize,
92 original_content: &mut Vec<String>,
93 ignore_macros: bool,
94) -> Option<usize> {
95 if to_find.is_none() {
96 return None;
97 }
98 let to_find = to_find.as_ref().unwrap();
99 let mut pos = 0;
100
101 while pos < elements.len() {
102 if match elements[pos].0 {
103 Some(ref a) => {
104 let ret = a == to_find || {
108 let mut tmp = to_find.clone();
109 remove_macro_parent(&mut tmp);
110 *a == tmp
111 };
112
113 if !ret
115 && to_find.ty == Type::Unknown
116 && to_find.parent.is_some()
117 && a.parent.is_some()
118 && a.parent == to_find.parent
119 {
120 if match to_find.parent {
121 Some(ref p) => {
122 p.ty == Type::Struct || p.ty == Type::Enum || p.ty == Type::Use
123 }
124 None => false,
125 } {
126 let mut tmp = to_find.clone();
127 tmp.ty = Type::Variant;
128 a == &tmp
129 } else {
130 false
131 }
132 } else {
133 ret
134 }
135 }
136 _ => false,
137 } {
138 let mut file_comment = false;
139
140 if !elements[pos].1.is_empty() && elements[pos].1[0].starts_with("//!") {
141 line += 1;
142 file_comment = true;
143 } else {
144 while line > 0
145 && (line + *decal) > 0
146 && original_content[line + *decal - 1]
147 .trim_start()
148 .starts_with('#')
149 {
150 line -= 1;
151 }
152 }
153 let mut first = true;
154 for comment in elements[pos].1.iter().skip(usize::from(file_comment)) {
155 let depth = if let Some(ref e) = elements[pos].0 {
156 e.get_depth(ignore_macros)
157 } else {
158 0
159 };
160 if regenerate_comment(
161 file_comment,
162 line + *decal,
163 depth + 1,
164 comment,
165 original_content,
166 first,
167 ) {
168 *decal += 1;
169 }
170 *decal += 1;
171 first = false;
172 }
173 return Some(pos);
174 }
175 pos += 1;
176 }
177 None
178}
179
180pub fn regenerate_comments(
182 work_dir: &Path,
183 path: &str,
184 infos: &mut Infos,
185 ignore_macros: bool,
186 ignore_doc_commented: bool,
187) {
188 if !infos.contains_key(&None) && !infos.contains_key(&Some(path.to_owned())) {
189 return;
190 }
191 let full_path = work_dir.join(path);
192 match strip::build_event_list(&full_path) {
193 Ok(ref mut parse_result) => {
194 if let Some(v) = infos.get_mut(&Some(path.to_owned())) {
196 do_regenerate(
197 &full_path,
198 parse_result,
199 v,
200 ignore_macros,
201 ignore_doc_commented,
202 );
203 }
204 if let Some(v) = infos.get_mut(&None) {
206 do_regenerate(
207 &full_path,
208 parse_result,
209 v,
210 ignore_macros,
211 ignore_doc_commented,
212 );
213 }
214 }
215 Err(e) => {
216 println!("Error in file '{}': {}", path, e);
217 }
218 }
219}
220
221fn check_if_regen(it: usize, parse_result: &ParseResult, ignore_doc_commented: bool) -> bool {
222 ignore_doc_commented
223 && it > 0
224 && matches!(
225 parse_result.event_list[it - 1].event,
226 EventType::Comment(_) | EventType::FileComment(_)
227 )
228}
229
230fn do_regenerate(
231 path: &Path,
232 parse_result: &mut ParseResult,
233 elements: &mut Vec<(Option<TypeStruct>, Vec<String>)>,
234 ignore_macros: bool,
235 ignore_doc_commented: bool,
236) {
237 let mut position = 0;
238 let mut decal = 0;
239
240 for entry in elements.iter() {
242 if entry.0.is_none() {
243 let mut it = 0;
244
245 while it < parse_result.original_content.len()
246 && parse_result.original_content[it].starts_with('/')
247 {
248 it += 1;
249 }
250 if it > 0 {
251 it += 1;
252 }
253 if it < parse_result.original_content.len() {
254 for line in &entry.1 {
255 if line.trim().is_empty() {
256 parse_result.original_content.insert(it, "//!".to_string());
257 } else {
258 parse_result
259 .original_content
260 .insert(it, format!("//! {}", &line));
261 }
262 decal += 1;
263 it += 1;
264 }
265 }
266 parse_result.original_content.insert(it, "".to_owned());
267 decal += 1;
268 break;
269 }
270 position += 1;
271 }
272 if position < elements.len() {
273 elements.remove(position);
274 }
275 let mut waiting_type = None;
276 let mut current = None;
277 let mut it = 0;
278
279 while it < parse_result.event_list.len() {
280 match parse_result.event_list[it].event {
281 EventType::Type(ref t) => {
282 if t.ty != Type::Unknown {
283 waiting_type = Some(t.clone());
284 let tmp = {
285 let t = strip::add_to_type_scope(¤t, &waiting_type);
286 if ignore_macros {
287 erase_macro_path(t)
288 } else {
289 t
290 }
291 };
292
293 if !check_if_regen(it, parse_result, ignore_doc_commented) {
294 if let Some(l) = get_corresponding_type(
295 elements,
296 &tmp,
297 parse_result.event_list[it].line,
298 &mut decal,
299 &mut parse_result.original_content,
300 ignore_macros,
301 ) {
302 elements.remove(l);
303 }
304 }
305 } else if let Some(ref c) = current {
306 if c.ty == Type::Struct || c.ty == Type::Enum || c.ty == Type::Mod {
307 let tmp = Some(t.clone());
308 let cc = {
309 let t = strip::add_to_type_scope(¤t, &tmp);
310 if ignore_macros {
311 erase_macro_path(t)
312 } else {
313 t
314 }
315 };
316
317 if !check_if_regen(it, parse_result, ignore_doc_commented) {
318 if let Some(l) = get_corresponding_type(
319 elements,
320 &cc,
321 parse_result.event_list[it].line,
322 &mut decal,
323 &mut parse_result.original_content,
324 ignore_macros,
325 ) {
326 elements.remove(l);
327 }
328 }
329 }
330 }
331 }
332 EventType::InScope => {
333 current = strip::add_to_type_scope(¤t, &waiting_type);
334 waiting_type = None;
335 }
336 EventType::OutScope => {
337 current = strip::type_out_scope(¤t);
338 waiting_type = None;
339 }
340 _ => {}
341 }
342 it += 1;
343 }
344 rewrite_file(path, &parse_result.original_content);
345}
346
347fn rewrite_file(path: &Path, o_content: &[String]) {
348 match File::create(path) {
349 Ok(mut f) => {
350 write!(f, "{}", o_content.join("\n")).unwrap();
351 }
352 Err(e) => {
353 println!("Cannot open '{}': {}", path.display(), e);
354 }
355 }
356}
357
358fn parse_mod_line(line: &str) -> Option<TypeStruct> {
359 let line = line
360 .replace(FILE_COMMENT, "")
361 .replace(MOD_COMMENT, "")
362 .replace(END_INFO, "");
363 if line.is_empty() {
364 return None;
365 }
366 let parts: Vec<&str> = line.split("::").collect();
367 let mut current = None;
368
369 for part in parts {
370 let elems: Vec<&str> = part.split(' ').filter(|x| !x.is_empty()).collect();
371
372 current = strip::add_to_type_scope(
373 ¤t.clone(),
374 &Some(TypeStruct::new(
375 Type::from(elems[0]),
376 elems[elems.len() - 1],
377 )),
378 );
379 }
380 current
381}
382
383fn save_remainings(infos: &Infos, comment_file: &str) {
384 let mut remainings = 0;
385
386 for content in infos.values() {
387 if !content.is_empty() {
388 remainings += 1;
389 }
390 }
391 if remainings < 1 {
392 let _ = remove_file(comment_file);
393 return;
394 }
395 match File::create(comment_file) {
396 Ok(mut out_file) => {
397 println!(
398 "Some comments haven't been regenerated to the files. Saving them \
399 back to '{}'.",
400 comment_file
401 );
402 for (key, content) in infos {
403 if content.is_empty() {
404 continue;
405 }
406 let key = key.as_ref().map(|s| &s[..]).unwrap_or("*");
408 let _ = writeln!(out_file, "{}", &write_file(key));
409 for line in content {
410 if let Some(ref d) = line.0 {
411 let _ = writeln!(
412 out_file,
413 "{}",
414 write_comment(d, &join(&line.1, "\n"), false)
415 );
416 }
417 }
418 }
419 }
420 Err(e) => {
421 println!(
422 "An error occured while trying to open '{}': {}",
423 comment_file, e
424 );
425 }
426 }
427}
428
429pub fn regenerate_doc_comments(
430 directory: &str,
431 verbose: bool,
432 comment_file: &str,
433 ignore_macros: bool,
434 ignore_doc_commented: bool,
435) {
436 let f = match OpenOptions::new().read(true).open(comment_file) {
438 Ok(f) => f,
439 Err(e) => {
440 println!(
441 "An error occured while trying to open '{}': {}",
442 comment_file, e
443 );
444 return;
445 }
446 };
447 let reader = BufReader::new(f);
448 let lines = reader.lines().map(|line| line.unwrap());
449 let mut infos = parse_cmts(lines, ignore_macros);
450 let ignores: &[&str] = &[];
451
452 loop_over_files(
453 directory.as_ref(),
454 &mut |w, s| regenerate_comments(w, s, &mut infos, ignore_macros, ignore_doc_commented),
455 ignores,
456 verbose,
457 );
458 save_remainings(&infos, comment_file);
459}
460
461fn sub_erase_macro_path(ty: Option<Box<TypeStruct>>, is_parent: bool) -> Option<Box<TypeStruct>> {
462 match ty {
463 Some(ref t) if is_parent => {
464 if t.ty == Type::Macro {
465 sub_erase_macro_path(t.clone().parent, true)
466 } else {
467 let mut tmp = t.clone();
468 tmp.parent = sub_erase_macro_path(t.clone().parent, true);
469 Some(tmp)
470 }
471 }
472 Some(t) => {
473 let mut tmp = t.clone();
474 tmp.parent = sub_erase_macro_path(t.parent, true);
475 Some(tmp)
476 }
477 None => None,
478 }
479}
480
481fn erase_macro_path(ty: Option<TypeStruct>) -> Option<TypeStruct> {
482 ty.map(|t| *sub_erase_macro_path(Some(Box::new(t)), false).unwrap())
483}
484
485pub fn parse_cmts<S, I>(lines: I, ignore_macros: bool) -> Infos
486where
487 S: Deref<Target = str>,
488 I: Iterator<Item = S>,
489{
490 enum State {
491 Initial,
492 File {
493 file: Option<String>,
494 infos: Vec<(Option<TypeStruct>, Vec<String>)>,
495 ty: Option<TypeStruct>,
496 comments: Vec<String>,
497 },
498 }
499
500 #[allow(clippy::option_option)]
504 fn line_file(line: &str) -> Option<Option<String>> {
505 if let Some(after) = line.strip_prefix(FILE) {
506 let name = after.replace(END_INFO, "");
507 if name == "*" {
508 Some(None)
509 } else {
510 Some(Some(name))
511 }
512 } else {
513 None
514 }
515 }
516
517 let mut ret = HashMap::new();
518 let mut state = State::Initial;
519
520 for line in lines {
521 state = match state {
522 State::Initial => {
523 if let Some(file) = line_file(&line) {
524 State::File {
525 file,
526 infos: vec![],
527 ty: None,
528 comments: vec![],
529 }
530 } else {
531 panic!("Unrecognized format on line: `{}`", line.deref());
532 }
533 }
534 State::File {
535 mut file,
536 mut infos,
537 mut ty,
538 mut comments,
539 } => {
540 if let Some(new_file) = line_file(&line) {
541 if !comments.is_empty() {
542 infos.push((ty.take(), comments));
543 comments = vec![];
544 }
545 if !infos.is_empty() {
546 ret.insert(file, infos);
547 file = new_file;
548 infos = vec![];
549 }
550 } else if line.starts_with(FILE_COMMENT) {
551 if let Some(ty) = ty.take() {
552 if !comments.is_empty() {
553 infos.push((Some(ty), comments));
554 comments = vec!["//!".to_owned()];
555 }
556 } else if !comments.is_empty() {
557 infos.push((None, comments));
558 comments = vec![];
559 }
560 ty = parse_mod_line(&line[..]);
561 } else if line.starts_with(MOD_COMMENT) {
562 if !comments.is_empty() {
563 infos.push((ty, comments));
564 comments = vec![];
565 }
566 ty = parse_mod_line(&line[..]);
567 } else {
568 comments.push(line[..].to_owned());
569 }
570 State::File {
571 file,
572 infos,
573 ty: if ignore_macros {
574 erase_macro_path(ty)
575 } else {
576 ty
577 },
578 comments,
579 }
580 }
581 }
582 }
583
584 if let State::File {
585 file,
586 mut infos,
587 ty,
588 comments,
589 } = state
590 {
591 if !comments.is_empty() {
592 infos.push((ty, comments));
593 }
594 if !infos.is_empty() {
595 ret.insert(file, infos);
596 }
597 }
598
599 ret
600}