1 | //! The preprocessing we apply to doc comments. |
2 | //! |
3 | //! #[derive(Parser)] works in terms of "paragraphs". Paragraph is a sequence of |
4 | //! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines. |
5 | |
6 | use std::iter; |
7 | |
8 | pub fn extract_doc_comment(attrs: &[syn::Attribute]) -> Vec<String> { |
9 | // multiline comments (`/** ... */`) may have LFs (`\n`) in them, |
10 | // we need to split so we could handle the lines correctly |
11 | // |
12 | // we also need to remove leading and trailing blank lines |
13 | let mut lines: Vec<_> = attrs |
14 | .iter() |
15 | .filter(|attr| attr.path().is_ident("doc" )) |
16 | .filter_map(|attr| { |
17 | // non #[doc = "..."] attributes are not our concern |
18 | // we leave them for rustc to handle |
19 | match &attr.meta { |
20 | syn::Meta::NameValue(syn::MetaNameValue { |
21 | value: |
22 | syn::Expr::Lit(syn::ExprLit { |
23 | lit: syn::Lit::Str(s), |
24 | .. |
25 | }), |
26 | .. |
27 | }) => Some(s.value()), |
28 | _ => None, |
29 | } |
30 | }) |
31 | .skip_while(|s| is_blank(s)) |
32 | .flat_map(|s| { |
33 | let lines = s |
34 | .split(' \n' ) |
35 | .map(|s| { |
36 | // remove one leading space no matter what |
37 | let s = s.strip_prefix(' ' ).unwrap_or(s); |
38 | s.to_owned() |
39 | }) |
40 | .collect::<Vec<_>>(); |
41 | lines |
42 | }) |
43 | .collect(); |
44 | |
45 | while let Some(true) = lines.last().map(|s| is_blank(s)) { |
46 | lines.pop(); |
47 | } |
48 | |
49 | lines |
50 | } |
51 | |
52 | pub fn format_doc_comment( |
53 | lines: &[String], |
54 | preprocess: bool, |
55 | force_long: bool, |
56 | ) -> (Option<String>, Option<String>) { |
57 | if let Some(first_blank) = lines.iter().position(|s| is_blank(s)) { |
58 | let (short, long) = if preprocess { |
59 | let paragraphs = split_paragraphs(lines); |
60 | let short = paragraphs[0].clone(); |
61 | let long = paragraphs.join(" \n\n" ); |
62 | (remove_period(short), long) |
63 | } else { |
64 | let short = lines[..first_blank].join(" \n" ); |
65 | let long = lines.join(" \n" ); |
66 | (short, long) |
67 | }; |
68 | |
69 | (Some(short), Some(long)) |
70 | } else { |
71 | let (short, long) = if preprocess { |
72 | let short = merge_lines(lines); |
73 | let long = force_long.then(|| short.clone()); |
74 | let short = remove_period(short); |
75 | (short, long) |
76 | } else { |
77 | let short = lines.join(" \n" ); |
78 | let long = force_long.then(|| short.clone()); |
79 | (short, long) |
80 | }; |
81 | |
82 | (Some(short), long) |
83 | } |
84 | } |
85 | |
86 | fn split_paragraphs(lines: &[String]) -> Vec<String> { |
87 | let mut last_line: usize = 0; |
88 | iterimpl Iterator ::from_fn(|| { |
89 | let slice: &[String] = &lines[last_line..]; |
90 | let start: usize = slice.iter().position(|s| !is_blank(s)).unwrap_or(default:0); |
91 | |
92 | let slice: &[String] = &slice[start..]; |
93 | let len: usize = slice |
94 | .iter() |
95 | .position(|s| is_blank(s)) |
96 | .unwrap_or(default:slice.len()); |
97 | |
98 | last_line += start + len; |
99 | |
100 | if len != 0 { |
101 | Some(merge_lines(&slice[..len])) |
102 | } else { |
103 | None |
104 | } |
105 | }) |
106 | .collect() |
107 | } |
108 | |
109 | fn remove_period(mut s: String) -> String { |
110 | if s.ends_with('.' ) && !s.ends_with(".." ) { |
111 | s.pop(); |
112 | } |
113 | s |
114 | } |
115 | |
116 | fn is_blank(s: &str) -> bool { |
117 | s.trim().is_empty() |
118 | } |
119 | |
120 | fn merge_lines(lines: impl IntoIterator<Item = impl AsRef<str>>) -> String { |
121 | lines |
122 | .into_iter() |
123 | .map(|s| s.as_ref().trim().to_owned()) |
124 | .collect::<Vec<_>>() |
125 | .join(sep:" " ) |
126 | } |
127 | |