From 0ad8265fee1af5551a0d9ca284788ee1a2116fa5 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Fri, 9 Mar 2012 11:47:31 -0800 Subject: [PATCH] rustdoc: Add the concept of 'sections' --- src/rustdoc/doc.rs | 15 ++ src/rustdoc/extract.rs | 3 + src/rustdoc/rustdoc.rc | 1 + src/rustdoc/rustdoc.rs | 3 +- src/rustdoc/sectionalize_pass.rs | 237 +++++++++++++++++++++++++++++++ src/rustdoc/text_pass.rs | 80 ++++++++++- 6 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 src/rustdoc/sectionalize_pass.rs diff --git a/src/rustdoc/doc.rs b/src/rustdoc/doc.rs index 896d1a9dfa8..256baf7e50f 100644 --- a/src/rustdoc/doc.rs +++ b/src/rustdoc/doc.rs @@ -11,6 +11,15 @@ enum page { 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 // probably be fewer special cases if the crate had its own name and // topmod's name was the empty string. @@ -36,6 +45,7 @@ type itemdoc = { path: [str], brief: option, desc: option, + sections: [section], // Indicates that this node is a reexport of a different item reexport: bool }; @@ -99,6 +109,7 @@ type methoddoc = { name: str, brief: option, desc: option, + sections: [section], args: [argdoc], return: retdoc, failure: option, @@ -400,4 +411,8 @@ impl util for A { fn desc() -> option { self.item().desc } + + fn sections() -> [section] { + self.item().sections + } } \ No newline at end of file diff --git a/src/rustdoc/extract.rs b/src/rustdoc/extract.rs index f52ac727c02..3aadfbaa40c 100644 --- a/src/rustdoc/extract.rs +++ b/src/rustdoc/extract.rs @@ -44,6 +44,7 @@ fn mk_itemdoc(id: ast::node_id, name: ast::ident) -> doc::itemdoc { path: [], brief: none, desc: none, + sections: [], reexport: false } } @@ -250,6 +251,7 @@ fn ifacedoc_from_iface( name: method.ident, brief: none, desc: none, + sections: [], args: argdocs_from_args(method.decl.inputs), return: { desc: none @@ -292,6 +294,7 @@ fn impldoc_from_impl( name: method.ident, brief: none, desc: none, + sections: [], args: argdocs_from_args(method.decl.inputs), return: { desc: none diff --git a/src/rustdoc/rustdoc.rc b/src/rustdoc/rustdoc.rc index 1ae040f6e6e..c879846eb16 100644 --- a/src/rustdoc/rustdoc.rc +++ b/src/rustdoc/rustdoc.rc @@ -40,3 +40,4 @@ mod sort_item_type_pass; mod reexport_pass; mod par; mod page_pass; +mod sectionalize_pass; \ No newline at end of file diff --git a/src/rustdoc/rustdoc.rs b/src/rustdoc/rustdoc.rs index c99a1139e6c..a893dccfa71 100755 --- a/src/rustdoc/rustdoc.rs +++ b/src/rustdoc/rustdoc.rs @@ -148,8 +148,9 @@ fn run(config: config::config) { // prune_undoc_items_pass::mk_pass(), prune_hidden_pass::mk_pass(), desc_to_brief_pass::mk_pass(), - trim_pass::mk_pass(), unindent_pass::mk_pass(), + sectionalize_pass::mk_pass(), + trim_pass::mk_pass(), sort_item_name_pass::mk_pass(), sort_item_type_pass::mk_pass(), markdown_index_pass::mk_pass(config), diff --git a/src/rustdoc/sectionalize_pass.rs b/src/rustdoc/sectionalize_pass.rs new file mode 100644 index 00000000000..fc957835822 --- /dev/null +++ b/src/rustdoc/sectionalize_pass.rs @@ -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) -> (option, [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 { + 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) + } + } +} diff --git a/src/rustdoc/text_pass.rs b/src/rustdoc/text_pass.rs index 847c99803b6..cb197883331 100644 --- a/src/rustdoc/text_pass.rs +++ b/src/rustdoc/text_pass.rs @@ -39,11 +39,21 @@ fn fold_item(fold: fold::fold, doc: doc::itemdoc) -> doc::itemdoc { { 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 } } +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, doc: doc::fndoc) -> doc::fndoc { let fold_ctxt = fold.ctxt; 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), desc: maybe_apply_op(op, doc.desc), + sections: apply_to_sections(op, doc.sections), args: par::anymap(doc.args) {|doc| { 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"); } +#[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)] 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); + let doc = sectionalize_pass::mk_pass().f(srv, doc); mk_pass("", {|s| str::trim(s)}).f(srv, doc) } }