]>
Commit | Line | Data |
---|---|---|
9e0c209e SL |
1 | use std::cmp; |
2 | use std::string::String; | |
3 | use std::usize; | |
4 | ||
9fa01778 | 5 | use crate::clean::{self, DocFragment, Item}; |
532ac7d7 | 6 | use crate::core::DocContext; |
9fa01778 XL |
7 | use crate::fold::{self, DocFolder}; |
8 | use crate::passes::Pass; | |
b7449926 | 9 | |
416331ca XL |
10 | #[cfg(test)] |
11 | mod tests; | |
12 | ||
532ac7d7 XL |
13 | pub const UNINDENT_COMMENTS: Pass = Pass { |
14 | name: "unindent-comments", | |
60c5eb7d | 15 | run: unindent_comments, |
532ac7d7 XL |
16 | description: "removes excess indentation on comments in order for markdown to like it", |
17 | }; | |
9e0c209e | 18 | |
532ac7d7 | 19 | pub fn unindent_comments(krate: clean::Crate, _: &DocContext<'_>) -> clean::Crate { |
476ff2be | 20 | CommentCleaner.fold_crate(krate) |
9e0c209e SL |
21 | } |
22 | ||
23 | struct CommentCleaner; | |
24 | ||
25 | impl fold::DocFolder for CommentCleaner { | |
26 | fn fold_item(&mut self, mut i: Item) -> Option<Item> { | |
476ff2be | 27 | i.attrs.unindent_doc_comments(); |
9e0c209e SL |
28 | self.fold_item_recur(i) |
29 | } | |
30 | } | |
31 | ||
476ff2be SL |
32 | impl clean::Attributes { |
33 | pub fn unindent_doc_comments(&mut self) { | |
ff7c6d11 XL |
34 | unindent_fragments(&mut self.doc_strings); |
35 | } | |
36 | } | |
37 | ||
38 | fn unindent_fragments(docs: &mut Vec<DocFragment>) { | |
39 | for fragment in docs { | |
40 | match *fragment { | |
60c5eb7d XL |
41 | DocFragment::SugaredDoc(_, _, ref mut doc_string) |
42 | | DocFragment::RawDoc(_, _, ref mut doc_string) | |
43 | | DocFragment::Include(_, _, _, ref mut doc_string) => { | |
44 | *doc_string = unindent(doc_string) | |
45 | } | |
476ff2be SL |
46 | } |
47 | } | |
48 | } | |
49 | ||
9e0c209e | 50 | fn unindent(s: &str) -> String { |
60c5eb7d | 51 | let lines = s.lines().collect::<Vec<&str>>(); |
9e0c209e SL |
52 | let mut saw_first_line = false; |
53 | let mut saw_second_line = false; | |
54 | let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| { | |
9e0c209e SL |
55 | // After we see the first non-whitespace line, look at |
56 | // the line we have. If it is not whitespace, and therefore | |
57 | // part of the first paragraph, then ignore the indentation | |
58 | // level of the first line | |
59 | let ignore_previous_indents = | |
60c5eb7d | 60 | saw_first_line && !saw_second_line && !line.chars().all(|c| c.is_whitespace()); |
9e0c209e | 61 | |
60c5eb7d | 62 | let min_indent = if ignore_previous_indents { usize::MAX } else { min_indent }; |
9e0c209e SL |
63 | |
64 | if saw_first_line { | |
65 | saw_second_line = true; | |
66 | } | |
67 | ||
68 | if line.chars().all(|c| c.is_whitespace()) { | |
69 | min_indent | |
70 | } else { | |
71 | saw_first_line = true; | |
72 | let mut whitespace = 0; | |
73 | line.chars().all(|char| { | |
74 | // Compare against either space or tab, ignoring whether they | |
75 | // are mixed or not | |
76 | if char == ' ' || char == '\t' { | |
77 | whitespace += 1; | |
78 | true | |
79 | } else { | |
80 | false | |
81 | } | |
82 | }); | |
83 | cmp::min(min_indent, whitespace) | |
84 | } | |
85 | }); | |
86 | ||
87 | if !lines.is_empty() { | |
60c5eb7d XL |
88 | let mut unindented = vec![lines[0].trim_start().to_string()]; |
89 | unindented.extend_from_slice( | |
90 | &lines[1..] | |
91 | .iter() | |
92 | .map(|&line| { | |
93 | if line.chars().all(|c| c.is_whitespace()) { | |
94 | line.to_string() | |
95 | } else { | |
96 | assert!(line.len() >= min_indent); | |
97 | line[min_indent..].to_string() | |
98 | } | |
99 | }) | |
100 | .collect::<Vec<_>>(), | |
101 | ); | |
9e0c209e SL |
102 | unindented.join("\n") |
103 | } else { | |
104 | s.to_string() | |
105 | } | |
106 | } |