Skip to content

Commit 0ad8265

Browse files
committed
rustdoc: Add the concept of 'sections'
1 parent 4ffcb95 commit 0ad8265

File tree

6 files changed

+337
-2
lines changed

6 files changed

+337
-2
lines changed

src/rustdoc/doc.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ enum page {
1111
itempage(itemtag)
1212
}
1313

14+
#[doc = "
15+
Most rustdocs can be parsed into 'sections' according to their markdown
16+
headers
17+
"]
18+
type section = {
19+
header: str,
20+
body: str
21+
};
22+
1423
// FIXME: We currently give topmod the name of the crate. There would
1524
// probably be fewer special cases if the crate had its own name and
1625
// topmod's name was the empty string.
@@ -36,6 +45,7 @@ type itemdoc = {
3645
path: [str],
3746
brief: option<str>,
3847
desc: option<str>,
48+
sections: [section],
3949
// Indicates that this node is a reexport of a different item
4050
reexport: bool
4151
};
@@ -99,6 +109,7 @@ type methoddoc = {
99109
name: str,
100110
brief: option<str>,
101111
desc: option<str>,
112+
sections: [section],
102113
args: [argdoc],
103114
return: retdoc,
104115
failure: option<str>,
@@ -400,4 +411,8 @@ impl util<A:item> for A {
400411
fn desc() -> option<str> {
401412
self.item().desc
402413
}
414+
415+
fn sections() -> [section] {
416+
self.item().sections
417+
}
403418
}

src/rustdoc/extract.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ fn mk_itemdoc(id: ast::node_id, name: ast::ident) -> doc::itemdoc {
4444
path: [],
4545
brief: none,
4646
desc: none,
47+
sections: [],
4748
reexport: false
4849
}
4950
}
@@ -250,6 +251,7 @@ fn ifacedoc_from_iface(
250251
name: method.ident,
251252
brief: none,
252253
desc: none,
254+
sections: [],
253255
args: argdocs_from_args(method.decl.inputs),
254256
return: {
255257
desc: none
@@ -292,6 +294,7 @@ fn impldoc_from_impl(
292294
name: method.ident,
293295
brief: none,
294296
desc: none,
297+
sections: [],
295298
args: argdocs_from_args(method.decl.inputs),
296299
return: {
297300
desc: none

src/rustdoc/rustdoc.rc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ mod sort_item_type_pass;
4040
mod reexport_pass;
4141
mod par;
4242
mod page_pass;
43+
mod sectionalize_pass;

src/rustdoc/rustdoc.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ fn run(config: config::config) {
148148
// prune_undoc_items_pass::mk_pass(),
149149
prune_hidden_pass::mk_pass(),
150150
desc_to_brief_pass::mk_pass(),
151-
trim_pass::mk_pass(),
152151
unindent_pass::mk_pass(),
152+
sectionalize_pass::mk_pass(),
153+
trim_pass::mk_pass(),
153154
sort_item_name_pass::mk_pass(),
154155
sort_item_type_pass::mk_pass(),
155156
markdown_index_pass::mk_pass(config),

src/rustdoc/sectionalize_pass.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#[doc = "Breaks rustdocs into sections according to their headers"];
2+
3+
export mk_pass;
4+
5+
fn mk_pass() -> pass {
6+
{
7+
name: "sectionalize",
8+
f: run
9+
}
10+
}
11+
12+
fn run(_srv: astsrv::srv, doc: doc::doc) -> doc::doc {
13+
let fold = fold::fold({
14+
fold_item: fold_item,
15+
fold_iface: fold_iface,
16+
fold_impl: fold_impl
17+
with *fold::default_any_fold(())
18+
});
19+
fold.fold_doc(fold, doc)
20+
}
21+
22+
fn fold_item(fold: fold::fold<()>, doc: doc::itemdoc) -> doc::itemdoc {
23+
let doc = fold::default_seq_fold_item(fold, doc);
24+
let (desc, sections) = sectionalize(doc.desc);
25+
26+
{
27+
desc: desc,
28+
sections: sections
29+
with doc
30+
}
31+
}
32+
33+
fn fold_iface(fold: fold::fold<()>, doc: doc::ifacedoc) -> doc::ifacedoc {
34+
let doc = fold::default_seq_fold_iface(fold, doc);
35+
36+
{
37+
methods: par::anymap(doc.methods) {|method|
38+
let (desc, sections) = sectionalize(method.desc);
39+
40+
{
41+
desc: desc,
42+
sections: sections
43+
with method
44+
}
45+
}
46+
with doc
47+
}
48+
}
49+
50+
fn fold_impl(fold: fold::fold<()>, doc: doc::impldoc) -> doc::impldoc {
51+
let doc = fold::default_seq_fold_impl(fold, doc);
52+
53+
{
54+
methods: par::anymap(doc.methods) {|method|
55+
let (desc, sections) = sectionalize(method.desc);
56+
57+
{
58+
desc: desc,
59+
sections: sections
60+
with method
61+
}
62+
}
63+
with doc
64+
}
65+
}
66+
67+
fn sectionalize(desc: option<str>) -> (option<str>, [doc::section]) {
68+
69+
#[doc = "
70+
71+
Take a description of the form
72+
73+
General text
74+
75+
# Section header
76+
77+
Section text
78+
79+
# Section header
80+
81+
Section text
82+
83+
and remove each header and accompanying text into section records.
84+
85+
"];
86+
87+
if option::is_none(desc) {
88+
ret (none, []);
89+
}
90+
91+
let lines = str::lines(option::get(desc));
92+
93+
let new_desc = none;
94+
let current_section = none;
95+
let sections = [];
96+
97+
for line in lines {
98+
alt parse_header(line) {
99+
some(header) {
100+
if option::is_some(current_section) {
101+
sections += [option::get(current_section)];
102+
}
103+
current_section = some({
104+
header: header,
105+
body: ""
106+
});
107+
}
108+
none {
109+
alt current_section {
110+
some(section) {
111+
current_section = some({
112+
body: section.body + "\n" + line
113+
with section
114+
});
115+
}
116+
none {
117+
alt new_desc {
118+
some(desc) {
119+
new_desc = some(desc + "\n" + line);
120+
}
121+
none {
122+
new_desc = some(line);
123+
}
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
130+
131+
if option::is_some(current_section) {
132+
sections += [option::get(current_section)];
133+
}
134+
135+
(new_desc, sections)
136+
}
137+
138+
fn parse_header(line: str) -> option<str> {
139+
if str::starts_with(line, "# ") {
140+
some(str::slice(line, 2u, str::len(line)))
141+
} else {
142+
none
143+
}
144+
}
145+
146+
#[test]
147+
fn should_create_section_headers() {
148+
let doc = test::mk_doc(
149+
"#[doc = \"\
150+
# Header\n\
151+
Body\"]\
152+
mod a { }");
153+
assert str::contains(
154+
doc.cratemod().mods()[0].item.sections[0].header,
155+
"Header");
156+
}
157+
158+
#[test]
159+
fn should_create_section_bodies() {
160+
let doc = test::mk_doc(
161+
"#[doc = \"\
162+
# Header\n\
163+
Body\"]\
164+
mod a { }");
165+
assert str::contains(
166+
doc.cratemod().mods()[0].item.sections[0].body,
167+
"Body");
168+
}
169+
170+
#[test]
171+
fn should_not_create_sections_from_indented_headers() {
172+
let doc = test::mk_doc(
173+
"#[doc = \"\n\
174+
Text\n # Header\n\
175+
Body\"]\
176+
mod a { }");
177+
assert vec::is_empty(doc.cratemod().mods()[0].item.sections);
178+
}
179+
180+
#[test]
181+
fn should_remove_section_text_from_main_desc() {
182+
let doc = test::mk_doc(
183+
"#[doc = \"\
184+
Description\n\n\
185+
# Header\n\
186+
Body\"]\
187+
mod a { }");
188+
assert !str::contains(
189+
option::get(doc.cratemod().mods()[0].desc()),
190+
"Header");
191+
assert !str::contains(
192+
option::get(doc.cratemod().mods()[0].desc()),
193+
"Body");
194+
}
195+
196+
#[test]
197+
fn should_eliminate_desc_if_it_is_just_whitespace() {
198+
let doc = test::mk_doc(
199+
"#[doc = \"\
200+
# Header\n\
201+
Body\"]\
202+
mod a { }");
203+
assert doc.cratemod().mods()[0].desc() == none;
204+
}
205+
206+
#[test]
207+
fn should_sectionalize_iface_methods() {
208+
let doc = test::mk_doc(
209+
"iface i {
210+
#[doc = \"\
211+
# Header\n\
212+
Body\"]\
213+
fn a(); }");
214+
assert doc.cratemod().ifaces()[0].methods[0].sections.len() == 1u;
215+
}
216+
217+
#[test]
218+
fn should_sectionalize_impl_methods() {
219+
let doc = test::mk_doc(
220+
"impl i for bool {
221+
#[doc = \"\
222+
# Header\n\
223+
Body\"]\
224+
fn a() { } }");
225+
assert doc.cratemod().impls()[0].methods[0].sections.len() == 1u;
226+
}
227+
228+
#[cfg(test)]
229+
mod test {
230+
fn mk_doc(source: str) -> doc::doc {
231+
astsrv::from_str(source) {|srv|
232+
let doc = extract::from_srv(srv, "");
233+
let doc = attr_pass::mk_pass().f(srv, doc);
234+
run(srv, doc)
235+
}
236+
}
237+
}

0 commit comments

Comments
 (0)