rustdoc: Add the concept of 'sections'
This commit is contained in:
parent
4ffcb95974
commit
0ad8265fee
6 changed files with 337 additions and 2 deletions
|
@ -11,6 +11,15 @@ enum page {
|
||||||
itempage(itemtag)
|
itempage(itemtag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc = "
|
||||||
|
Most rustdocs can be parsed into 'sections' according to their markdown
|
||||||
|
headers
|
||||||
|
"]
|
||||||
|
type section = {
|
||||||
|
header: str,
|
||||||
|
body: str
|
||||||
|
};
|
||||||
|
|
||||||
// FIXME: We currently give topmod the name of the crate. There would
|
// FIXME: We currently give topmod the name of the crate. There would
|
||||||
// probably be fewer special cases if the crate had its own name and
|
// probably be fewer special cases if the crate had its own name and
|
||||||
// topmod's name was the empty string.
|
// topmod's name was the empty string.
|
||||||
|
@ -36,6 +45,7 @@ type itemdoc = {
|
||||||
path: [str],
|
path: [str],
|
||||||
brief: option<str>,
|
brief: option<str>,
|
||||||
desc: option<str>,
|
desc: option<str>,
|
||||||
|
sections: [section],
|
||||||
// Indicates that this node is a reexport of a different item
|
// Indicates that this node is a reexport of a different item
|
||||||
reexport: bool
|
reexport: bool
|
||||||
};
|
};
|
||||||
|
@ -99,6 +109,7 @@ type methoddoc = {
|
||||||
name: str,
|
name: str,
|
||||||
brief: option<str>,
|
brief: option<str>,
|
||||||
desc: option<str>,
|
desc: option<str>,
|
||||||
|
sections: [section],
|
||||||
args: [argdoc],
|
args: [argdoc],
|
||||||
return: retdoc,
|
return: retdoc,
|
||||||
failure: option<str>,
|
failure: option<str>,
|
||||||
|
@ -400,4 +411,8 @@ impl util<A:item> for A {
|
||||||
fn desc() -> option<str> {
|
fn desc() -> option<str> {
|
||||||
self.item().desc
|
self.item().desc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sections() -> [section] {
|
||||||
|
self.item().sections
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -44,6 +44,7 @@ fn mk_itemdoc(id: ast::node_id, name: ast::ident) -> doc::itemdoc {
|
||||||
path: [],
|
path: [],
|
||||||
brief: none,
|
brief: none,
|
||||||
desc: none,
|
desc: none,
|
||||||
|
sections: [],
|
||||||
reexport: false
|
reexport: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,6 +251,7 @@ fn ifacedoc_from_iface(
|
||||||
name: method.ident,
|
name: method.ident,
|
||||||
brief: none,
|
brief: none,
|
||||||
desc: none,
|
desc: none,
|
||||||
|
sections: [],
|
||||||
args: argdocs_from_args(method.decl.inputs),
|
args: argdocs_from_args(method.decl.inputs),
|
||||||
return: {
|
return: {
|
||||||
desc: none
|
desc: none
|
||||||
|
@ -292,6 +294,7 @@ fn impldoc_from_impl(
|
||||||
name: method.ident,
|
name: method.ident,
|
||||||
brief: none,
|
brief: none,
|
||||||
desc: none,
|
desc: none,
|
||||||
|
sections: [],
|
||||||
args: argdocs_from_args(method.decl.inputs),
|
args: argdocs_from_args(method.decl.inputs),
|
||||||
return: {
|
return: {
|
||||||
desc: none
|
desc: none
|
||||||
|
|
|
@ -40,3 +40,4 @@ mod sort_item_type_pass;
|
||||||
mod reexport_pass;
|
mod reexport_pass;
|
||||||
mod par;
|
mod par;
|
||||||
mod page_pass;
|
mod page_pass;
|
||||||
|
mod sectionalize_pass;
|
|
@ -148,8 +148,9 @@ fn run(config: config::config) {
|
||||||
// prune_undoc_items_pass::mk_pass(),
|
// prune_undoc_items_pass::mk_pass(),
|
||||||
prune_hidden_pass::mk_pass(),
|
prune_hidden_pass::mk_pass(),
|
||||||
desc_to_brief_pass::mk_pass(),
|
desc_to_brief_pass::mk_pass(),
|
||||||
trim_pass::mk_pass(),
|
|
||||||
unindent_pass::mk_pass(),
|
unindent_pass::mk_pass(),
|
||||||
|
sectionalize_pass::mk_pass(),
|
||||||
|
trim_pass::mk_pass(),
|
||||||
sort_item_name_pass::mk_pass(),
|
sort_item_name_pass::mk_pass(),
|
||||||
sort_item_type_pass::mk_pass(),
|
sort_item_type_pass::mk_pass(),
|
||||||
markdown_index_pass::mk_pass(config),
|
markdown_index_pass::mk_pass(config),
|
||||||
|
|
237
src/rustdoc/sectionalize_pass.rs
Normal file
237
src/rustdoc/sectionalize_pass.rs
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
#[doc = "Breaks rustdocs into sections according to their headers"];
|
||||||
|
|
||||||
|
export mk_pass;
|
||||||
|
|
||||||
|
fn mk_pass() -> pass {
|
||||||
|
{
|
||||||
|
name: "sectionalize",
|
||||||
|
f: run
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(_srv: astsrv::srv, doc: doc::doc) -> doc::doc {
|
||||||
|
let fold = fold::fold({
|
||||||
|
fold_item: fold_item,
|
||||||
|
fold_iface: fold_iface,
|
||||||
|
fold_impl: fold_impl
|
||||||
|
with *fold::default_any_fold(())
|
||||||
|
});
|
||||||
|
fold.fold_doc(fold, doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_item(fold: fold::fold<()>, doc: doc::itemdoc) -> doc::itemdoc {
|
||||||
|
let doc = fold::default_seq_fold_item(fold, doc);
|
||||||
|
let (desc, sections) = sectionalize(doc.desc);
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: desc,
|
||||||
|
sections: sections
|
||||||
|
with doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_iface(fold: fold::fold<()>, doc: doc::ifacedoc) -> doc::ifacedoc {
|
||||||
|
let doc = fold::default_seq_fold_iface(fold, doc);
|
||||||
|
|
||||||
|
{
|
||||||
|
methods: par::anymap(doc.methods) {|method|
|
||||||
|
let (desc, sections) = sectionalize(method.desc);
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: desc,
|
||||||
|
sections: sections
|
||||||
|
with method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_impl(fold: fold::fold<()>, doc: doc::impldoc) -> doc::impldoc {
|
||||||
|
let doc = fold::default_seq_fold_impl(fold, doc);
|
||||||
|
|
||||||
|
{
|
||||||
|
methods: par::anymap(doc.methods) {|method|
|
||||||
|
let (desc, sections) = sectionalize(method.desc);
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: desc,
|
||||||
|
sections: sections
|
||||||
|
with method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sectionalize(desc: option<str>) -> (option<str>, [doc::section]) {
|
||||||
|
|
||||||
|
#[doc = "
|
||||||
|
|
||||||
|
Take a description of the form
|
||||||
|
|
||||||
|
General text
|
||||||
|
|
||||||
|
# Section header
|
||||||
|
|
||||||
|
Section text
|
||||||
|
|
||||||
|
# Section header
|
||||||
|
|
||||||
|
Section text
|
||||||
|
|
||||||
|
and remove each header and accompanying text into section records.
|
||||||
|
|
||||||
|
"];
|
||||||
|
|
||||||
|
if option::is_none(desc) {
|
||||||
|
ret (none, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lines = str::lines(option::get(desc));
|
||||||
|
|
||||||
|
let new_desc = none;
|
||||||
|
let current_section = none;
|
||||||
|
let sections = [];
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
alt parse_header(line) {
|
||||||
|
some(header) {
|
||||||
|
if option::is_some(current_section) {
|
||||||
|
sections += [option::get(current_section)];
|
||||||
|
}
|
||||||
|
current_section = some({
|
||||||
|
header: header,
|
||||||
|
body: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
none {
|
||||||
|
alt current_section {
|
||||||
|
some(section) {
|
||||||
|
current_section = some({
|
||||||
|
body: section.body + "\n" + line
|
||||||
|
with section
|
||||||
|
});
|
||||||
|
}
|
||||||
|
none {
|
||||||
|
alt new_desc {
|
||||||
|
some(desc) {
|
||||||
|
new_desc = some(desc + "\n" + line);
|
||||||
|
}
|
||||||
|
none {
|
||||||
|
new_desc = some(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if option::is_some(current_section) {
|
||||||
|
sections += [option::get(current_section)];
|
||||||
|
}
|
||||||
|
|
||||||
|
(new_desc, sections)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_header(line: str) -> option<str> {
|
||||||
|
if str::starts_with(line, "# ") {
|
||||||
|
some(str::slice(line, 2u, str::len(line)))
|
||||||
|
} else {
|
||||||
|
none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_create_section_headers() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body\"]\
|
||||||
|
mod a { }");
|
||||||
|
assert str::contains(
|
||||||
|
doc.cratemod().mods()[0].item.sections[0].header,
|
||||||
|
"Header");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_create_section_bodies() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body\"]\
|
||||||
|
mod a { }");
|
||||||
|
assert str::contains(
|
||||||
|
doc.cratemod().mods()[0].item.sections[0].body,
|
||||||
|
"Body");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_create_sections_from_indented_headers() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\n\
|
||||||
|
Text\n # Header\n\
|
||||||
|
Body\"]\
|
||||||
|
mod a { }");
|
||||||
|
assert vec::is_empty(doc.cratemod().mods()[0].item.sections);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_remove_section_text_from_main_desc() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\
|
||||||
|
Description\n\n\
|
||||||
|
# Header\n\
|
||||||
|
Body\"]\
|
||||||
|
mod a { }");
|
||||||
|
assert !str::contains(
|
||||||
|
option::get(doc.cratemod().mods()[0].desc()),
|
||||||
|
"Header");
|
||||||
|
assert !str::contains(
|
||||||
|
option::get(doc.cratemod().mods()[0].desc()),
|
||||||
|
"Body");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_eliminate_desc_if_it_is_just_whitespace() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body\"]\
|
||||||
|
mod a { }");
|
||||||
|
assert doc.cratemod().mods()[0].desc() == none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_sectionalize_iface_methods() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"iface i {
|
||||||
|
#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body\"]\
|
||||||
|
fn a(); }");
|
||||||
|
assert doc.cratemod().ifaces()[0].methods[0].sections.len() == 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_sectionalize_impl_methods() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"impl i for bool {
|
||||||
|
#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body\"]\
|
||||||
|
fn a() { } }");
|
||||||
|
assert doc.cratemod().impls()[0].methods[0].sections.len() == 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
fn mk_doc(source: str) -> doc::doc {
|
||||||
|
astsrv::from_str(source) {|srv|
|
||||||
|
let doc = extract::from_srv(srv, "");
|
||||||
|
let doc = attr_pass::mk_pass().f(srv, doc);
|
||||||
|
run(srv, doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,11 +39,21 @@ fn fold_item(fold: fold::fold<op>, doc: doc::itemdoc) -> doc::itemdoc {
|
||||||
|
|
||||||
{
|
{
|
||||||
brief: maybe_apply_op(fold.ctxt, doc.brief),
|
brief: maybe_apply_op(fold.ctxt, doc.brief),
|
||||||
desc: maybe_apply_op(fold.ctxt, doc.desc)
|
desc: maybe_apply_op(fold.ctxt, doc.desc),
|
||||||
|
sections: apply_to_sections(fold.ctxt, doc.sections)
|
||||||
with doc
|
with doc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_to_sections(op: op, sections: [doc::section]) -> [doc::section] {
|
||||||
|
par::anymap(sections) {|section|
|
||||||
|
{
|
||||||
|
header: op(section.header),
|
||||||
|
body: op(section.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fold_fn(fold: fold::fold<op>, doc: doc::fndoc) -> doc::fndoc {
|
fn fold_fn(fold: fold::fold<op>, doc: doc::fndoc) -> doc::fndoc {
|
||||||
let fold_ctxt = fold.ctxt;
|
let fold_ctxt = fold.ctxt;
|
||||||
let doc = fold::default_seq_fold_fn(fold, doc);
|
let doc = fold::default_seq_fold_fn(fold, doc);
|
||||||
|
@ -108,6 +118,7 @@ fn apply_to_methods(op: op, docs: [doc::methoddoc]) -> [doc::methoddoc] {
|
||||||
{
|
{
|
||||||
brief: maybe_apply_op(op, doc.brief),
|
brief: maybe_apply_op(op, doc.brief),
|
||||||
desc: maybe_apply_op(op, doc.desc),
|
desc: maybe_apply_op(op, doc.desc),
|
||||||
|
sections: apply_to_sections(op, doc.sections),
|
||||||
args: par::anymap(doc.args) {|doc|
|
args: par::anymap(doc.args) {|doc|
|
||||||
{
|
{
|
||||||
desc: maybe_apply_op(op, doc.desc)
|
desc: maybe_apply_op(op, doc.desc)
|
||||||
|
@ -282,12 +293,79 @@ fn should_execute_op_on_type_desc() {
|
||||||
assert doc.cratemod().types()[0].desc() == some("a");
|
assert doc.cratemod().types()[0].desc() == some("a");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_execute_on_item_section_headers() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\
|
||||||
|
# Header \n\
|
||||||
|
Body\"]\
|
||||||
|
fn a() { }");
|
||||||
|
assert doc.cratemod().fns()[0].sections()[0].header == "Header";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_execute_on_item_section_bodies() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body \"]\
|
||||||
|
fn a() { }");
|
||||||
|
assert doc.cratemod().fns()[0].sections()[0].body == "Body";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_execute_on_iface_method_section_headers() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"iface i {
|
||||||
|
#[doc = \"\
|
||||||
|
# Header \n\
|
||||||
|
Body\"]\
|
||||||
|
fn a(); }");
|
||||||
|
assert doc.cratemod().ifaces()[0].methods[0].sections[0].header
|
||||||
|
== "Header";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_execute_on_iface_method_section_bodies() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"iface i {
|
||||||
|
#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body \"]\
|
||||||
|
fn a(); }");
|
||||||
|
assert doc.cratemod().ifaces()[0].methods[0].sections[0].body == "Body";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_execute_on_impl_method_section_headers() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"impl i for bool {
|
||||||
|
#[doc = \"\
|
||||||
|
# Header \n\
|
||||||
|
Body\"]\
|
||||||
|
fn a() { } }");
|
||||||
|
assert doc.cratemod().impls()[0].methods[0].sections[0].header
|
||||||
|
== "Header";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_execute_on_impl_method_section_bodies() {
|
||||||
|
let doc = test::mk_doc(
|
||||||
|
"impl i for bool {
|
||||||
|
#[doc = \"\
|
||||||
|
# Header\n\
|
||||||
|
Body \"]\
|
||||||
|
fn a() { } }");
|
||||||
|
assert doc.cratemod().impls()[0].methods[0].sections[0].body == "Body";
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
fn mk_doc(source: str) -> doc::doc {
|
fn mk_doc(source: str) -> doc::doc {
|
||||||
astsrv::from_str(source) {|srv|
|
astsrv::from_str(source) {|srv|
|
||||||
let doc = extract::from_srv(srv, "");
|
let doc = extract::from_srv(srv, "");
|
||||||
let doc = attr_pass::mk_pass().f(srv, doc);
|
let doc = attr_pass::mk_pass().f(srv, doc);
|
||||||
|
let doc = sectionalize_pass::mk_pass().f(srv, doc);
|
||||||
mk_pass("", {|s| str::trim(s)}).f(srv, doc)
|
mk_pass("", {|s| str::trim(s)}).f(srv, doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue