// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Breaks rustdocs into sections according to their headers use astsrv; use doc::ItemUtils; use doc; use fold::Fold; use fold; use pass::Pass; pub fn mk_pass() -> Pass { Pass { name: ~"sectionalize", f: run } } pub fn run(_srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc { let fold = Fold { fold_item: fold_item, fold_trait: fold_trait, fold_impl: fold_impl, .. 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.clone()); doc::ItemDoc { desc: desc, sections: sections, .. doc } } fn fold_trait(fold: &fold::Fold<()>, doc: doc::TraitDoc) -> doc::TraitDoc { let doc = fold::default_seq_fold_trait(fold, doc); doc::TraitDoc { methods: do doc.methods.map |method| { let (desc, sections) = sectionalize(method.desc.clone()); doc::MethodDoc { desc: desc, sections: sections, .. (*method).clone() } }, .. doc } } fn fold_impl(fold: &fold::Fold<()>, doc: doc::ImplDoc) -> doc::ImplDoc { let doc = fold::default_seq_fold_impl(fold, doc); doc::ImplDoc { methods: do doc.methods.map |method| { let (desc, sections) = sectionalize(method.desc.clone()); doc::MethodDoc { desc: desc, sections: sections, .. (*method).clone() } }, .. doc } } fn sectionalize(desc: Option<~str>) -> (Option<~str>, ~[doc::Section]) { /*! * 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 desc.is_none() { return (None, ~[]); } let mut new_desc = None::<~str>; let mut current_section: Option = None; let mut sections = ~[]; for line in desc.get_ref().any_line_iter() { match parse_header(line) { Some(header) => { if current_section.is_some() { sections.push((*current_section.get_ref()).clone()); } current_section = Some(doc::Section { header: header.to_owned(), body: ~"" }); } None => { match current_section.clone() { Some(section) => { current_section = Some(doc::Section { body: fmt!("%s\n%s", section.body, line), .. section }); } None => { new_desc = match new_desc.clone() { Some(desc) => { Some(fmt!("%s\n%s", desc, line)) } None => { Some(line.to_owned()) } }; } } } } } if current_section.is_some() { sections.push(current_section.unwrap()); } (new_desc, sections) } fn parse_header<'a>(line: &'a str) -> Option<&'a str> { if line.starts_with("# ") { Some(line.slice_from(2)) } else { None } } #[cfg(test)] mod test { use astsrv; use attr_pass; use doc; use extract; use prune_hidden_pass; use sectionalize_pass::run; fn mk_doc(source: ~str) -> doc::Doc { do astsrv::from_str(source.clone()) |srv| { let doc = extract::from_srv(srv.clone(), ~""); let doc = (attr_pass::mk_pass().f)(srv.clone(), doc); let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc); run(srv.clone(), doc) } } #[test] fn should_create_section_headers() { let doc = mk_doc( ~"#[doc = \"\ # Header\n\ Body\"]\ mod a { }"); assert!(doc.cratemod().mods()[0].item.sections[0].header.contains("Header")); } #[test] fn should_create_section_bodies() { let doc = mk_doc( ~"#[doc = \"\ # Header\n\ Body\"]\ mod a { }"); assert!(doc.cratemod().mods()[0].item.sections[0].body.contains("Body")); } #[test] fn should_not_create_sections_from_indented_headers() { let doc = mk_doc( ~"#[doc = \"\n\ Text\n # Header\n\ Body\"]\ mod a { }"); assert!(doc.cratemod().mods()[0].item.sections.is_empty()); } #[test] fn should_remove_section_text_from_main_desc() { let doc = mk_doc( ~"#[doc = \"\ Description\n\n\ # Header\n\ Body\"]\ mod a { }"); assert!(!doc.cratemod().mods()[0].desc().unwrap().contains("Header")); assert!(!doc.cratemod().mods()[0].desc().unwrap().contains("Body")); } #[test] fn should_eliminate_desc_if_it_is_just_whitespace() { let doc = mk_doc( ~"#[doc = \"\ # Header\n\ Body\"]\ mod a { }"); assert_eq!(doc.cratemod().mods()[0].desc(), None); } #[test] fn should_sectionalize_trait_methods() { let doc = mk_doc( ~"trait i { #[doc = \"\ # Header\n\ Body\"]\ fn a(); }"); assert_eq!(doc.cratemod().traits()[0].methods[0].sections.len(), 1u); } #[test] fn should_sectionalize_impl_methods() { let doc = mk_doc( ~"impl bool { #[doc = \"\ # Header\n\ Body\"]\ fn a() { } }"); assert_eq!(doc.cratemod().impls()[0].methods[0].sections.len(), 1u); } }