1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Generate markdown from a document tree
18 use markdown_writer::Writer;
19 use markdown_writer::WriterUtils;
20 use markdown_writer::WriterFactory;
29 pub fn mk_pass(writer_factory: WriterFactory) -> Pass {
30 let writer_factory = Cell::new(writer_factory);
33 f: |srv, doc| run(srv, doc, writer_factory.take())
40 writer_factory: WriterFactory
43 fn mods_last(item1: &doc::ItemTag, item2: &doc::ItemTag) -> bool {
44 fn is_mod(item: &doc::ItemTag) -> bool {
46 doc::ModTag(_) => true,
51 let lteq = !is_mod(item1) || is_mod(item2);
55 // Sort the items so mods come last. All mods will be
56 // output at the same header level so sorting mods last
57 // makes the headers come out nested correctly.
58 let sorted_doc = (sort_pass::mk_pass(
59 ~"mods last", mods_last
60 ).f)(srv, doc.clone());
62 write_markdown(sorted_doc, writer_factory);
71 pub fn write_markdown(
73 writer_factory: WriterFactory
75 // There is easy parallelism to be had here, but
76 // we don't want to spawn too many pandoc processes.
77 // (See #2484, which is closed.)
78 do doc.pages.map |page| {
80 w: writer_factory((*page).clone())
82 write_page(&ctxt, page)
86 fn write_page(ctxt: &Ctxt, page: &doc::Page) {
87 write_title(ctxt, (*page).clone());
88 match (*page).clone() {
89 doc::CratePage(doc) => {
90 write_crate(ctxt, doc);
92 doc::ItemPage(doc) => {
93 // We don't write a header for item's pages because their
94 // header in the html output is created by the page title
95 write_item_no_header(ctxt, doc);
101 fn write_title(ctxt: &Ctxt, page: doc::Page) {
102 ctxt.w.put_line(fmt!("%% %s", make_title(page)));
103 ctxt.w.put_line(~"");
106 fn make_title(page: doc::Page) -> ~str {
107 let item = match page {
108 doc::CratePage(CrateDoc) => {
109 doc::ModTag(CrateDoc.topmod.clone())
111 doc::ItemPage(ItemTag) => {
115 let title = markdown_pass::header_text(item);
116 let title = title.replace("`", "");
127 fn write_header(ctxt: &Ctxt, lvl: Hlvl, doc: doc::ItemTag) {
128 let text = header_text(doc);
129 write_header_(ctxt, lvl, text);
132 fn write_header_(ctxt: &Ctxt, lvl: Hlvl, title: ~str) {
133 let hashes = str::from_chars(vec::from_elem(lvl as uint, '#'));
134 ctxt.w.put_line(fmt!("%s %s", hashes, title));
135 ctxt.w.put_line(~"");
138 pub fn header_kind(doc: doc::ItemTag) -> ~str {
141 if doc.id() == syntax::ast::CRATE_NODE_ID {
153 doc::StaticTag(_) => {
159 doc::TraitTag(_) => {
168 doc::StructTag(_) => {
174 pub fn header_name(doc: doc::ItemTag) -> ~str {
175 let fullpath = (doc.path() + &[doc.name_()]).connect("::");
177 &doc::ModTag(_) if doc.id() != syntax::ast::CRATE_NODE_ID => {
180 &doc::NmodTag(_) => {
183 &doc::ImplTag(ref doc) => {
184 assert!(doc.self_ty.is_some());
185 let bounds = if doc.bounds_str.is_some() {
186 fmt!(" where %s", *doc.bounds_str.get_ref())
190 let self_ty = doc.self_ty.get_ref();
191 let mut trait_part = ~"";
192 for (i, trait_type) in doc.trait_types.iter().enumerate() {
194 trait_part.push_str(" of ");
196 trait_part.push_str(", ");
198 trait_part.push_str(*trait_type);
200 fmt!("%s for %s%s", trait_part, *self_ty, bounds)
208 pub fn header_text(doc: doc::ItemTag) -> ~str {
210 &doc::ImplTag(ref ImplDoc) => {
211 let header_kind = header_kind(doc.clone());
212 let bounds = if ImplDoc.bounds_str.is_some() {
213 fmt!(" where `%s`", *ImplDoc.bounds_str.get_ref())
217 let desc = if ImplDoc.trait_types.is_empty() {
218 fmt!("for `%s`%s", *ImplDoc.self_ty.get_ref(), bounds)
220 fmt!("of `%s` for `%s`%s",
221 ImplDoc.trait_types[0],
222 *ImplDoc.self_ty.get_ref(),
225 return fmt!("%s %s", header_kind, desc);
230 header_text_(header_kind(doc.clone()),
234 fn header_text_(kind: &str, name: &str) -> ~str {
235 fmt!("%s `%s`", kind, name)
242 write_top_module(ctxt, doc.topmod.clone());
249 write_mod_contents(ctxt, ModDoc);
256 write_mod_contents(ctxt, ModDoc);
262 sections: &[doc::Section]
264 write_desc(ctxt, desc);
265 write_sections(ctxt, sections);
274 ctxt.w.put_line(desc);
275 ctxt.w.put_line(~"");
281 fn write_sections(ctxt: &Ctxt, sections: &[doc::Section]) {
282 for section in sections.iter() {
283 write_section(ctxt, (*section).clone());
287 fn write_section(ctxt: &Ctxt, section: doc::Section) {
288 write_header_(ctxt, H4, section.header.clone());
289 ctxt.w.put_line(section.body.clone());
290 ctxt.w.put_line(~"");
293 fn write_mod_contents(
297 write_common(ctxt, doc.desc(), doc.sections());
298 if doc.index.is_some() {
299 write_index(ctxt, doc.index.get_ref());
302 for itemTag in doc.items.iter() {
303 write_item(ctxt, (*itemTag).clone());
307 fn write_item(ctxt: &Ctxt, doc: doc::ItemTag) {
308 write_item_(ctxt, doc, true);
311 fn write_item_no_header(ctxt: &Ctxt, doc: doc::ItemTag) {
312 write_item_(ctxt, doc, false);
315 fn write_item_(ctxt: &Ctxt, doc: doc::ItemTag, write_header: bool) {
317 write_item_header(ctxt, doc.clone());
321 doc::ModTag(ModDoc) => write_mod(ctxt, ModDoc),
322 doc::NmodTag(nModDoc) => write_nmod(ctxt, nModDoc),
323 doc::FnTag(FnDoc) => write_fn(ctxt, FnDoc),
324 doc::ConstTag(ConstDoc) => write_const(ctxt, ConstDoc),
325 doc::EnumTag(EnumDoc) => write_enum(ctxt, EnumDoc),
326 doc::TraitTag(TraitDoc) => write_trait(ctxt, TraitDoc),
327 doc::ImplTag(ImplDoc) => write_impl(ctxt, ImplDoc),
328 doc::TyTag(TyDoc) => write_type(ctxt, TyDoc),
329 doc::StructTag(StructDoc) => put_struct(ctxt, StructDoc),
333 fn write_item_header(ctxt: &Ctxt, doc: doc::ItemTag) {
334 write_header(ctxt, item_header_lvl(&doc), doc);
337 fn item_header_lvl(doc: &doc::ItemTag) -> Hlvl {
339 &doc::ModTag(_) | &doc::NmodTag(_) => H1,
344 fn write_index(ctxt: &Ctxt, index: &doc::Index) {
345 if index.entries.is_empty() {
349 ctxt.w.put_line(~"<div class='index'>");
350 ctxt.w.put_line(~"");
352 for entry in index.entries.iter() {
353 let header = header_text_(entry.kind, entry.name);
354 let id = entry.link.clone();
355 if entry.brief.is_some() {
356 ctxt.w.put_line(fmt!("* [%s](%s) - %s",
357 header, id, *entry.brief.get_ref()));
359 ctxt.w.put_line(fmt!("* [%s](%s)", header, id));
362 ctxt.w.put_line(~"");
363 ctxt.w.put_line(~"</div>");
364 ctxt.w.put_line(~"");
367 fn write_nmod(ctxt: &Ctxt, doc: doc::NmodDoc) {
368 write_common(ctxt, doc.desc(), doc.sections());
369 if doc.index.is_some() {
370 write_index(ctxt, doc.index.get_ref());
373 for FnDoc in doc.fns.iter() {
374 write_item_header(ctxt, doc::FnTag((*FnDoc).clone()));
375 write_fn(ctxt, (*FnDoc).clone());
383 write_fnlike(ctxt, doc.sig.clone(), doc.desc(), doc.sections());
390 sections: &[doc::Section]
392 write_sig(ctxt, sig);
393 write_common(ctxt, desc, sections);
396 fn write_sig(ctxt: &Ctxt, sig: Option<~str>) {
399 ctxt.w.put_line(code_block(sig));
400 ctxt.w.put_line(~"");
402 None => fail!("unimplemented")
406 fn code_block(s: ~str) -> ~str {
416 write_sig(ctxt, doc.sig.clone());
417 write_common(ctxt, doc.desc(), doc.sections());
424 write_common(ctxt, doc.desc(), doc.sections());
425 write_variants(ctxt, doc.variants);
430 docs: &[doc::VariantDoc]
436 write_header_(ctxt, H4, ~"Variants");
438 for variant in docs.iter() {
439 write_variant(ctxt, (*variant).clone());
442 ctxt.w.put_line(~"");
445 fn write_variant(ctxt: &Ctxt, doc: doc::VariantDoc) {
446 assert!(doc.sig.is_some());
447 let sig = doc.sig.get_ref();
449 // space out list items so they all end up within paragraph elements
450 ctxt.w.put_line(~"");
452 match doc.desc.clone() {
454 ctxt.w.put_line(list_item_indent(fmt!("* `%s` - %s", *sig, desc)));
457 ctxt.w.put_line(fmt!("* `%s`", *sig));
462 fn list_item_indent(item: &str) -> ~str {
463 let indented = item.any_line_iter().collect::<~[&str]>();
465 // separate markdown elements within `*` lists must be indented by four
466 // spaces, or they will escape the list context. indenting everything
467 // seems fine though.
468 indented.connect("\n ")
471 fn write_trait(ctxt: &Ctxt, doc: doc::TraitDoc) {
472 write_common(ctxt, doc.desc(), doc.sections());
473 write_methods(ctxt, doc.methods);
476 fn write_methods(ctxt: &Ctxt, docs: &[doc::MethodDoc]) {
477 for doc in docs.iter() {
478 write_method(ctxt, (*doc).clone());
482 fn write_method(ctxt: &Ctxt, doc: doc::MethodDoc) {
483 write_header_(ctxt, H3, header_text_("Method", doc.name));
484 write_fnlike(ctxt, doc.sig.clone(), doc.desc.clone(), doc.sections);
487 fn write_impl(ctxt: &Ctxt, doc: doc::ImplDoc) {
488 write_common(ctxt, doc.desc(), doc.sections());
489 write_methods(ctxt, doc.methods);
496 write_sig(ctxt, doc.sig.clone());
497 write_common(ctxt, doc.desc(), doc.sections());
504 write_sig(ctxt, doc.sig.clone());
505 write_common(ctxt, doc.desc(), doc.sections());
514 use desc_to_brief_pass;
517 use markdown_index_pass;
518 use markdown_pass::{mk_pass, write_markdown};
522 use prune_hidden_pass;
523 use sectionalize_pass;
528 fn render(source: ~str) -> ~str {
529 let (srv, doc) = create_doc_srv(source);
530 let markdown = write_markdown_str_srv(srv, doc);
531 debug!("markdown: %s", markdown);
535 fn create_doc_srv(source: ~str) -> (astsrv::Srv, doc::Doc) {
536 do astsrv::from_str(source) |srv| {
538 let config = config::Config {
539 output_style: config::DocPerCrate,
540 .. config::default_config(&Path("whatever"))
543 let doc = extract::from_srv(srv.clone(), ~"");
544 debug!("doc (extract): %?", doc);
545 let doc = (tystr_pass::mk_pass().f)(srv.clone(), doc);
546 debug!("doc (tystr): %?", doc);
547 let doc = (path_pass::mk_pass().f)(srv.clone(), doc);
548 debug!("doc (path): %?", doc);
549 let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
550 debug!("doc (attr): %?", doc);
551 let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc);
552 debug!("doc (prune_hidden): %?", doc);
553 let doc = (desc_to_brief_pass::mk_pass().f)(srv.clone(), doc);
554 debug!("doc (desc_to_brief): %?", doc);
555 let doc = (unindent_pass::mk_pass().f)(srv.clone(), doc);
556 debug!("doc (unindent): %?", doc);
557 let doc = (sectionalize_pass::mk_pass().f)(srv.clone(), doc);
558 debug!("doc (trim): %?", doc);
559 let doc = (trim_pass::mk_pass().f)(srv.clone(), doc);
560 debug!("doc (sectionalize): %?", doc);
561 let doc = (markdown_index_pass::mk_pass(config).f)(
563 debug!("doc (index): %?", doc);
568 fn create_doc(source: ~str) -> doc::Doc {
569 let (_, doc) = create_doc_srv(source);
573 fn write_markdown_str(
576 let (writer_factory, po) = markdown_writer::future_writer_factory();
577 write_markdown(doc, writer_factory);
578 return po.recv().second();
581 fn write_markdown_str_srv(
585 let (writer_factory, po) = markdown_writer::future_writer_factory();
586 let pass = mk_pass(writer_factory);
588 return po.recv().second();
592 fn write_markdown_should_write_mod_headers() {
593 let markdown = render(~"mod moo { }");
594 assert!(markdown.contains("# Module `moo`"));
598 fn should_leave_blank_line_after_header() {
599 let markdown = render(~"mod morp { }");
600 assert!(markdown.contains("Module `morp`\n\n"));
604 fn should_write_modules_last() {
606 Because the markdown pass writes all modules at the same level of
607 indentation (it doesn't 'nest' them), we need to make sure that we
608 write all of the modules contained in each module after all other
609 types of items, or else the header nesting will end up wrong, with
610 modules appearing to contain items that they do not.
612 let markdown = render(
620 let idx_a = markdown.find_str("# Module `a`").unwrap();
621 let idx_b = markdown.find_str("## Function `b`").unwrap();
622 let idx_c = markdown.find_str("# Module `c`").unwrap();
623 let idx_d = markdown.find_str("## Function `d`").unwrap();
625 assert!(idx_b < idx_d);
626 assert!(idx_d < idx_a);
627 assert!(idx_a < idx_c);
631 fn should_request_new_writer_for_each_page() {
632 // This port will send us a (page, str) pair for every writer
634 let (writer_factory, po) = markdown_writer::future_writer_factory();
635 let (srv, doc) = create_doc_srv(~"mod a { }");
636 // Split the document up into pages
637 let doc = (page_pass::mk_pass(config::DocPerMod).f)(srv, doc);
638 write_markdown(doc, writer_factory);
639 // We expect two pages to have been written
640 for _ in range(0, 2u) {
646 fn should_write_title_for_each_page() {
647 let (writer_factory, po) = markdown_writer::future_writer_factory();
648 let (srv, doc) = create_doc_srv(
649 ~"#[link(name = \"core\")]; mod a { }");
650 let doc = (page_pass::mk_pass(config::DocPerMod).f)(srv, doc);
651 write_markdown(doc, writer_factory);
652 for _ in range(0, 2u) {
653 let (page, markdown) = po.recv();
655 doc::CratePage(_) => {
656 assert!(markdown.contains("% Crate core"));
658 doc::ItemPage(_) => {
659 assert!(markdown.contains("% Module a"));
666 fn should_write_full_path_to_mod() {
667 let markdown = render(~"mod a { mod b { mod c { } } }");
668 assert!(markdown.contains("# Module `a::b::c`"));
672 fn should_write_sections() {
673 let markdown = render(
679 assert!(markdown.contains("#### Header\n\nBody\n\n"));
683 fn should_write_crate_description() {
684 let markdown = render(~"#[doc = \"this is the crate\"];");
685 assert!(markdown.contains("this is the crate"));
690 fn should_write_index() {
691 let markdown = render(~"mod a { } mod b { }");
692 assert!(markdown.contains(
693 "\n\n* [Module `a`](#module-a)\n\
694 * [Module `b`](#module-b)\n\n"
699 fn should_write_index_brief() {
700 let markdown = render(~"#[doc = \"test\"] mod a { }");
701 assert!(markdown.contains("(#module-a) - test\n"));
705 fn should_not_write_index_if_no_entries() {
706 let markdown = render(~"");
707 assert!(!markdown.contains("\n\n\n"));
711 fn should_write_index_for_foreign_mods() {
712 let markdown = render(~"extern { fn a(); }");
713 assert!(markdown.contains(
714 "\n\n* [Function `a`](#function-a)\n\n"
719 fn should_write_foreign_fns() {
720 let markdown = render(
721 ~"extern { #[doc = \"test\"] fn a(); }");
722 assert!(markdown.contains("test"));
726 fn should_write_foreign_fn_headers() {
727 let markdown = render(
728 ~"extern { #[doc = \"test\"] fn a(); }");
729 assert!(markdown.contains("## Function `a`"));
733 fn write_markdown_should_write_function_header() {
734 let markdown = render(~"fn func() { }");
735 assert!(markdown.contains("## Function `func`"));
739 fn should_write_the_function_signature() {
740 let markdown = render(~"#[doc = \"f\"] fn a() { }");
741 assert!(markdown.contains("\n~~~ {.rust}\nfn a()\n"));
745 fn should_insert_blank_line_after_fn_signature() {
746 let markdown = render(~"#[doc = \"f\"] fn a() { }");
747 assert!(markdown.contains("fn a()\n~~~\n\n"));
751 fn should_correctly_bracket_fn_signature() {
752 let doc = create_doc(~"fn a() { }");
755 doc::CratePage(doc::CrateDoc{
757 items: ~[doc::FnTag(doc::SimpleItemDoc{
758 sig: Some(~"line 1\nline 2"),
759 .. (doc.cratemod().fns()[0]).clone()
767 let markdown = write_markdown_str(doc);
768 assert!(markdown.contains("~~~ {.rust}\nline 1\nline 2\n~~~"));
772 fn should_leave_blank_line_between_fn_header_and_sig() {
773 let markdown = render(~"fn a() { }");
774 assert!(markdown.contains("Function `a`\n\n~~~ {.rust}\nfn a()"));
778 fn should_write_const_header() {
779 let markdown = render(~"static a: bool = true;");
780 assert!(markdown.contains("## Static `a`\n\n"));
784 fn should_write_const_description() {
785 let markdown = render(
787 static a: bool = true;");
788 assert!(markdown.contains("\n\nb\n\n"));
792 fn should_write_enum_header() {
793 let markdown = render(~"enum a { b }");
794 assert!(markdown.contains("## Enum `a`\n\n"));
798 fn should_write_enum_description() {
799 let markdown = render(~"#[doc = \"b\"] enum a { b }");
800 assert!(markdown.contains("\n\nb\n\n"));
804 fn should_write_variant_list() {
805 let markdown = render(
807 #[doc = \"test\"] b, \
808 #[doc = \"test\"] c }");
809 assert!(markdown.contains(
810 "\n\n#### Variants\n\
814 \n* `c` - test\n\n"));
818 fn should_write_variant_list_without_descs() {
819 let markdown = render(~"enum a { b, c }");
820 assert!(markdown.contains(
821 "\n\n#### Variants\n\
829 fn should_write_variant_list_with_indent() {
830 let markdown = render(
831 ~"enum a { #[doc = \"line 1\\n\\nline 2\"] b, c }");
832 assert!(markdown.contains(
833 "\n\n#### Variants\n\
843 fn should_write_variant_list_with_signatures() {
844 let markdown = render(~"enum a { b(int), #[doc = \"a\"] c(int) }");
845 assert!(markdown.contains(
846 "\n\n#### Variants\n\
850 \n* `c(int)` - a\n\n"));
854 fn should_write_trait_header() {
855 let markdown = render(~"trait i { fn a(); }");
856 assert!(markdown.contains("## Trait `i`"));
860 fn should_write_trait_desc() {
861 let markdown = render(~"#[doc = \"desc\"] trait i { fn a(); }");
862 assert!(markdown.contains("desc"));
866 fn should_write_trait_method_header() {
867 let markdown = render(~"trait i { fn a(); }");
868 assert!(markdown.contains("### Method `a`"));
872 fn should_write_trait_method_signature() {
873 let markdown = render(~"trait i { fn a(&self); }");
874 assert!(markdown.contains("\n~~~ {.rust}\nfn a(&self)"));
878 fn should_write_impl_header() {
879 let markdown = render(~"impl int { fn a() { } }");
880 assert!(markdown.contains("## Implementation for `int`"));
884 fn should_write_impl_header_with_bounds() {
885 let markdown = render(~"impl <T> int<T> { }");
886 assert!(markdown.contains("## Implementation for `int<T>` where `<T>`"));
890 fn should_write_impl_header_with_trait() {
891 let markdown = render(~"impl j for int { fn a() { } }");
892 assert!(markdown.contains(
893 "## Implementation of `j` for `int`"));
897 fn should_write_impl_desc() {
898 let markdown = render(
899 ~"#[doc = \"desc\"] impl int { fn a() { } }");
900 assert!(markdown.contains("desc"));
904 fn should_write_impl_method_header() {
905 let markdown = render(
906 ~"impl int { fn a() { } }");
907 assert!(markdown.contains("### Method `a`"));
911 fn should_write_impl_method_signature() {
912 let markdown = render(
913 ~"impl int { fn a(&mut self) { } }");
914 assert!(markdown.contains("~~~ {.rust}\nfn a(&mut self)"));
918 fn should_write_type_header() {
919 let markdown = render(~"type t = int;");
920 assert!(markdown.contains("## Type `t`"));
924 fn should_write_type_desc() {
925 let markdown = render(
926 ~"#[doc = \"desc\"] type t = int;");
927 assert!(markdown.contains("\n\ndesc\n\n"));
931 fn should_write_type_signature() {
932 let markdown = render(~"type t = int;");
933 assert!(markdown.contains("\n\n~~~ {.rust}\ntype t = int\n~~~\n"));
937 fn should_put_struct_header() {
938 let markdown = render(~"struct S { field: () }");
939 assert!(markdown.contains("## Struct `S`\n\n"));