]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/markdown_pass.rs
Change Freeze to Static
[rust.git] / src / librustdoc / markdown_pass.rs
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.
4 //
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.
10
11 //! Generate markdown from a document tree
12
13
14 use astsrv;
15 use doc::ItemUtils;
16 use doc;
17 use markdown_pass;
18 use markdown_writer::Writer;
19 use markdown_writer::WriterUtils;
20 use markdown_writer::WriterFactory;
21 use pass::Pass;
22 use sort_pass;
23
24 use std::cell::Cell;
25 use std::str;
26 use std::vec;
27 use syntax;
28
29 pub fn mk_pass(writer_factory: WriterFactory) -> Pass {
30     let writer_factory = Cell::new(writer_factory);
31     Pass {
32         name: ~"markdown",
33         f: |srv, doc| run(srv, doc, writer_factory.take())
34     }
35 }
36
37 fn run(
38     srv: astsrv::Srv,
39     doc: doc::Doc,
40     writer_factory: WriterFactory
41 ) -> doc::Doc {
42
43     fn mods_last(item1: &doc::ItemTag, item2: &doc::ItemTag) -> bool {
44         fn is_mod(item: &doc::ItemTag) -> bool {
45             match *item {
46                 doc::ModTag(_) => true,
47                 _ => false
48             }
49         }
50
51         let lteq = !is_mod(item1) || is_mod(item2);
52         lteq
53     }
54
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());
61
62     write_markdown(sorted_doc, writer_factory);
63
64     return doc;
65 }
66
67 struct Ctxt {
68     w: Writer
69 }
70
71 pub fn write_markdown(
72     doc: doc::Doc,
73     writer_factory: WriterFactory
74 ) {
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| {
79         let ctxt = Ctxt {
80             w: writer_factory((*page).clone())
81         };
82         write_page(&ctxt, page)
83     };
84 }
85
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);
91         }
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);
96         }
97     }
98     ctxt.w.put_done();
99 }
100
101 fn write_title(ctxt: &Ctxt, page: doc::Page) {
102     ctxt.w.put_line(fmt!("%% %s", make_title(page)));
103     ctxt.w.put_line(~"");
104 }
105
106 fn make_title(page: doc::Page) -> ~str {
107     let item = match page {
108         doc::CratePage(CrateDoc) => {
109             doc::ModTag(CrateDoc.topmod.clone())
110         }
111         doc::ItemPage(ItemTag) => {
112             ItemTag
113         }
114     };
115     let title = markdown_pass::header_text(item);
116     let title = title.replace("`", "");
117     return title;
118 }
119
120 enum Hlvl {
121     H1 = 1,
122     H2 = 2,
123     H3 = 3,
124     H4 = 4
125 }
126
127 fn write_header(ctxt: &Ctxt, lvl: Hlvl, doc: doc::ItemTag) {
128     let text = header_text(doc);
129     write_header_(ctxt, lvl, text);
130 }
131
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(~"");
136 }
137
138 pub fn header_kind(doc: doc::ItemTag) -> ~str {
139     match doc {
140         doc::ModTag(_) => {
141             if doc.id() == syntax::ast::CRATE_NODE_ID {
142                 ~"Crate"
143             } else {
144                 ~"Module"
145             }
146         }
147         doc::NmodTag(_) => {
148             ~"Foreign module"
149         }
150         doc::FnTag(_) => {
151             ~"Function"
152         }
153         doc::StaticTag(_) => {
154             ~"Static"
155         }
156         doc::EnumTag(_) => {
157             ~"Enum"
158         }
159         doc::TraitTag(_) => {
160             ~"Trait"
161         }
162         doc::ImplTag(_) => {
163             ~"Implementation"
164         }
165         doc::TyTag(_) => {
166             ~"Type"
167         }
168         doc::StructTag(_) => {
169             ~"Struct"
170         }
171     }
172 }
173
174 pub fn header_name(doc: doc::ItemTag) -> ~str {
175     let fullpath = (doc.path() + &[doc.name_()]).connect("::");
176     match &doc {
177         &doc::ModTag(_) if doc.id() != syntax::ast::CRATE_NODE_ID => {
178             fullpath
179         }
180         &doc::NmodTag(_) => {
181             fullpath
182         }
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())
187             } else {
188                 ~""
189             };
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() {
193                 if i == 0 {
194                     trait_part.push_str(" of ");
195                 } else {
196                     trait_part.push_str(", ");
197                 }
198                 trait_part.push_str(*trait_type);
199             }
200             fmt!("%s for %s%s", trait_part, *self_ty, bounds)
201         }
202         _ => {
203             doc.name_()
204         }
205     }
206 }
207
208 pub fn header_text(doc: doc::ItemTag) -> ~str {
209     match &doc {
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())
214             } else {
215                 ~""
216             };
217             let desc = if ImplDoc.trait_types.is_empty() {
218                 fmt!("for `%s`%s", *ImplDoc.self_ty.get_ref(), bounds)
219             } else {
220                 fmt!("of `%s` for `%s`%s",
221                      ImplDoc.trait_types[0],
222                      *ImplDoc.self_ty.get_ref(),
223                      bounds)
224             };
225             return fmt!("%s %s", header_kind, desc);
226         }
227         _ => {}
228     }
229
230     header_text_(header_kind(doc.clone()),
231                  header_name(doc))
232 }
233
234 fn header_text_(kind: &str, name: &str) -> ~str {
235     fmt!("%s `%s`", kind, name)
236 }
237
238 fn write_crate(
239     ctxt: &Ctxt,
240     doc: doc::CrateDoc
241 ) {
242     write_top_module(ctxt, doc.topmod.clone());
243 }
244
245 fn write_top_module(
246     ctxt: &Ctxt,
247     ModDoc: doc::ModDoc
248 ) {
249     write_mod_contents(ctxt, ModDoc);
250 }
251
252 fn write_mod(
253     ctxt: &Ctxt,
254     ModDoc: doc::ModDoc
255 ) {
256     write_mod_contents(ctxt, ModDoc);
257 }
258
259 fn write_common(
260     ctxt: &Ctxt,
261     desc: Option<~str>,
262     sections: &[doc::Section]
263 ) {
264     write_desc(ctxt, desc);
265     write_sections(ctxt, sections);
266 }
267
268 fn write_desc(
269     ctxt: &Ctxt,
270     desc: Option<~str>
271 ) {
272     match desc {
273         Some(desc) => {
274             ctxt.w.put_line(desc);
275             ctxt.w.put_line(~"");
276         }
277         None => ()
278     }
279 }
280
281 fn write_sections(ctxt: &Ctxt, sections: &[doc::Section]) {
282     for section in sections.iter() {
283         write_section(ctxt, (*section).clone());
284     }
285 }
286
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(~"");
291 }
292
293 fn write_mod_contents(
294     ctxt: &Ctxt,
295     doc: doc::ModDoc
296 ) {
297     write_common(ctxt, doc.desc(), doc.sections());
298     if doc.index.is_some() {
299         write_index(ctxt, doc.index.get_ref());
300     }
301
302     for itemTag in doc.items.iter() {
303         write_item(ctxt, (*itemTag).clone());
304     }
305 }
306
307 fn write_item(ctxt: &Ctxt, doc: doc::ItemTag) {
308     write_item_(ctxt, doc, true);
309 }
310
311 fn write_item_no_header(ctxt: &Ctxt, doc: doc::ItemTag) {
312     write_item_(ctxt, doc, false);
313 }
314
315 fn write_item_(ctxt: &Ctxt, doc: doc::ItemTag, write_header: bool) {
316     if write_header {
317         write_item_header(ctxt, doc.clone());
318     }
319
320     match doc {
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),
330     }
331 }
332
333 fn write_item_header(ctxt: &Ctxt, doc: doc::ItemTag) {
334     write_header(ctxt, item_header_lvl(&doc), doc);
335 }
336
337 fn item_header_lvl(doc: &doc::ItemTag) -> Hlvl {
338     match doc {
339         &doc::ModTag(_) | &doc::NmodTag(_) => H1,
340         _ => H2
341     }
342 }
343
344 fn write_index(ctxt: &Ctxt, index: &doc::Index) {
345     if index.entries.is_empty() {
346         return;
347     }
348
349     ctxt.w.put_line(~"<div class='index'>");
350     ctxt.w.put_line(~"");
351
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()));
358         } else {
359             ctxt.w.put_line(fmt!("* [%s](%s)", header, id));
360         }
361     }
362     ctxt.w.put_line(~"");
363     ctxt.w.put_line(~"</div>");
364     ctxt.w.put_line(~"");
365 }
366
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());
371     }
372
373     for FnDoc in doc.fns.iter() {
374         write_item_header(ctxt, doc::FnTag((*FnDoc).clone()));
375         write_fn(ctxt, (*FnDoc).clone());
376     }
377 }
378
379 fn write_fn(
380     ctxt: &Ctxt,
381     doc: doc::FnDoc
382 ) {
383     write_fnlike(ctxt, doc.sig.clone(), doc.desc(), doc.sections());
384 }
385
386 fn write_fnlike(
387     ctxt: &Ctxt,
388     sig: Option<~str>,
389     desc: Option<~str>,
390     sections: &[doc::Section]
391 ) {
392     write_sig(ctxt, sig);
393     write_common(ctxt, desc, sections);
394 }
395
396 fn write_sig(ctxt: &Ctxt, sig: Option<~str>) {
397     match sig {
398         Some(sig) => {
399             ctxt.w.put_line(code_block(sig));
400             ctxt.w.put_line(~"");
401         }
402         None => fail!("unimplemented")
403     }
404 }
405
406 fn code_block(s: ~str) -> ~str {
407     fmt!("~~~ {.rust}
408 %s
409 ~~~", s)
410 }
411
412 fn write_const(
413     ctxt: &Ctxt,
414     doc: doc::ConstDoc
415 ) {
416     write_sig(ctxt, doc.sig.clone());
417     write_common(ctxt, doc.desc(), doc.sections());
418 }
419
420 fn write_enum(
421     ctxt: &Ctxt,
422     doc: doc::EnumDoc
423 ) {
424     write_common(ctxt, doc.desc(), doc.sections());
425     write_variants(ctxt, doc.variants);
426 }
427
428 fn write_variants(
429     ctxt: &Ctxt,
430     docs: &[doc::VariantDoc]
431 ) {
432     if docs.is_empty() {
433         return;
434     }
435
436     write_header_(ctxt, H4, ~"Variants");
437
438     for variant in docs.iter() {
439         write_variant(ctxt, (*variant).clone());
440     }
441
442     ctxt.w.put_line(~"");
443 }
444
445 fn write_variant(ctxt: &Ctxt, doc: doc::VariantDoc) {
446     assert!(doc.sig.is_some());
447     let sig = doc.sig.get_ref();
448
449     // space out list items so they all end up within paragraph elements
450     ctxt.w.put_line(~"");
451
452     match doc.desc.clone() {
453         Some(desc) => {
454             ctxt.w.put_line(list_item_indent(fmt!("* `%s` - %s", *sig, desc)));
455         }
456         None => {
457             ctxt.w.put_line(fmt!("* `%s`", *sig));
458         }
459     }
460 }
461
462 fn list_item_indent(item: &str) -> ~str {
463     let indented = item.any_line_iter().collect::<~[&str]>();
464
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    ")
469 }
470
471 fn write_trait(ctxt: &Ctxt, doc: doc::TraitDoc) {
472     write_common(ctxt, doc.desc(), doc.sections());
473     write_methods(ctxt, doc.methods);
474 }
475
476 fn write_methods(ctxt: &Ctxt, docs: &[doc::MethodDoc]) {
477     for doc in docs.iter() {
478         write_method(ctxt, (*doc).clone());
479     }
480 }
481
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);
485 }
486
487 fn write_impl(ctxt: &Ctxt, doc: doc::ImplDoc) {
488     write_common(ctxt, doc.desc(), doc.sections());
489     write_methods(ctxt, doc.methods);
490 }
491
492 fn write_type(
493     ctxt: &Ctxt,
494     doc: doc::TyDoc
495 ) {
496     write_sig(ctxt, doc.sig.clone());
497     write_common(ctxt, doc.desc(), doc.sections());
498 }
499
500 fn put_struct(
501     ctxt: &Ctxt,
502     doc: doc::StructDoc
503 ) {
504     write_sig(ctxt, doc.sig.clone());
505     write_common(ctxt, doc.desc(), doc.sections());
506 }
507
508 #[cfg(test)]
509 mod test {
510
511     use astsrv;
512     use attr_pass;
513     use config;
514     use desc_to_brief_pass;
515     use doc;
516     use extract;
517     use markdown_index_pass;
518     use markdown_pass::{mk_pass, write_markdown};
519     use markdown_writer;
520     use path_pass;
521     use page_pass;
522     use prune_hidden_pass;
523     use sectionalize_pass;
524     use trim_pass;
525     use tystr_pass;
526     use unindent_pass;
527
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);
532         markdown
533     }
534
535     fn create_doc_srv(source: ~str) -> (astsrv::Srv, doc::Doc) {
536         do astsrv::from_str(source) |srv| {
537
538             let config = config::Config {
539                 output_style: config::DocPerCrate,
540                 .. config::default_config(&Path("whatever"))
541             };
542
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)(
562                 srv.clone(), doc);
563             debug!("doc (index): %?", doc);
564             (srv.clone(), doc)
565         }
566     }
567
568     fn create_doc(source: ~str) -> doc::Doc {
569         let (_, doc) = create_doc_srv(source);
570         doc
571     }
572
573     fn write_markdown_str(
574         doc: doc::Doc
575     ) -> ~str {
576         let (writer_factory, po) = markdown_writer::future_writer_factory();
577         write_markdown(doc, writer_factory);
578         return po.recv().second();
579     }
580
581     fn write_markdown_str_srv(
582         srv: astsrv::Srv,
583         doc: doc::Doc
584     ) -> ~str {
585         let (writer_factory, po) = markdown_writer::future_writer_factory();
586         let pass = mk_pass(writer_factory);
587         (pass.f)(srv, doc);
588         return po.recv().second();
589     }
590
591     #[test]
592     fn write_markdown_should_write_mod_headers() {
593         let markdown = render(~"mod moo { }");
594         assert!(markdown.contains("# Module `moo`"));
595     }
596
597     #[test]
598     fn should_leave_blank_line_after_header() {
599         let markdown = render(~"mod morp { }");
600         assert!(markdown.contains("Module `morp`\n\n"));
601     }
602
603     #[test]
604     fn should_write_modules_last() {
605         /*
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.
611         */
612         let markdown = render(
613             ~"mod a { }\
614               fn b() { }\
615               mod c {
616 }\
617               fn d() { }"
618         );
619
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();
624
625         assert!(idx_b < idx_d);
626         assert!(idx_d < idx_a);
627         assert!(idx_a < idx_c);
628     }
629
630     #[test]
631     fn should_request_new_writer_for_each_page() {
632         // This port will send us a (page, str) pair for every writer
633         // that was created
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) {
641             po.recv();
642         }
643     }
644
645     #[test]
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();
654             match page {
655                 doc::CratePage(_) => {
656                     assert!(markdown.contains("% Crate core"));
657                 }
658                 doc::ItemPage(_) => {
659                     assert!(markdown.contains("% Module a"));
660                 }
661             }
662         }
663     }
664
665     #[test]
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`"));
669     }
670
671     #[test]
672     fn should_write_sections() {
673         let markdown = render(
674             ~"#[doc = \"\
675               # Header\n\
676               Body\"]\
677               mod a {
678 }");
679         assert!(markdown.contains("#### Header\n\nBody\n\n"));
680     }
681
682     #[test]
683     fn should_write_crate_description() {
684         let markdown = render(~"#[doc = \"this is the crate\"];");
685         assert!(markdown.contains("this is the crate"));
686     }
687
688
689     #[test]
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"
695         ));
696     }
697
698     #[test]
699     fn should_write_index_brief() {
700         let markdown = render(~"#[doc = \"test\"] mod a { }");
701         assert!(markdown.contains("(#module-a) - test\n"));
702     }
703
704     #[test]
705     fn should_not_write_index_if_no_entries() {
706         let markdown = render(~"");
707         assert!(!markdown.contains("\n\n\n"));
708     }
709
710     #[test]
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"
715         ));
716     }
717
718     #[test]
719     fn should_write_foreign_fns() {
720         let markdown = render(
721             ~"extern { #[doc = \"test\"] fn a(); }");
722         assert!(markdown.contains("test"));
723     }
724
725     #[test]
726     fn should_write_foreign_fn_headers() {
727         let markdown = render(
728             ~"extern { #[doc = \"test\"] fn a(); }");
729         assert!(markdown.contains("## Function `a`"));
730     }
731
732     #[test]
733     fn write_markdown_should_write_function_header() {
734         let markdown = render(~"fn func() { }");
735         assert!(markdown.contains("## Function `func`"));
736     }
737
738     #[test]
739     fn should_write_the_function_signature() {
740         let markdown = render(~"#[doc = \"f\"] fn a() { }");
741         assert!(markdown.contains("\n~~~ {.rust}\nfn a()\n"));
742     }
743
744     #[test]
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"));
748     }
749
750     #[test]
751     fn should_correctly_bracket_fn_signature() {
752         let doc = create_doc(~"fn a() { }");
753         let doc = doc::Doc{
754             pages: ~[
755                 doc::CratePage(doc::CrateDoc{
756                     topmod: doc::ModDoc{
757                         items: ~[doc::FnTag(doc::SimpleItemDoc{
758                             sig: Some(~"line 1\nline 2"),
759                             .. (doc.cratemod().fns()[0]).clone()
760                         })],
761                         .. doc.cratemod()
762                     },
763                     .. doc.CrateDoc()
764                 })
765             ]
766         };
767         let markdown = write_markdown_str(doc);
768         assert!(markdown.contains("~~~ {.rust}\nline 1\nline 2\n~~~"));
769     }
770
771     #[test]
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()"));
775     }
776
777     #[test]
778     fn should_write_const_header() {
779         let markdown = render(~"static a: bool = true;");
780         assert!(markdown.contains("## Static `a`\n\n"));
781     }
782
783     #[test]
784     fn should_write_const_description() {
785         let markdown = render(
786             ~"#[doc = \"b\"]\
787               static a: bool = true;");
788         assert!(markdown.contains("\n\nb\n\n"));
789     }
790
791     #[test]
792     fn should_write_enum_header() {
793         let markdown = render(~"enum a { b }");
794         assert!(markdown.contains("## Enum `a`\n\n"));
795     }
796
797     #[test]
798     fn should_write_enum_description() {
799         let markdown = render(~"#[doc = \"b\"] enum a { b }");
800         assert!(markdown.contains("\n\nb\n\n"));
801     }
802
803     #[test]
804     fn should_write_variant_list() {
805         let markdown = render(
806             ~"enum a { \
807               #[doc = \"test\"] b, \
808               #[doc = \"test\"] c }");
809         assert!(markdown.contains(
810             "\n\n#### Variants\n\
811              \n\
812              \n* `b` - test\
813              \n\
814              \n* `c` - test\n\n"));
815     }
816
817     #[test]
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\
822              \n\
823              \n* `b`\
824              \n\
825              \n* `c`\n\n"));
826     }
827
828     #[test]
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\
834              \n\
835              \n* `b` - line 1\
836              \n    \
837              \n    line 2\
838              \n\
839              \n* `c`\n\n"));
840     }
841
842     #[test]
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\
847              \n\
848              \n* `b(int)`\
849              \n\
850              \n* `c(int)` - a\n\n"));
851     }
852
853     #[test]
854     fn should_write_trait_header() {
855         let markdown = render(~"trait i { fn a(); }");
856         assert!(markdown.contains("## Trait `i`"));
857     }
858
859     #[test]
860     fn should_write_trait_desc() {
861         let markdown = render(~"#[doc = \"desc\"] trait i { fn a(); }");
862         assert!(markdown.contains("desc"));
863     }
864
865     #[test]
866     fn should_write_trait_method_header() {
867         let markdown = render(~"trait i { fn a(); }");
868         assert!(markdown.contains("### Method `a`"));
869     }
870
871     #[test]
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)"));
875     }
876
877     #[test]
878     fn should_write_impl_header() {
879         let markdown = render(~"impl int { fn a() { } }");
880         assert!(markdown.contains("## Implementation for `int`"));
881     }
882
883     #[test]
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>`"));
887     }
888
889     #[test]
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`"));
894     }
895
896     #[test]
897     fn should_write_impl_desc() {
898         let markdown = render(
899             ~"#[doc = \"desc\"] impl int { fn a() { } }");
900         assert!(markdown.contains("desc"));
901     }
902
903     #[test]
904     fn should_write_impl_method_header() {
905         let markdown = render(
906             ~"impl int { fn a() { } }");
907         assert!(markdown.contains("### Method `a`"));
908     }
909
910     #[test]
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)"));
915     }
916
917     #[test]
918     fn should_write_type_header() {
919         let markdown = render(~"type t = int;");
920         assert!(markdown.contains("## Type `t`"));
921     }
922
923     #[test]
924     fn should_write_type_desc() {
925         let markdown = render(
926             ~"#[doc = \"desc\"] type t = int;");
927         assert!(markdown.contains("\n\ndesc\n\n"));
928     }
929
930     #[test]
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"));
934     }
935
936     #[test]
937     fn should_put_struct_header() {
938         let markdown = render(~"struct S { field: () }");
939         assert!(markdown.contains("## Struct `S`\n\n"));
940     }
941 }