about summary refs log tree commit diff
path: root/src/rustdoc/unindent_pass.rs
blob: 26b785b6614f825f7441cc47d91f412c540d67cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*!
 * Removes the common level of indention from description strings. For
 * instance, if an entire doc comment is indented 8 spaces we want to
 * remove those 8 spaces from every line.
 *
 * The first line of a string is allowed to be intend less than
 * subsequent lines in the same paragraph in order to account for
 * instances where the string containing the doc comment is opened in the
 * middle of a line, and each of the following lines is indented.
 */

export mk_pass;

fn mk_pass() -> pass {
    text_pass::mk_pass(~"unindent", unindent)
}

fn unindent(s: ~str) -> ~str {
    let lines = str::lines_any(s);
    let mut saw_first_line = false;
    let mut saw_second_line = false;
    let min_indent = do vec::foldl(uint::max_value, lines)
        |min_indent, line| {

        // After we see the first non-whitespace line, look at
        // the line we have. If it is not whitespace, and therefore
        // part of the first paragraph, then ignore the indentation
        // level of the first line
        let ignore_previous_indents =
            saw_first_line &&
            !saw_second_line &&
            !str::is_whitespace(line);

        let min_indent = if ignore_previous_indents {
            uint::max_value
        } else {
            min_indent
        };

        if saw_first_line {
            saw_second_line = true;
        }

        if str::is_whitespace(line) {
            min_indent
        } else {
            saw_first_line = true;
            let mut spaces = 0u;
            do str::all(line) |char| {
                // Only comparing against space because I wouldn't
                // know what to do with mixed whitespace chars
                if char == ' ' {
                    spaces += 1u;
                    true
                } else {
                    false
                }
            };
            uint::min(min_indent, spaces)
        }
    };

    if vec::is_not_empty(lines) {
        let unindented = ~[str::trim(vec::head(lines))]
            + do par::anymap(vec::tail(lines)) |line| {
            if str::is_whitespace(line) {
                line
            } else {
                assert str::len(line) >= min_indent;
                str::slice(line, min_indent, str::len(line))
            }
        };
        str::connect(unindented, ~"\n")
    } else {
        s
    }
}

#[test]
fn should_unindent() {
    let s = ~"    line1\n    line2";
    let r = unindent(s);
    assert r == ~"line1\nline2";
}

#[test]
fn should_unindent_multiple_paragraphs() {
    let s = ~"    line1\n\n    line2";
    let r = unindent(s);
    assert r == ~"line1\n\nline2";
}

#[test]
fn should_leave_multiple_indent_levels() {
    // Line 2 is indented another level beyond the
    // base indentation and should be preserved
    let s = ~"    line1\n\n        line2";
    let r = unindent(s);
    assert r == ~"line1\n\n    line2";
}

#[test]
fn should_ignore_first_line_indent() {
    // Thi first line of the first paragraph may not be indented as
    // far due to the way the doc string was written:
    //
    // #[doc = "Start way over here
    //          and continue here"]
    let s = ~"line1\n    line2";
    let r = unindent(s);
    assert r == ~"line1\nline2";
}

#[test]
fn should_not_ignore_first_line_indent_in_a_single_line_para() {
    let s = ~"line1\n\n    line2";
    let r = unindent(s);
    assert r == ~"line1\n\n    line2";
}