From 69b8ef806b3742ba7d41a77cd216713c84f36254 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 7 Mar 2014 14:31:41 +1100 Subject: [PATCH 01/10] rustdoc: run on plain Markdown files. This theoretically gives rustdoc the ability to render our guides, tutorial and manual (not in practice, since the files themselves need to be adjusted slightly to use Sundown-compatible functionality). Fixes #11392. --- src/doc/rustdoc.md | 25 +++++ src/librustdoc/html/markdown.rs | 18 +++- src/librustdoc/lib.rs | 38 ++++++- src/librustdoc/markdown.rs | 171 ++++++++++++++++++++++++++++++++ src/librustdoc/test.rs | 68 +++++++++---- 5 files changed, 292 insertions(+), 28 deletions(-) create mode 100644 src/librustdoc/markdown.rs diff --git a/src/doc/rustdoc.md b/src/doc/rustdoc.md index 545cafd7f31d1..415db46be5b10 100644 --- a/src/doc/rustdoc.md +++ b/src/doc/rustdoc.md @@ -181,3 +181,28 @@ rustdoc will implicitly add `extern crate ;` where `` is the name the crate being tested to the top of each code example. This means that rustdoc must be able to find a compiled version of the library crate being tested. Extra search paths may be added via the `-L` flag to `rustdoc`. + +# Standalone Markdown files + +As well as Rust crates, rustdoc supports rendering pure Markdown files +into HTML and testing the code snippets from them. A Markdown file is +detected by a `.md` or `.markdown` extension. + +There are 4 options to modify the output that Rustdoc creates. +- `--markdown-css PATH`: adds a `` tag pointing to `PATH`. +- `--markdown-in-header FILE`: includes the contents of `FILE` at the + end of the `...` section. +- `--markdown-before-content FILE`: includes the contents of `FILE` + directly after ``, before the rendered content (including the + title). +- `--markdown-after-content FILE`: includes the contents of `FILE` + directly before ``, after all the rendered content. + +All of these can be specified multiple times, and they are output in +the order in which they are specified. The first line of the file must +be the title, prefixed with `%` (e.g. this page has `% Rust +Documentation` on the first line). + +Like with a Rust crate, the `--test` argument will run the code +examples to check they compile, and obeys any `--test-args` flags. The +tests are named after the last `#` heading. diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 19a28931a8a1f..30040a1846c38 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -28,7 +28,6 @@ use std::cast; use std::fmt; -use std::intrinsics; use std::io; use std::libc; use std::local_data; @@ -258,7 +257,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) { }; if ignore { return } vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| { - let tests: &mut ::test::Collector = intrinsics::transmute(opaque); + let tests = &mut *(opaque as *mut ::test::Collector); let text = str::from_utf8(text).unwrap(); let mut lines = text.lines().map(|l| stripped_filtered_line(l).unwrap_or(l)); let text = lines.to_owned_vec().connect("\n"); @@ -266,6 +265,19 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) { }) } } + extern fn header(_ob: *buf, text: *buf, level: libc::c_int, opaque: *libc::c_void) { + unsafe { + let tests = &mut *(opaque as *mut ::test::Collector); + if text.is_null() { + tests.register_header("", level as u32); + } else { + vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| { + let text = str::from_utf8(text).unwrap(); + tests.register_header(text, level as u32); + }) + } + } + } unsafe { let ob = bufnew(OUTPUT_UNIT); @@ -276,7 +288,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) { blockcode: Some(block), blockquote: None, blockhtml: None, - header: None, + header: Some(header), other: mem::init() }; diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 19e3aed646269..94bc5ed252663 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -14,7 +14,7 @@ #[crate_type = "dylib"]; #[crate_type = "rlib"]; -#[feature(globs, struct_variant, managed_boxes)]; +#[feature(globs, struct_variant, managed_boxes, macro_rules)]; extern crate syntax; extern crate rustc; @@ -26,6 +26,7 @@ extern crate collections; extern crate testing = "test"; extern crate time; +use std::cell::RefCell; use std::local_data; use std::io; use std::io::{File, MemWriter}; @@ -44,6 +45,7 @@ pub mod html { pub mod markdown; pub mod render; } +pub mod markdown; pub mod passes; pub mod plugins; pub mod visit_ast; @@ -105,6 +107,19 @@ pub fn opts() -> ~[getopts::OptGroup] { optflag("", "test", "run code examples as tests"), optmulti("", "test-args", "arguments to pass to the test runner", "ARGS"), + optmulti("", "markdown-css", "CSS files to include via in a rendered Markdown file", + "FILES"), + optmulti("", "markdown-in-header", + "files to include inline in the section of a rendered Markdown file", + "FILES"), + optmulti("", "markdown-before-content", + "files to include inline between and the content of a rendered \ + Markdown file", + "FILES"), + optmulti("", "markdown-after-content", + "files to include inline between the content and of a rendered \ + Markdown file", + "FILES"), ] } @@ -137,8 +152,24 @@ pub fn main_args(args: &[~str]) -> int { } let input = matches.free[0].as_slice(); - if matches.opt_present("test") { - return test::run(input, &matches); + let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice())); + let libs = @RefCell::new(libs.move_iter().collect()); + + let test_args = matches.opt_strs("test-args"); + let test_args = test_args.iter().flat_map(|s| s.words()).map(|s| s.to_owned()).to_owned_vec(); + + let should_test = matches.opt_present("test"); + let markdown_input = input.ends_with(".md") || input.ends_with(".markdown"); + + let output = matches.opt_str("o").map(|s| Path::new(s)); + + match (should_test, markdown_input) { + (true, true) => return markdown::test(input, libs, test_args), + (true, false) => return test::run(input, libs, test_args), + + (false, true) => return markdown::render(input, output.unwrap_or(Path::new("doc")), + &matches), + (false, false) => {} } if matches.opt_strs("passes") == ~[~"list"] { @@ -163,7 +194,6 @@ pub fn main_args(args: &[~str]) -> int { info!("going to format"); let started = time::precise_time_ns(); - let output = matches.opt_str("o").map(|s| Path::new(s)); match matches.opt_str("w") { Some(~"html") | None => { match html::render::run(krate, output.unwrap_or(Path::new("doc"))) { diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs new file mode 100644 index 0000000000000..a998e3d69944f --- /dev/null +++ b/src/librustdoc/markdown.rs @@ -0,0 +1,171 @@ +// Copyright 2014 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. + +use std::{str, io}; +use std::cell::RefCell; +use std::vec_ng::Vec; + +use collections::HashSet; + +use getopts; +use testing; + +use html::escape::Escape; +use html::markdown::{Markdown, find_testable_code, reset_headers}; +use test::Collector; + +fn load_string(input: &Path) -> io::IoResult> { + let mut f = try!(io::File::open(input)); + let d = try!(f.read_to_end()); + Ok(str::from_utf8_owned(d)) +} +macro_rules! load_or_return { + ($input: expr, $cant_read: expr, $not_utf8: expr) => { + { + let input = Path::new($input); + match load_string(&input) { + Err(e) => { + let _ = writeln!(&mut io::stderr(), + "error reading `{}`: {}", input.display(), e); + return $cant_read; + } + Ok(None) => { + let _ = writeln!(&mut io::stderr(), + "error reading `{}`: not UTF-8", input.display()); + return $not_utf8; + } + Ok(Some(s)) => s + } + } + } +} + +/// Separate any lines at the start of the file that begin with `%`. +fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) { + let mut metadata = Vec::new(); + for line in s.lines() { + if line.starts_with("%") { + // remove % + metadata.push(line.slice_from(1).trim_left()) + } else { + let line_start_byte = s.subslice_offset(line); + return (metadata, s.slice_from(line_start_byte)); + } + } + // if we're here, then all lines were metadata % lines. + (metadata, "") +} + +fn load_external_files(names: &[~str]) -> Option<~str> { + let mut out = ~""; + for name in names.iter() { + out.push_str(load_or_return!(name.as_slice(), None, None)); + out.push_char('\n'); + } + Some(out) +} + +/// Render `input` (e.g. "foo.md") into an HTML file in `output` +/// (e.g. output = "bar" => "bar/foo.html"). +pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int { + let input_p = Path::new(input); + output.push(input_p.filestem().unwrap()); + output.set_extension("html"); + + let mut css = ~""; + for name in matches.opt_strs("markdown-css").iter() { + let s = format!("\n", name); + css.push_str(s) + } + + let input_str = load_or_return!(input, 1, 2); + + let (in_header, before_content, after_content) = + match (load_external_files(matches.opt_strs("markdown-in-header")), + load_external_files(matches.opt_strs("markdown-before-content")), + load_external_files(matches.opt_strs("markdown-after-content"))) { + (Some(a), Some(b), Some(c)) => (a,b,c), + _ => return 3 + }; + + let mut out = match io::File::create(&output) { + Err(e) => { + let _ = writeln!(&mut io::stderr(), + "error opening `{}` for writing: {}", + output.display(), e); + return 4; + } + Ok(f) => f + }; + + let (metadata, text) = extract_leading_metadata(input_str); + if metadata.len() == 0 { + let _ = writeln!(&mut io::stderr(), + "invalid markdown file: expecting initial line with `% ...TITLE...`"); + return 5; + } + let title = metadata.get(0).as_slice(); + + reset_headers(); + + let err = write!( + &mut out, + r#" + + + + + {title} + + {css} + {in_header} + + + + + {before_content} +

{title}

+ {text} + {after_content} + +"#, + title = Escape(title), + css = css, + in_header = in_header, + before_content = before_content, + text = Markdown(text), + after_content = after_content); + + match err { + Err(e) => { + let _ = writeln!(&mut io::stderr(), + "error writing to `{}`: {}", + output.display(), e); + 6 + } + Ok(_) => 0 + } +} + +/// Run any tests/code examples in the markdown file `input`. +pub fn test(input: &str, libs: @RefCell>, mut test_args: ~[~str]) -> int { + let input_str = load_or_return!(input, 1, 2); + + let mut collector = Collector::new(input.to_owned(), libs, true); + find_testable_code(input_str, &mut collector); + test_args.unshift(~"rustdoctest"); + testing::test_main(test_args, collector.tests); + 0 +} diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index 5edc24c606659..640a3304094a8 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -9,6 +9,7 @@ // except according to those terms. use std::cell::RefCell; +use std::char; use std::io; use std::io::Process; use std::local_data; @@ -22,7 +23,6 @@ use rustc::back::link; use rustc::driver::driver; use rustc::driver::session; use rustc::metadata::creader::Loader; -use getopts; use syntax::diagnostic; use syntax::parse; use syntax::codemap::CodeMap; @@ -35,11 +35,9 @@ use html::markdown; use passes; use visit_ast::RustdocVisitor; -pub fn run(input: &str, matches: &getopts::Matches) -> int { +pub fn run(input: &str, libs: @RefCell>, mut test_args: ~[~str]) -> int { let input_path = Path::new(input); let input = driver::FileInput(input_path.clone()); - let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice())); - let libs = @RefCell::new(libs.move_iter().collect()); let sessopts = @session::Options { maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()), @@ -79,21 +77,12 @@ pub fn run(input: &str, matches: &getopts::Matches) -> int { let (krate, _) = passes::unindent_comments(krate); let (krate, _) = passes::collapse_docs(krate); - let mut collector = Collector { - tests: ~[], - names: ~[], - cnt: 0, - libs: libs, - cratename: krate.name.to_owned(), - }; + let mut collector = Collector::new(krate.name.to_owned(), libs, false); collector.fold_crate(krate); - let args = matches.opt_strs("test-args"); - let mut args = args.iter().flat_map(|s| s.words()).map(|s| s.to_owned()); - let mut args = args.to_owned_vec(); - args.unshift(~"rustdoctest"); + test_args.unshift(~"rustdoctest"); - testing::test_main(args, collector.tests); + testing::test_main(test_args, collector.tests); 0 } @@ -198,17 +187,35 @@ fn maketest(s: &str, cratename: &str) -> ~str { } pub struct Collector { - priv tests: ~[testing::TestDescAndFn], + tests: ~[testing::TestDescAndFn], priv names: ~[~str], priv libs: @RefCell>, priv cnt: uint, + priv use_headers: bool, + priv current_header: Option<~str>, priv cratename: ~str, } impl Collector { - pub fn add_test(&mut self, test: &str, should_fail: bool, no_run: bool) { - let test = test.to_owned(); - let name = format!("{}_{}", self.names.connect("::"), self.cnt); + pub fn new(cratename: ~str, libs: @RefCell>, use_headers: bool) -> Collector { + Collector { + tests: ~[], + names: ~[], + libs: libs, + cnt: 0, + use_headers: use_headers, + current_header: None, + cratename: cratename + } + } + + pub fn add_test(&mut self, test: ~str, should_fail: bool, no_run: bool) { + let name = if self.use_headers { + let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or(""); + format!("{}_{}", s, self.cnt) + } else { + format!("{}_{}", self.names.connect("::"), self.cnt) + }; self.cnt += 1; let libs = self.libs.borrow(); let libs = (*libs.get()).clone(); @@ -225,6 +232,25 @@ impl Collector { }), }); } + + pub fn register_header(&mut self, name: &str, level: u32) { + if self.use_headers && level == 1 { + // we use these headings as test names, so it's good if + // they're valid identifiers. + let name = name.chars().enumerate().map(|(i, c)| { + if (i == 0 && char::is_XID_start(c)) || + (i != 0 && char::is_XID_continue(c)) { + c + } else { + '_' + } + }).collect::<~str>(); + + // new header => reset count. + self.cnt = 0; + self.current_header = Some(name); + } + } } impl DocFolder for Collector { @@ -237,7 +263,7 @@ impl DocFolder for Collector { match item.doc_value() { Some(doc) => { self.cnt = 0; - markdown::find_testable_code(doc, self); + markdown::find_testable_code(doc, &mut *self); } None => {} } From f22c96cc888eac4c1c5d44ea4765e5c2a65f64f3 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 8 Mar 2014 01:13:17 +1100 Subject: [PATCH 02/10] rustdoc: add table-of-contents recording & rendering, use it with plain markdown files. This means that # Foo ## Bar # Baz ### Qux ## Quz Gets a TOC like 1 Foo 1.1 Bar 2 Baz 2.0.1 Qux 2.1 Quz This functionality is only used when rendering a single markdown file, never on an individual module, although it could very feasibly be extended to allow modules to opt-in to a table of contents (std::fmt comes to mind). --- src/librustdoc/html/markdown.rs | 50 ++++-- src/librustdoc/html/toc.rs | 269 ++++++++++++++++++++++++++++++++ src/librustdoc/lib.rs | 1 + src/librustdoc/markdown.rs | 4 +- 4 files changed, 312 insertions(+), 12 deletions(-) create mode 100644 src/librustdoc/html/toc.rs diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 30040a1846c38..8f7829dda9d1d 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -36,12 +36,16 @@ use std::str; use std::vec; use collections::HashMap; +use html::toc::TocBuilder; use html::highlight; /// A unit struct which has the `fmt::Show` trait implemented. When /// formatted, this struct will emit the HTML corresponding to the rendered /// version of the contained markdown string. pub struct Markdown<'a>(&'a str); +/// A unit struct like `Markdown`, that renders the markdown with a +/// table of contents. +pub struct MarkdownWithToc<'a>(&'a str); static OUTPUT_UNIT: libc::size_t = 64; static MKDEXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 0; @@ -75,6 +79,7 @@ struct html_renderopt { struct my_opaque { opt: html_renderopt, dfltblk: extern "C" fn(*buf, *buf, *buf, *libc::c_void), + toc_builder: Option, } struct buf { @@ -121,7 +126,7 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> { local_data_key!(used_header_map: HashMap<~str, uint>) -pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { +pub fn render(w: &mut io::Writer, s: &str, print_toc: bool) -> fmt::Result { extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) { unsafe { let my_opaque: &my_opaque = cast::transmute(opaque); @@ -162,7 +167,7 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { } extern fn header(ob: *buf, text: *buf, level: libc::c_int, - _opaque: *libc::c_void) { + opaque: *libc::c_void) { // sundown does this, we may as well too "\n".with_c_str(|p| unsafe { bufputs(ob, p) }); @@ -183,6 +188,8 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { } }).to_owned_vec().connect("-"); + let opaque = unsafe {&mut *(opaque as *mut my_opaque)}; + // Make sure our hyphenated ID is unique for this page let id = local_data::get_mut(used_header_map, |map| { let map = map.unwrap(); @@ -194,9 +201,18 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { id.clone() }); + let sec = match opaque.toc_builder { + Some(ref mut builder) => { + builder.push(level as u32, s.clone(), id.clone()) + } + None => {""} + }; + // Render the HTML - let text = format!(r#"{}"#, - s, lvl = level, id = id); + let text = format!(r#"{sec_len,plural,=0{}other{{sec} }}{}"#, + s, lvl = level, id = id, + sec_len = sec.len(), sec = sec); + text.with_c_str(|p| unsafe { bufputs(ob, p) }); } @@ -218,23 +234,30 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { let mut callbacks: sd_callbacks = mem::init(); sdhtml_renderer(&callbacks, &options, 0); - let opaque = my_opaque { + let mut opaque = my_opaque { opt: options, dfltblk: callbacks.blockcode.unwrap(), + toc_builder: if print_toc {Some(TocBuilder::new())} else {None} }; callbacks.blockcode = Some(block); callbacks.header = Some(header); let markdown = sd_markdown_new(extensions, 16, &callbacks, - &opaque as *my_opaque as *libc::c_void); + &mut opaque as *mut my_opaque as *libc::c_void); sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown); sd_markdown_free(markdown); - let ret = vec::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| { - w.write(buf) - }); + let mut ret = match opaque.toc_builder { + Some(b) => write!(w, "", b.into_toc()), + None => Ok(()) + }; + if ret.is_ok() { + ret = vec::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| { + w.write(buf) + }); + } bufrelease(ob); ret } @@ -319,6 +342,13 @@ impl<'a> fmt::Show for Markdown<'a> { let Markdown(md) = *self; // This is actually common enough to special-case if md.len() == 0 { return Ok(()) } - render(fmt.buf, md.as_slice()) + render(fmt.buf, md.as_slice(), false) + } +} + +impl<'a> fmt::Show for MarkdownWithToc<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let MarkdownWithToc(md) = *self; + render(fmt.buf, md.as_slice(), true) } } diff --git a/src/librustdoc/html/toc.rs b/src/librustdoc/html/toc.rs new file mode 100644 index 0000000000000..61031c222e74a --- /dev/null +++ b/src/librustdoc/html/toc.rs @@ -0,0 +1,269 @@ +// Copyright 2013 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. + +//! Table-of-contents creation. + +use std::fmt; +use std::vec_ng::Vec; + +/// A (recursive) table of contents +#[deriving(Eq)] +pub struct Toc { + /// The levels are strictly decreasing, i.e. + /// + /// entries[0].level >= entries[1].level >= ... + /// + /// Normally they are equal, but can differ in cases like A and B, + /// both of which end up in the same `Toc` as they have the same + /// parent (Main). + /// + /// # Main + /// ### A + /// ## B + priv entries: Vec +} + +impl Toc { + fn count_entries_with_level(&self, level: u32) -> uint { + self.entries.iter().count(|e| e.level == level) + } +} + +#[deriving(Eq)] +pub struct TocEntry { + priv level: u32, + priv sec_number: ~str, + priv name: ~str, + priv id: ~str, + priv children: Toc, +} + +/// Progressive construction of a table of contents. +#[deriving(Eq)] +pub struct TocBuilder { + priv top_level: Toc, + /// The current heirachy of parent headings, the levels are + /// strictly increasing (i.e. chain[0].level < chain[1].level < + /// ...) with each entry being the most recent occurance of a + /// heading with that level (it doesn't include the most recent + /// occurences of every level, just, if *is* in `chain` then is is + /// the most recent one). + /// + /// We also have `chain[0].level <= top_level.entries[last]`. + priv chain: Vec +} + +impl TocBuilder { + pub fn new() -> TocBuilder { + TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() } + } + + + /// Convert into a true `Toc` struct. + pub fn into_toc(mut self) -> Toc { + // we know all levels are >= 1. + self.fold_until(0); + self.top_level + } + + /// Collapse the chain until the first heading more important than + /// `level` (i.e. lower level) + /// + /// Example: + /// + /// ## A + /// # B + /// # C + /// ## D + /// ## E + /// ### F + /// #### G + /// ### H + /// + /// If we are considering H (i.e. level 3), then A and B are in + /// self.top_level, D is in C.children, and C, E, F, G are in + /// self.chain. + /// + /// When we attempt to push H, we realise that first G is not the + /// parent (level is too high) so it is popped from chain and put + /// into F.children, then F isn't the parent (level is equal, aka + /// sibling), so it's also popped and put into E.children. + /// + /// This leaves us looking at E, which does have a smaller level, + /// and, by construction, it's the most recent thing with smaller + /// level, i.e. it's the immediate parent of H. + fn fold_until(&mut self, level: u32) { + let mut this = None; + loop { + match self.chain.pop() { + Some(mut next) => { + this.map(|e| next.children.entries.push(e)); + if next.level < level { + // this is the parent we want, so return it to + // its rightful place. + self.chain.push(next); + return + } else { + this = Some(next); + } + } + None => { + this.map(|e| self.top_level.entries.push(e)); + return + } + } + } + } + + /// Push a level `level` heading into the appropriate place in the + /// heirarchy, returning a string containing the section number in + /// `..` format. + pub fn push<'a>(&'a mut self, level: u32, name: ~str, id: ~str) -> &'a str { + assert!(level >= 1); + + // collapse all previous sections into their parents until we + // get to relevant heading (i.e. the first one with a smaller + // level than us) + self.fold_until(level); + + let mut sec_number; + { + let (toc_level, toc) = match self.chain.last() { + None => { + sec_number = ~""; + (0, &self.top_level) + } + Some(entry) => { + sec_number = entry.sec_number.clone(); + sec_number.push_str("."); + (entry.level, &entry.children) + } + }; + // fill in any missing zeros, e.g. for + // # Foo (1) + // ### Bar (1.0.1) + for _ in range(toc_level, level - 1) { + sec_number.push_str("0."); + } + let number = toc.count_entries_with_level(level); + sec_number.push_str(format!("{}", number + 1)) + } + + self.chain.push(TocEntry { + level: level, + name: name, + sec_number: sec_number, + id: id, + children: Toc { entries: Vec::new() } + }); + + // get the thing we just pushed, so we can borrow the string + // out of it with the right lifetime + let just_inserted = self.chain.mut_last().unwrap(); + just_inserted.sec_number.as_slice() + } +} + +impl fmt::Show for Toc { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + try!(write!(fmt.buf, "
    ")); + for entry in self.entries.iter() { + // recursively format this table of contents (the + // `{children}` is the key). + try!(write!(fmt.buf, + "\n
  • {num} {name}{children}
  • ", + id = entry.id, + num = entry.sec_number, name = entry.name, + children = entry.children)) + } + write!(fmt.buf, "
") + } +} + +#[cfg(test)] +mod test { + use super::{TocBuilder, Toc, TocEntry}; + + #[test] + fn builder_smoke() { + let mut builder = TocBuilder::new(); + + // this is purposely not using a fancy macro like below so + // that we're sure that this is doing the correct thing, and + // there's been no macro mistake. + macro_rules! push { + ($level: expr, $name: expr) => { + assert_eq!(builder.push($level, $name.to_owned(), ~""), $name); + } + } + push!(2, "0.1"); + push!(1, "1"); + { + push!(2, "1.1"); + { + push!(3, "1.1.1"); + push!(3, "1.1.2"); + } + push!(2, "1.2"); + { + push!(3, "1.2.1"); + push!(3, "1.2.2"); + } + } + push!(1, "2"); + push!(1, "3"); + { + push!(4, "3.0.0.1"); + { + push!(6, "3.0.0.1.0.1"); + } + push!(4, "3.0.0.2"); + push!(2, "3.1"); + { + push!(4, "3.1.0.1"); + } + } + + macro_rules! toc { + ($(($level: expr, $name: expr, $(($sub: tt))* )),*) => { + Toc { + entries: vec!( + $( + TocEntry { + level: $level, + name: $name.to_owned(), + sec_number: $name.to_owned(), + id: ~"", + children: toc!($($sub),*) + } + ),* + ) + } + } + } + let expected = toc!( + (2, "0.1", ), + + (1, "1", + ((2, "1.1", ((3, "1.1.1", )) ((3, "1.1.2", )))) + ((2, "1.2", ((3, "1.2.1", )) ((3, "1.2.2", )))) + ), + + (1, "2", ), + + (1, "3", + ((4, "3.0.0.1", ((6, "3.0.0.1.0.1", )))) + ((4, "3.0.0.2", )) + ((2, "3.1", ((4, "3.1.0.1", )))) + ) + ); + assert_eq!(expected, builder.into_toc()); + } +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 94bc5ed252663..2d08dca97b986 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -44,6 +44,7 @@ pub mod html { pub mod layout; pub mod markdown; pub mod render; + pub mod toc; } pub mod markdown; pub mod passes; diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index a998e3d69944f..67a08706e982c 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -18,7 +18,7 @@ use getopts; use testing; use html::escape::Escape; -use html::markdown::{Markdown, find_testable_code, reset_headers}; +use html::markdown::{MarkdownWithToc, find_testable_code, reset_headers}; use test::Collector; fn load_string(input: &Path) -> io::IoResult> { @@ -145,7 +145,7 @@ pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int css = css, in_header = in_header, before_content = before_content, - text = Markdown(text), + text = MarkdownWithToc(text), after_content = after_content); match err { From 7a70ec1ba6e64949cab5edcad33b0d7538065884 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 8 Mar 2014 01:24:54 +1100 Subject: [PATCH 03/10] rustdoc: hardcode each header as a link. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids having to include JS in the guide/tutorial/manual pages just to get the headers being links. The on-hover behaviour showing the little section marker § is preserved, because that gives a useful hint that the heading is a link. --- src/librustdoc/html/markdown.rs | 3 ++- src/librustdoc/html/render.rs | 3 ++- src/librustdoc/html/static/main.css | 19 +++++++++++++------ src/librustdoc/html/static/main.js | 6 ------ 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 8f7829dda9d1d..61c1cd734a38d 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -209,7 +209,8 @@ pub fn render(w: &mut io::Writer, s: &str, print_toc: bool) -> fmt::Result { }; // Render the HTML - let text = format!(r#"{sec_len,plural,=0{}other{{sec} }}{}"#, + let text = format!(r#"{sec_len,plural,=0{}other{{sec} }}{}"#, s, lvl = level, id = id, sec_len = sec.len(), sec = sec); diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index e985de4d2f578..b705e976e4656 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -1064,7 +1064,8 @@ fn item_module(w: &mut Writer, cx: &Context, clean::ForeignStaticItem(..) => ("ffi-statics", "Foreign Statics"), clean::MacroItem(..) => ("macros", "Macros"), }; - try!(write!(w, "

{}

\n", short, name)); + try!(write!(w, "

{name}

\n
", + id = short, name = name)); } match myitem.inner { diff --git a/src/librustdoc/html/static/main.css b/src/librustdoc/html/static/main.css index 9a5cdaba33c80..0efee51a21909 100644 --- a/src/librustdoc/html/static/main.css +++ b/src/librustdoc/html/static/main.css @@ -320,9 +320,16 @@ pre.rust .string { color: #718C00; } pre.rust .lifetime { color: #C13928; } pre.rust .attribute, pre.rust .attribute .ident { color: #C82829; } -h1 a.anchor, -h2 a.anchor, -h3 a.anchor { display: none; } -h1:hover a.anchor, -h2:hover a.anchor, -h3:hover a.anchor { display: inline-block; } +h1:hover a:after, +h2:hover a:after, +h3:hover a:after, +h4:hover a:after, +h5:hover a:after, +h6:hover a:after { + content: ' § '; +} + +h1.fqn:hover a:after, +:hover a.fnname:after { + content: none; +} diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 59970ac4508db..3056bca68d784 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -600,10 +600,4 @@ initSearch(searchIndex); - $.each($('h1, h2, h3'), function(idx, element) { - if ($(element).attr('id') != undefined) { - $(element).append(' § '); - } - }); }()); From 8e904120487f67172eb2d509531ed8f9e230af3a Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 8 Mar 2014 21:30:43 +1100 Subject: [PATCH 04/10] rustdoc: adding some common feature gates when testing a markdown file. The manual, tutorial and guides need the feature gates quite often, unfortunately, so this is the low-cost path to migrating to use rustdoc. This is only activated for pure-Markdown files. Preferably this would be avoided: #12773 --- src/librustdoc/markdown.rs | 2 +- src/librustdoc/test.rs | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 67a08706e982c..5d8e0008b870d 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -163,7 +163,7 @@ pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int pub fn test(input: &str, libs: @RefCell>, mut test_args: ~[~str]) -> int { let input_str = load_or_return!(input, 1, 2); - let mut collector = Collector::new(input.to_owned(), libs, true); + let mut collector = Collector::new(input.to_owned(), libs, true, true); find_testable_code(input_str, &mut collector); test_args.unshift(~"rustdoctest"); testing::test_main(test_args, collector.tests); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index 640a3304094a8..45607a0992e23 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -77,7 +77,7 @@ pub fn run(input: &str, libs: @RefCell>, mut test_args: ~[~str]) - let (krate, _) = passes::unindent_comments(krate); let (krate, _) = passes::collapse_docs(krate); - let mut collector = Collector::new(krate.name.to_owned(), libs, false); + let mut collector = Collector::new(krate.name.to_owned(), libs, false, false); collector.fold_crate(krate); test_args.unshift(~"rustdoctest"); @@ -88,8 +88,8 @@ pub fn run(input: &str, libs: @RefCell>, mut test_args: ~[~str]) - } fn runtest(test: &str, cratename: &str, libs: HashSet, should_fail: bool, - no_run: bool) { - let test = maketest(test, cratename); + no_run: bool, loose_feature_gating: bool) { + let test = maketest(test, cratename, loose_feature_gating); let parsesess = parse::new_parse_sess(); let input = driver::StrInput(test); @@ -162,11 +162,18 @@ fn runtest(test: &str, cratename: &str, libs: HashSet, should_fail: bool, } } -fn maketest(s: &str, cratename: &str) -> ~str { +fn maketest(s: &str, cratename: &str, loose_feature_gating: bool) -> ~str { let mut prog = ~r" #[deny(warnings)]; #[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)]; "; + + if loose_feature_gating { + // FIXME #12773: avoid inserting these when the tutorial & manual + // etc. have been updated to not use them so prolifically. + prog.push_str("#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n"); + } + if !s.contains("extern crate") { if s.contains("extra") { prog.push_str("extern crate extra;\n"); @@ -194,10 +201,13 @@ pub struct Collector { priv use_headers: bool, priv current_header: Option<~str>, priv cratename: ~str, + + priv loose_feature_gating: bool } impl Collector { - pub fn new(cratename: ~str, libs: @RefCell>, use_headers: bool) -> Collector { + pub fn new(cratename: ~str, libs: @RefCell>, + use_headers: bool, loose_feature_gating: bool) -> Collector { Collector { tests: ~[], names: ~[], @@ -205,7 +215,9 @@ impl Collector { cnt: 0, use_headers: use_headers, current_header: None, - cratename: cratename + cratename: cratename, + + loose_feature_gating: loose_feature_gating } } @@ -220,6 +232,7 @@ impl Collector { let libs = self.libs.borrow(); let libs = (*libs.get()).clone(); let cratename = self.cratename.to_owned(); + let loose_feature_gating = self.loose_feature_gating; debug!("Creating test {}: {}", name, test); self.tests.push(testing::TestDescAndFn { desc: testing::TestDesc { @@ -228,7 +241,7 @@ impl Collector { should_fail: false, // compiler failures are test failures }, testfn: testing::DynTestFn(proc() { - runtest(test, cratename, libs, should_fail, no_run); + runtest(test, cratename, libs, should_fail, no_run, loose_feature_gating); }), }); } From 2d7d7e59f9b37b431a4d6f3ab576c4bd197dc231 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 8 Mar 2014 22:05:20 +1100 Subject: [PATCH 05/10] docs: adjust code blocks to pass with rustdoc. The changes are basically just because rustdoc runs tests/rendering on more snippets by default (i.e. everything without a `notrust` tag), and not anything significant. --- src/doc/complement-lang-faq.md | 2 +- src/doc/guide-ffi.md | 8 ++- src/doc/guide-lifetimes.md | 8 +-- src/doc/guide-testing.md | 10 ++++ src/doc/index.md | 2 + src/doc/rust.md | 93 +++++++++++++++++----------------- src/doc/tutorial.md | 42 +++++++-------- 7 files changed, 92 insertions(+), 73 deletions(-) diff --git a/src/doc/complement-lang-faq.md b/src/doc/complement-lang-faq.md index bd0a47de44c5d..5b38ef5645e2b 100644 --- a/src/doc/complement-lang-faq.md +++ b/src/doc/complement-lang-faq.md @@ -135,7 +135,7 @@ For simplicity, we do not plan to do so. Implementing automatic semicolon insert **Short answer** set the RUST_LOG environment variable to the name of your source file, sans extension. -```sh +``` {.sh .notrust} rustc hello.rs export RUST_LOG=hello ./hello diff --git a/src/doc/guide-ffi.md b/src/doc/guide-ffi.md index c279ff314b474..b9cab358e909d 100644 --- a/src/doc/guide-ffi.md +++ b/src/doc/guide-ffi.md @@ -263,6 +263,7 @@ to the C library and afterwards be invoked from there. A basic example is: Rust code: + ~~~~ {.ignore} extern fn callback(a:i32) { println!("I'm called from C with value {0}", a); @@ -283,7 +284,8 @@ fn main() { ~~~~ C code: -~~~~ {.ignore} + +~~~~ {.notrust} typedef void (*rust_callback)(int32_t); rust_callback cb; @@ -314,6 +316,7 @@ the notification. This will allow the callback to unsafely access the referenced Rust object. Rust code: + ~~~~ {.ignore} struct RustObject { @@ -346,7 +349,8 @@ fn main() { ~~~~ C code: -~~~~ {.ignore} + +~~~~ {.notrust} typedef void (*rust_callback)(int32_t); void* cb_target; rust_callback cb; diff --git a/src/doc/guide-lifetimes.md b/src/doc/guide-lifetimes.md index d31b0eed8492a..69596b6e30475 100644 --- a/src/doc/guide-lifetimes.md +++ b/src/doc/guide-lifetimes.md @@ -205,7 +205,7 @@ struct X { f: int } fn example1() { let mut x = X { f: 3 }; let y = &mut x.f; // -+ L - ... // | + // ... // | } // -+ ~~~ @@ -221,7 +221,7 @@ The situation gets more complex when borrowing data inside heap boxes: fn example2() { let mut x = @X { f: 3 }; let y = &x.f; // -+ L - ... // | + // ... // | } // -+ ~~~ @@ -251,7 +251,7 @@ fn example2() { let mut x = @X {f: 3}; let x1 = x; let y = &x1.f; // -+ L - ... // | + // ... // | } // -+ ~~~ @@ -282,7 +282,7 @@ fn example3() -> int { return *y; // | } // -+ x = ~Foo {f: 4}; - ... + // ... # return 0; } ~~~ diff --git a/src/doc/guide-testing.md b/src/doc/guide-testing.md index f129f7db72994..1a01fad5da214 100644 --- a/src/doc/guide-testing.md +++ b/src/doc/guide-testing.md @@ -185,6 +185,7 @@ amount. For example: ~~~ +# #[allow(unused_imports)]; extern crate test; use std::vec; @@ -201,6 +202,8 @@ fn initialise_a_vector(b: &mut BenchHarness) { b.iter(|| {vec::from_elem(1024, 0u64);} ); b.bytes = 1024 * 8; } + +# fn main() {} ~~~ The benchmark runner will calibrate measurement of the benchmark @@ -244,6 +247,7 @@ recognize that some calculation has no external effects and remove it entirely. ~~~ +# #[allow(unused_imports)]; extern crate test; use test::BenchHarness; @@ -253,6 +257,8 @@ fn bench_xor_1000_ints(bh: &mut BenchHarness) { range(0, 1000).fold(0, |old, new| old ^ new); }); } + +# fn main() {} ~~~ gives the following results @@ -271,6 +277,7 @@ cannot remove the computation entirely. This could be done for the example above by adjusting the `bh.iter` call to ~~~ +# struct X; impl X { fn iter(&self, _: || -> T) {} } let bh = X; bh.iter(|| range(0, 1000).fold(0, |old, new| old ^ new)) ~~~ @@ -281,9 +288,12 @@ forces it to consider any argument as used. ~~~ extern crate test; +# fn main() { +# struct X; impl X { fn iter(&self, _: || -> T) {} } let bh = X; bh.iter(|| { test::black_box(range(0, 1000).fold(0, |old, new| old ^ new)); }); +# } ~~~ Neither of these read or modify the value, and are very cheap for diff --git a/src/doc/index.md b/src/doc/index.md index cf2d8155f7e51..8dacf0e4de886 100644 --- a/src/doc/index.md +++ b/src/doc/index.md @@ -25,6 +25,8 @@ li {list-style-type: none; } * [The standard library, `std`](std/index.html) + + * [The `arena` allocation library](arena/index.html) * [The `collections` library](collections/index.html) * [The `extra` library of extra stuff](extra/index.html) diff --git a/src/doc/rust.md b/src/doc/rust.md index d37b33d2316a6..d1cef9a061477 100644 --- a/src/doc/rust.md +++ b/src/doc/rust.md @@ -60,7 +60,7 @@ dialect of EBNF supported by common automated LL(k) parsing tools such as `llgen`, rather than the dialect given in ISO 14977. The dialect can be defined self-referentially as follows: -~~~~ {.ebnf .notation} +~~~~ {.notrust .ebnf .notation} grammar : rule + ; rule : nonterminal ':' productionrule ';' ; productionrule : production [ '|' production ] * ; @@ -150,7 +150,7 @@ Some productions are defined by exclusion of particular Unicode characters: ## Comments -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} comment : block_comment | line_comment ; block_comment : "/*" block_comment_body * '*' + '/' ; block_comment_body : (block_comment | character) * ; @@ -171,7 +171,7 @@ Non-doc comments are interpreted as a form of whitespace. ## Whitespace -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} whitespace_char : '\x20' | '\x09' | '\x0a' | '\x0d' ; whitespace : [ whitespace_char | comment ] + ; ~~~~ @@ -188,7 +188,7 @@ with any other legal whitespace element, such as a single space character. ## Tokens -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} simple_token : keyword | unop | binop ; token : simple_token | ident | literal | symbol | whitespace token ; ~~~~ @@ -202,7 +202,7 @@ grammar as double-quoted strings. Other tokens have exact rules given. The keywords are the following strings: -~~~~ {.keyword} +~~~~ {.notrust .keyword} as break do @@ -230,13 +230,13 @@ evaluates to, rather than referring to it by name or some other evaluation rule. A literal is a form of constant expression, so is evaluated (primarily) at compile time. -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} literal : string_lit | char_lit | num_lit ; ~~~~ #### Character and string literals -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} char_lit : '\x27' char_body '\x27' ; string_lit : '"' string_body * '"' | 'r' raw_string ; @@ -318,7 +318,7 @@ r##"foo #"# bar"##; // foo #"# bar #### Number literals -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} num_lit : nonzero_dec [ dec_digit | '_' ] * num_suffix ? | '0' [ [ dec_digit | '_' ] * num_suffix ? | 'b' [ '1' | '0' | '_' ] + int_suffix ? @@ -416,7 +416,7 @@ The two values of the boolean type are written `true` and `false`. ### Symbols -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} symbol : "::" "->" | '#' | '[' | ']' | '(' | ')' | '{' | '}' | ',' | ';' ; @@ -431,7 +431,7 @@ operators](#binary-operator-expressions), or [keywords](#keywords). ## Paths -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} expr_path : ident [ "::" expr_path_tail ] + ; expr_path_tail : '<' type_expr [ ',' type_expr ] + '>' | expr_path ; @@ -495,7 +495,7 @@ All of the above extensions are expressions with values. ## Macros -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} expr_macro_rules : "macro_rules" '!' ident '(' macro_rule * ')' macro_rule : '(' matcher * ')' "=>" '(' transcriber * ')' ';' matcher : '(' matcher * ')' | '[' matcher * ']' @@ -635,7 +635,7 @@ each of which may have some number of [attributes](#attributes) attached to it. ## Items -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} item : mod_item | fn_item | type_item | struct_item | enum_item | static_item | trait_item | impl_item | extern_block ; ~~~~ @@ -683,7 +683,7 @@ That is, Rust has no notion of type abstraction: there are no first-class "foral ### Modules -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} mod_item : "mod" ident ( ';' | '{' mod '}' ); mod : [ view_item | item ] * ; ~~~~ @@ -703,15 +703,15 @@ An example of a module: mod math { type Complex = (f64, f64); fn sin(f: f64) -> f64 { - ... + /* ... */ # fail!(); } fn cos(f: f64) -> f64 { - ... + /* ... */ # fail!(); } fn tan(f: f64) -> f64 { - ... + /* ... */ # fail!(); } } @@ -751,7 +751,7 @@ mod task { #### View items -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} view_item : extern_crate_decl | use_decl ; ~~~~ @@ -764,7 +764,7 @@ There are several kinds of view item: ##### Extern crate declarations -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} extern_crate_decl : "extern" "crate" ident [ '(' link_attrs ')' ] ? [ '=' string_lit ] ? ; link_attrs : link_attr [ ',' link_attrs ] + ; link_attr : ident '=' literal ; @@ -796,7 +796,7 @@ extern crate foo = "some/where/rust-foo#foo:1.0"; // a full package ID for exter ##### Use declarations -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} use_decl : "pub" ? "use" ident [ '=' path | "::" path_glob ] ; @@ -1205,7 +1205,7 @@ whereas `Dog` is simply called an enum variant. ### Static items -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} static_item : "static" ident ':' type '=' expr ';' ; ~~~~ @@ -1447,7 +1447,7 @@ Implementation parameters are written after the `impl` keyword. # trait Seq { } impl Seq for ~[T] { - ... + /* ... */ } impl Seq for u32 { /* Treat the integer as a sequence of bits */ @@ -1456,7 +1456,7 @@ impl Seq for u32 { ### External blocks -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} extern_block_item : "extern" '{' extern_block '} ; extern_block : [ foreign_fn ] * ; ~~~~ @@ -1682,7 +1682,7 @@ import public items from their destination, not private items. ## Attributes -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} attribute : '#' '[' attr_list ']' ; attr_list : attr [ ',' attr_list ]* attr : ident [ '=' literal @@ -2226,7 +2226,7 @@ declaring a function-local item. #### Slot declarations -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} let_decl : "let" pat [':' type ] ? [ init ] ? ';' ; init : [ '=' ] expr ; ~~~~ @@ -2326,7 +2326,7 @@ values. ### Structure expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} struct_expr : expr_path '{' ident ':' expr [ ',' ident ':' expr ] * [ ".." expr ] '}' | @@ -2380,7 +2380,7 @@ Point3d {y: 0, z: 10, .. base}; ### Block expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} block_expr : '{' [ view_item ] * [ stmt ';' | item ] * [ expr ] '}' @@ -2398,7 +2398,7 @@ of the block are that of the expression itself. ### Method-call expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} method_call_expr : expr '.' ident paren_expr_list ; ~~~~ @@ -2409,7 +2409,7 @@ or dynamically dispatching if the left-hand-side expression is an indirect [obje ### Field expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} field_expr : expr '.' ident ~~~~ @@ -2417,9 +2417,10 @@ A _field expression_ consists of an expression followed by a single dot and an i when not immediately followed by a parenthesized expression-list (the latter is a [method call expression](#method-call-expressions)). A field expression denotes a field of a [structure](#structure-types). -~~~~ {.field} +~~~~ {.ignore .field} myrecord.myfield; -{a: 10, b: 20}.a; +foo().x; +(Struct {a: 10, b: 20}).a; ~~~~ A field access on a record is an [lvalue](#lvalues-rvalues-and-temporaries) referring to the value of that field. @@ -2430,7 +2431,7 @@ it is automatically dereferenced to make the field access possible. ### Vector expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} vec_expr : '[' "mut" ? vec_elems? ']' vec_elems : [expr [',' expr]*] | [expr ',' ".." expr] @@ -2452,7 +2453,7 @@ as a [literal](#literals) or a [static item](#static-items). ### Index expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} idx_expr : expr '[' expr ']' ~~~~ @@ -2504,7 +2505,7 @@ before the expression they apply to. ### Binary operator expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} binop_expr : expr binop expr ; ~~~~ @@ -2645,7 +2646,7 @@ Any such expression always has the [`unit`](#primitive-types) type. The precedence of Rust binary operators is ordered as follows, going from strong to weak: -~~~~ {.precedence} +~~~~ {.notrust .precedence} * / % as + - @@ -2669,7 +2670,7 @@ An expression enclosed in parentheses evaluates to the result of the enclosed expression. Parentheses can be used to explicitly specify evaluation order within an expression. -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} paren_expr : '(' expr ')' ; ~~~~ @@ -2682,7 +2683,7 @@ let x = (2 + 3) * 4; ### Call expressions -~~~~ {.abnf .gram} +~~~~ {.notrust .ebnf .gram} expr_list : [ expr [ ',' expr ]* ] ? ; paren_expr_list : '(' expr_list ')' ; call_expr : expr paren_expr_list ; @@ -2705,7 +2706,7 @@ let pi: Option = FromStr::from_str("3.14"); ### Lambda expressions -~~~~ {.abnf .gram} +~~~~ {.notrust .ebnf .gram} ident_list : [ ident [ ',' ident ]* ] ? ; lambda_expr : '|' ident_list '|' expr ; ~~~~ @@ -2748,7 +2749,7 @@ ten_times(|j| println!("hello, {}", j)); ### While loops -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} while_expr : "while" expr '{' block '}' ; ~~~~ @@ -2774,7 +2775,7 @@ The keyword `loop` in Rust appears both in _loop expressions_ and in _continue e A loop expression denotes an infinite loop; see [Continue expressions](#continue-expressions) for continue expressions. -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} loop_expr : [ lifetime ':' ] "loop" '{' block '}'; ~~~~ @@ -2785,7 +2786,7 @@ See [Break expressions](#break-expressions). ### Break expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} break_expr : "break" [ lifetime ]; ~~~~ @@ -2798,7 +2799,7 @@ but must enclose it. ### Continue expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} continue_expr : "loop" [ lifetime ]; ~~~~ @@ -2817,7 +2818,7 @@ A `loop` expression is only permitted in the body of a loop. ### For expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} for_expr : "for" pat "in" expr '{' block '}' ; ~~~~ @@ -2851,7 +2852,7 @@ for i in range(0u, 256) { ### If expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} if_expr : "if" expr '{' block '}' else_tail ? ; @@ -2872,7 +2873,7 @@ then any `else` block is executed. ### Match expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} match_expr : "match" expr '{' match_arm [ '|' match_arm ] * '}' ; match_arm : match_pat '=>' [ expr "," | '{' block '}' ] ; @@ -3063,7 +3064,7 @@ let message = match maybe_digit { ### Return expressions -~~~~ {.ebnf .gram} +~~~~ {.notrust .ebnf .gram} return_expr : "return" expr ? ; ~~~~ @@ -3895,7 +3896,7 @@ fn main() { These four log levels correspond to levels 1-4, as controlled by `RUST_LOG`: -```bash +``` {.bash .notrust} $ RUST_LOG=rust=3 ./rust This is an error log This is a warn log diff --git a/src/doc/tutorial.md b/src/doc/tutorial.md index 5069774a2a992..476cc26fe6735 100644 --- a/src/doc/tutorial.md +++ b/src/doc/tutorial.md @@ -1058,10 +1058,12 @@ being destroyed along with the owner. Since the `list` variable above is immutable, the whole list is immutable. The memory allocation itself is the box, while the owner holds onto a pointer to it: - List box List box List box List box - +--------------+ +--------------+ +--------------+ +--------------+ - list -> | Cons | 1 | ~ | -> | Cons | 2 | ~ | -> | Cons | 3 | ~ | -> | Nil | - +--------------+ +--------------+ +--------------+ +--------------+ +~~~ {.notrust} + List box List box List box List box + +--------------+ +--------------+ +--------------+ +--------------+ +list -> | Cons | 1 | ~ | -> | Cons | 2 | ~ | -> | Cons | 3 | ~ | -> | Nil | + +--------------+ +--------------+ +--------------+ +--------------+ +~~~ > ***Note:*** the above diagram shows the logical contents of the enum. The actual > memory layout of the enum may vary. For example, for the `List` enum shown @@ -1173,7 +1175,7 @@ ownership of a list to be passed in rather than just mutating it in-place. The obvious signature for a `List` equality comparison is the following: ~~~{.ignore} -fn eq(xs: List, ys: List) -> bool { ... } +fn eq(xs: List, ys: List) -> bool { /* ... */ } ~~~ However, this will cause both lists to be moved into the function. Ownership @@ -1181,7 +1183,7 @@ isn't required to compare the lists, so the function should take *references* (&T) instead. ~~~{.ignore} -fn eq(xs: &List, ys: &List) -> bool { ... } +fn eq(xs: &List, ys: &List) -> bool { /* ... */ } ~~~ A reference is a *non-owning* view of a value. A reference can be obtained with the `&` (address-of) @@ -1881,9 +1883,9 @@ A caller must in turn have a compatible pointer type to call the method. # Rectangle(Point, Point) # } impl Shape { - fn draw_reference(&self) { ... } - fn draw_owned(~self) { ... } - fn draw_value(self) { ... } + fn draw_reference(&self) { /* ... */ } + fn draw_owned(~self) { /* ... */ } + fn draw_value(self) { /* ... */ } } let s = Circle(Point { x: 1.0, y: 2.0 }, 3.0); @@ -1906,9 +1908,9 @@ to a reference. # Rectangle(Point, Point) # } # impl Shape { -# fn draw_reference(&self) { ... } -# fn draw_owned(~self) { ... } -# fn draw_value(self) { ... } +# fn draw_reference(&self) { /* ... */ } +# fn draw_owned(~self) { /* ... */ } +# fn draw_value(self) { /* ... */ } # } # let s = Circle(Point { x: 1.0, y: 2.0 }, 3.0); // As with typical function arguments, managed and owned pointers @@ -1934,8 +1936,8 @@ These methods are the preferred way to define constructor functions. ~~~~ {.ignore} impl Circle { - fn area(&self) -> f64 { ... } - fn new(area: f64) -> Circle { ... } + fn area(&self) -> f64 { /* ... */ } + fn new(area: f64) -> Circle { /* ... */ } } ~~~~ @@ -2395,8 +2397,8 @@ to an object: # fn new_rectangle() -> Rectangle { true } # fn draw_all(shapes: &[~Drawable]) {} -impl Drawable for Circle { fn draw(&self) { ... } } -impl Drawable for Rectangle { fn draw(&self) { ... } } +impl Drawable for Circle { fn draw(&self) { /* ... */ } } +impl Drawable for Rectangle { fn draw(&self) { /* ... */ } } let c: ~Circle = ~new_circle(); let r: ~Rectangle = ~new_rectangle(); @@ -2510,7 +2512,7 @@ use std::f64::consts::PI; # impl Circle for CircleStruct { fn radius(&self) -> f64 { (self.area() / PI).sqrt() } } # impl Shape for CircleStruct { fn area(&self) -> f64 { PI * square(self.radius) } } -let concrete = @CircleStruct{center:Point{x:3f,y:4f},radius:5f}; +let concrete = @CircleStruct{center:Point{x:3.0,y:4.0},radius:5.0}; let mycircle: @Circle = concrete as @Circle; let nonsense = mycircle.radius() * mycircle.area(); ~~~ @@ -2667,8 +2669,8 @@ mod farm { } impl Farm { - fn feed_chickens(&self) { ... } - pub fn add_chicken(&self, c: Chicken) { ... } + fn feed_chickens(&self) { /* ... */ } + pub fn add_chicken(&self, c: Chicken) { /* ... */ } } pub fn feed_animals(farm: &Farm) { @@ -3141,7 +3143,7 @@ Now for something that you can actually compile yourself. We define two crates, and use one of them as a library in the other. -~~~~ +~~~~{.ignore} // `world.rs` #[crate_id = "world#0.42"]; # extern crate extra; From f7833215b0a5f8e12d559b309254a8f235078802 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 9 Mar 2014 01:41:31 +1100 Subject: [PATCH 06/10] mk: rewrite the documentation handling. This converts it to be very similar to crates.mk, with a single list of the documentation items creating all the necessary bits and pieces. Changes include: - rustdoc is used to render HTML & test standalone docs - documentation building now obeys NO_REBUILD=1 - testing standalone docs now obeys NO_REBUILD=1 - L10N is slightly less broken (in particular, it shares dependencies and code with the rest of the code) - PDFs can be built for all documentation items, not just tutorial and manual - removes the obsolete & unused extract-tests.py script - adjust the CSS for standalone docs to use the rustdoc syntax highlighting --- mk/clean.mk | 1 - mk/crates.mk | 1 + mk/docs.mk | 389 +++++++++++++++++---------------------- mk/tests.mk | 101 +++++----- src/doc/rust.css | 31 ++-- src/etc/extract-tests.py | 217 ---------------------- 6 files changed, 235 insertions(+), 505 deletions(-) delete mode 100644 src/etc/extract-tests.py diff --git a/mk/clean.mk b/mk/clean.mk index bc5961a998131..73813a4b7512b 100644 --- a/mk/clean.mk +++ b/mk/clean.mk @@ -41,7 +41,6 @@ clean-misc: @$(call E, cleaning) $(Q)rm -f $(RUNTIME_OBJS) $(RUNTIME_DEF) $(Q)rm -f $(RUSTLLVM_LIB_OBJS) $(RUSTLLVM_OBJS_OBJS) $(RUSTLLVM_DEF) - $(Q)rm -Rf $(DOCS) $(Q)rm -Rf $(GENERATED) $(Q)rm -Rf tmp/* $(Q)rm -Rf rust-stage0-*.tar.bz2 $(PKG_NAME)-*.tar.gz $(PKG_NAME)-*.exe dist diff --git a/mk/crates.mk b/mk/crates.mk index 45b6ed1a058d0..70560d41b8d4c 100644 --- a/mk/crates.mk +++ b/mk/crates.mk @@ -92,6 +92,7 @@ TOOL_SOURCE_rustc := $(S)src/driver/driver.rs ################################################################################ DOC_CRATES := $(filter-out rustc, $(filter-out syntax, $(CRATES))) +COMPILER_DOC_CRATES := rustc syntax # This macro creates some simple definitions for each crate being built, just # some munging of all of the parameters above. diff --git a/mk/docs.mk b/mk/docs.mk index 7861888482ebe..f3fd94678ff13 100644 --- a/mk/docs.mk +++ b/mk/docs.mk @@ -9,24 +9,93 @@ # except according to those terms. ###################################################################### -# Doc variables and rules +# The various pieces of standalone documentation: guides, tutorial, +# manual etc. +# +# The DOCS variable is their names (with no file extension). +# +# RUSTDOC_FLAGS_xyz variables are extra arguments to pass to the +# rustdoc invocation for xyz. +# +# RUSTDOC_DEPS_xyz are extra dependencies for the rustdoc invocation +# on xyz. +# +# L10N_LANGS are the languages for which the docs have been +# translated. ###################################################################### +DOCS := index tutorial guide-ffi guide-macros guide-lifetimes \ + guide-tasks guide-container guide-pointers \ + complement-cheatsheet guide-runtime \ + rust + +RUSTDOC_DEPS_rust := doc/full-toc.inc +RUSTDOC_FLAGS_rust := --markdown-in-header=doc/full-toc.inc + +L10N_LANGS := ja + +# Generally no need to edit below here. -DOCS := -CDOCS := -DOCS_L10N := -HTML_DEPS := doc/ +# The options are passed to the documentation generators. +RUSTDOC_HTML_OPTS = --markdown-css rust.css \ + --markdown-before-content=doc/version_info.html \ + --markdown-in-header=doc/favicon.inc --markdown-after-content=doc/footer.inc -BASE_DOC_OPTS := --standalone --toc --number-sections -HTML_OPTS = $(BASE_DOC_OPTS) --to=html5 --section-divs --css=rust.css \ - --include-before-body=doc/version_info.html \ - --include-in-header=doc/favicon.inc --include-after-body=doc/footer.inc -TEX_OPTS = $(BASE_DOC_OPTS) --include-before-body=doc/version.md \ - --from=markdown --include-before-body=doc/footer.tex --to=latex -EPUB_OPTS = $(BASE_DOC_OPTS) --to=epub +PANDOC_BASE_OPTS := --standalone --toc --number-sections +PANDOC_TEX_OPTS = $(PANDOC_BASE_OPTS) --include-before-body=doc/version.md \ + --from=markdown --include-before-body=doc/footer.tex --to=latex +PANDOC_EPUB_OPTS = $(PANDOC_BASE_OPTS) --to=epub + + + +# The rustdoc executable... +RUSTDOC_EXE = $(HBIN2_H_$(CFG_BUILD))/rustdoc$(X_$(CFG_BUILD)) +# ...with rpath included in case --disable-rpath was provided to +# ./configure +RUSTDOC = $(RPATH_VAR2_T_$(CFG_BUILD)_H_$(CFG_BUILD)) $(RUSTDOC_EXE) D := $(S)src/doc +DOC_TARGETS := +COMPILER_DOC_TARGETS := +DOC_L10N_TARGETS := + +# If NO_REBUILD is set then break the dependencies on rustdoc so we +# build the documentation without having to rebuild rustdoc. +ifeq ($(NO_REBUILD),) +HTML_DEPS := $(RUSTDOC_EXE) +else +HTML_DEPS := +endif + +# Check for the various external utilities for the EPUB/PDF docs: + +ifeq ($(CFG_PDFLATEX),) + $(info cfg: no pdflatex found, omitting doc/rust.pdf) + NO_PDF_DOCS = 1 +else + ifeq ($(CFG_XETEX),) + $(info cfg: no xetex found, disabling doc/rust.pdf) + NO_PDF_DOCS = 1 + else + ifeq ($(CFG_LUATEX),) + $(info cfg: lacking luatex, disabling pdflatex) + NO_PDF_DOCS = 1 + endif + endif +endif + + +ifeq ($(CFG_PANDOC),) +$(info cfg: no pandoc found, omitting PDF and EPUB docs) +ONLY_HTML_DOCS = 1 +endif + +ifeq ($(CFG_NODE),) +$(info cfg: no node found, omitting PDF and EPUB docs) +ONLY_HTML_DOCS = 1 +endif + + ###################################################################### # Rust version ###################################################################### @@ -46,7 +115,7 @@ doc/version_info.html: $(D)/version_info.html.template $(MKFILE_DEPS) \ GENERATED += doc/version.md doc/version_info.html ###################################################################### -# Docs, from pandoc, rustdoc (which runs pandoc), and node +# Docs, from rustdoc and sometimes pandoc & node ###################################################################### doc/: @@ -75,184 +144,78 @@ doc/footer.tex: $(D)/footer.tex | doc/ @$(call E, cp: $@) $(Q)cp -a $< $@ 2> /dev/null -ifeq ($(CFG_PANDOC),) - $(info cfg: no pandoc found, omitting docs) - NO_DOCS = 1 -endif +# The (english) documentation for each doc item. -ifeq ($(CFG_NODE),) - $(info cfg: no node found, omitting docs) - NO_DOCS = 1 -endif +define DEF_DOC + +# HTML (rustdoc) +DOC_TARGETS += doc/$(1).html +doc/$(1).html: $$(D)/$(1).md $$(HTML_DEPS) $$(RUSTDOC_DEPS_$(1)) | doc/ + @$$(call E, rustdoc: $$@) + $$(RUSTDOC) $$(RUSTDOC_HTML_OPTS) $$(RUSTDOC_FLAGS_$(1)) $$< + +ifneq ($(ONLY_HTML_DOCS),1) + +# EPUB (pandoc directly) +DOC_TARGETS += doc/$(1).epub +doc/$(1).epub: $$(D)/$(1).md | doc/ + @$$(call E, pandoc: $$@) + $$(Q)$$(CFG_NODE) $$(D)/prep.js --highlight $$< | \ + $$(CFG_PANDOC) $$(PANDOC_EPUB_OPTS) --output=$$@ + +# PDF (md =(pandoc)=> tex =(pdflatex)=> pdf) +DOC_TARGETS += doc/$(1).tex +doc/$(1).tex: $$(D)/$(1).md doc/footer.tex doc/version.md | doc/ + @$$(call E, pandoc: $$@) + $$(Q)$$(CFG_NODE) $$(D)/prep.js $$< | \ + $$(CFG_PANDOC) $$(PANDOC_TEX_OPTS) --output=$$@ + +ifneq ($(NO_PDF_DOCS),1) +DOC_TARGETS += doc/$(1).pdf +doc/$(1).pdf: doc/$(1).tex + @$$(call E, pdflatex: $$@) + $$(Q)$$(CFG_PDFLATEX) \ + -interaction=batchmode \ + -output-directory=doc \ + $$< +endif # NO_PDF_DOCS + +endif # ONLY_HTML_DOCS + +endef + +$(foreach docname,$(DOCS),$(eval $(call DEF_DOC,$(docname)))) -ifneq ($(NO_DOCS),1) - -DOCS += doc/rust.html -doc/rust.html: $(D)/rust.md doc/full-toc.inc $(HTML_DEPS) | doc/ - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --include-in-header=doc/full-toc.inc --output=$@ - -DOCS += doc/rust.tex -doc/rust.tex: $(D)/rust.md doc/footer.tex doc/version.md | doc/ - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js $< | \ - $(CFG_PANDOC) $(TEX_OPTS) --output=$@ - -DOCS += doc/rust.epub -doc/rust.epub: $(D)/rust.md | doc/ - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(EPUB_OPTS) --output=$@ - -DOCS += doc/rustdoc.html -doc/rustdoc.html: $(D)/rustdoc.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/tutorial.html -doc/tutorial.html: $(D)/tutorial.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/tutorial.tex -doc/tutorial.tex: $(D)/tutorial.md doc/footer.tex doc/version.md - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js $< | \ - $(CFG_PANDOC) $(TEX_OPTS) --output=$@ - -DOCS += doc/tutorial.epub -doc/tutorial.epub: $(D)/tutorial.md - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(EPUB_OPTS) --output=$@ - - -DOCS_L10N += doc/l10n/ja/tutorial.html -doc/l10n/ja/tutorial.html: doc/l10n/ja/tutorial.md doc/version_info.html doc/rust.css - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight doc/l10n/ja/tutorial.md | \ - $(CFG_PANDOC) --standalone --toc \ - --section-divs --number-sections \ - --from=markdown --to=html5 --css=../../rust.css \ - --include-before-body=doc/version_info.html \ - --output=$@ - -# Complementary documentation + +# Localized documentation + +# FIXME: I (huonw) haven't actually been able to test properly, since +# e.g. (by default) I'm doing an out-of-tree build (#12763), but even +# adjusting for that, the files are too old(?) and are rejected by +# po4a. # -DOCS += doc/index.html -doc/index.html: $(D)/index.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/complement-lang-faq.html -doc/complement-lang-faq.html: $(D)/complement-lang-faq.md doc/full-toc.inc $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --include-in-header=doc/full-toc.inc --output=$@ - -DOCS += doc/complement-project-faq.html -doc/complement-project-faq.html: $(D)/complement-project-faq.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/complement-cheatsheet.html -doc/complement-cheatsheet.html: $(D)/complement-cheatsheet.md doc/full-toc.inc $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --include-in-header=doc/full-toc.inc --output=$@ - -DOCS += doc/complement-bugreport.html -doc/complement-bugreport.html: $(D)/complement-bugreport.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -# Guides - -DOCS += doc/guide-macros.html -doc/guide-macros.html: $(D)/guide-macros.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-container.html -doc/guide-container.html: $(D)/guide-container.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-ffi.html -doc/guide-ffi.html: $(D)/guide-ffi.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-testing.html -doc/guide-testing.html: $(D)/guide-testing.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-lifetimes.html -doc/guide-lifetimes.html: $(D)/guide-lifetimes.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-tasks.html -doc/guide-tasks.html: $(D)/guide-tasks.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-pointers.html -doc/guide-pointers.html: $(D)/guide-pointers.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - -DOCS += doc/guide-runtime.html -doc/guide-runtime.html: $(D)/guide-runtime.md $(HTML_DEPS) - @$(call E, pandoc: $@) - $(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \ - $(CFG_PANDOC) $(HTML_OPTS) --output=$@ - - ifeq ($(CFG_PDFLATEX),) - $(info cfg: no pdflatex found, omitting doc/rust.pdf) - else - ifeq ($(CFG_XETEX),) - $(info cfg: no xetex found, disabling doc/rust.pdf) - else - ifeq ($(CFG_LUATEX),) - $(info cfg: lacking luatex, disabling pdflatex) - else - -DOCS += doc/rust.pdf -doc/rust.pdf: doc/rust.tex - @$(call E, pdflatex: $@) - $(Q)$(CFG_PDFLATEX) \ - -interaction=batchmode \ - -output-directory=doc \ - $< - -DOCS += doc/tutorial.pdf -doc/tutorial.pdf: doc/tutorial.tex - @$(call E, pdflatex: $@) - $(Q)$(CFG_PDFLATEX) \ - -interaction=batchmode \ - -output-directory=doc \ - $< - - endif - endif - endif +# As such, I've attempted to get it working as much as possible (and +# switching from pandoc to rustdoc), but preserving the old behaviour +# (e.g. only running on the tutorial) +.PHONY: l10n-mds +l10n-mds: $(D)/po4a.conf \ + $(foreach lang,$(L10N_LANG),$(D)/po/$(lang)/*.md.po) + $(warning WARNING: localized documentation is experimental) + po4a --copyright-holder="The Rust Project Developers" \ + --package-name="Rust" \ + --package-version="$(CFG_RELEASE)" \ + -M UTF-8 -L UTF-8 \ + $(D)/po4a.conf + +define DEF_L10N_DOC +DOC_L10N_TARGETS += doc/l10n/$(1)/$(2).html +doc/l10n/$(1)/$(2).html: l10n-mds $$(HTML_DEPS) $$(RUSTDOC_DEPS_$(2)) + @$$(call E, rustdoc: $$@) + $$(RUSTDOC) $$(RUSTDOC_HTML_OPTS) $$(RUSTDOC_FLAGS_$(1)) doc/l10n/$(1)/$(2).md +endef + +$(foreach lang,$(L10N_LANGS),$(eval $(call DEF_L10N_DOC,$(lang),tutorial))) -endif # No pandoc / node ###################################################################### # LLnextgen (grammar analysis from refman) @@ -278,50 +241,44 @@ endif # Rustdoc (libstd/extra) ###################################################################### -# The rustdoc executable, rpath included in case --disable-rpath was provided to -# ./configure -RUSTDOC = $(HBIN2_H_$(CFG_BUILD))/rustdoc$(X_$(CFG_BUILD)) # The library documenting macro # # $(1) - The crate name (std/extra) # # Passes --cfg stage2 to rustdoc because it uses the stage2 librustc. -define libdoc -doc/$(1)/index.html: \ - $$(CRATEFILE_$(1)) \ - $$(RSINPUTS_$(1)) \ - $$(RUSTDOC) \ - $$(foreach dep,$$(RUST_DEPS_$(1)), \ +define DEF_LIB_DOC + +# If NO_REBUILD is set then break the dependencies on rustdoc so we +# build crate documentation without having to rebuild rustdoc. +ifeq ($(NO_REBUILD),) +LIB_DOC_DEP_$(1) = \ + $$(CRATEFILE_$(1)) \ + $$(RSINPUTS_$(1)) \ + $$(RUSTDOC_EXE) \ + $$(foreach dep,$$(RUST_DEPS_$(1)), \ $$(TLIB2_T_$(CFG_BUILD)_H_$(CFG_BUILD))/stamp.$$(dep)) - @$$(call E, rustdoc: $$@) - $$(Q)$$(RPATH_VAR2_T_$(CFG_BUILD)_H_$(CFG_BUILD)) $$(RUSTDOC) \ - --cfg stage2 $$< +else +LIB_DOC_DEP_$(1) = $$(CRATEFILE_$(1)) $$(RSINPUTS_$(1)) +endif +$(2) += doc/$(1)/index.html +doc/$(1)/index.html: $$(LIB_DOC_DEP_$(1)) + @$$(call E, rustdoc $$@) + $$(Q)$$(RUSTDOC) --cfg stage2 $$< endef -$(foreach crate,$(CRATES),$(eval $(call libdoc,$(crate)))) - -DOCS += $(DOC_CRATES:%=doc/%/index.html) - -CDOCS += doc/rustc/index.html -CDOCS += doc/syntax/index.html +$(foreach crate,$(DOC_CRATES),$(eval $(call DEF_LIB_DOC,$(crate),DOC_TARGETS))) +$(foreach crate,$(COMPILER_DOC_CRATES),$(eval $(call DEF_LIB_DOC,$(crate),COMPILER_DOC_TARGETS))) ifdef CFG_DISABLE_DOCS $(info cfg: disabling doc build (CFG_DISABLE_DOCS)) - DOCS := + DOC_TARGETS := endif -docs: $(DOCS) -compiler-docs: $(CDOCS) - -docs-l10n: $(DOCS_L10N) +docs: $(DOC_TARGETS) +compiler-docs: $(COMPILER_DOC_TARGETS) -doc/l10n/%.md: doc/po/%.md.po doc/po4a.conf - po4a --copyright-holder="The Rust Project Developers" \ - --package-name="Rust" \ - --package-version="$(CFG_RELEASE)" \ - -M UTF-8 -L UTF-8 \ - doc/po4a.conf +docs-l10n: $(DOC_L10N_TARGETS) .PHONY: docs-l10n diff --git a/mk/tests.mk b/mk/tests.mk index c55162d398c2d..8f48918d31aeb 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -19,12 +19,6 @@ TEST_DOC_CRATES = $(DOC_CRATES) TEST_HOST_CRATES = $(HOST_CRATES) TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES) -# Markdown files under doc/ that should have their code extracted and run -DOC_TEST_NAMES = tutorial guide-ffi guide-macros guide-lifetimes \ - guide-tasks guide-container guide-pointers \ - complement-cheatsheet guide-runtime \ - rust - ###################################################################### # Environment configuration ###################################################################### @@ -318,7 +312,7 @@ check-stage$(1)-T-$(2)-H-$(3)-doc-crates-exec: \ check-stage$(1)-T-$(2)-H-$(3)-doc-$$(crate)-exec) check-stage$(1)-T-$(2)-H-$(3)-doc-exec: \ - $$(foreach docname,$$(DOC_TEST_NAMES), \ + $$(foreach docname,$$(DOCS), \ check-stage$(1)-T-$(2)-H-$(3)-doc-$$(docname)-exec) check-stage$(1)-T-$(2)-H-$(3)-pretty-exec: \ @@ -662,32 +656,56 @@ $(foreach host,$(CFG_HOST), \ $(foreach pretty-name,$(PRETTY_NAMES), \ $(eval $(call DEF_RUN_PRETTY_TEST,$(stage),$(target),$(host),$(pretty-name))))))) -define DEF_RUN_DOC_TEST -DOC_TEST_ARGS$(1)-T-$(2)-H-$(3)-doc-$(4) := \ - $$(CTEST_COMMON_ARGS$(1)-T-$(2)-H-$(3)) \ - --src-base $(3)/test/doc-$(4)/ \ - --build-base $(3)/test/doc-$(4)/ \ - --mode run-pass +###################################################################### +# Crate & freestanding documentation tests +###################################################################### + +define DEF_RUSTDOC +RUSTDOC_EXE_$(1)_T_$(2)_H_$(3) := $$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3)) +RUSTDOC_$(1)_T_$(2)_H_$(3) := $$(RPATH_VAR$(1)_T_$(2)_H_$(3)) $$(RUSTDOC_EXE_$(1)_T_$(2)_H_$(3)) +endef + +$(foreach host,$(CFG_HOST), \ + $(foreach target,$(CFG_TARGET), \ + $(foreach stage,$(STAGES), \ + $(eval $(call DEF_RUSTDOC,$(stage),$(target),$(host)))))) + +# Freestanding + +define DEF_DOC_TEST check-stage$(1)-T-$(2)-H-$(3)-doc-$(4)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)) -$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): \ - $$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \ - doc-$(4)-extract$(3) - @$$(call E, run doc-$(4) [$(2)]: $$<) - $$(Q)$$(call CFG_RUN_CTEST_$(2),$(1),$$<,$(3)) \ - $$(DOC_TEST_ARGS$(1)-T-$(2)-H-$(3)-doc-$(4)) \ - --logfile $$(call TEST_LOG_FILE,$(1),$(2),$(3),doc-$(4)) \ - && touch $$@ +# If NO_REBUILD is set then break the dependencies on everything but +# the source files so we can test documentation without rebuilding +# rustdoc etc. +ifeq ($(NO_REBUILD),) +DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = \ + $$(D)/$(4).md \ + $$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \ + $$(RUSTDOC_EXE_$(1)_T_$(2)_H_$(3)) +else +DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(D)/$(4).md +endif +ifeq ($(2),$$(CFG_BUILD)) +$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): $$(DOCTESTDEP_$(1)_$(2)_$(3)_$(4)) + @$$(call E, run doc-$(4) [$(2)]) + $$(Q)$$(RUSTDOC_$(1)_T_$(2)_H_$(3)) --test $$< --test-args "$$(TESTARGS)" && touch $$@ +else +$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): + touch $$@ +endif endef $(foreach host,$(CFG_HOST), \ $(foreach target,$(CFG_TARGET), \ $(foreach stage,$(STAGES), \ - $(foreach docname,$(DOC_TEST_NAMES), \ - $(eval $(call DEF_RUN_DOC_TEST,$(stage),$(target),$(host),$(docname))))))) + $(foreach docname,$(DOCS), \ + $(eval $(call DEF_DOC_TEST,$(stage),$(target),$(host),$(docname))))))) + +# Crates define DEF_CRATE_DOC_TEST @@ -695,21 +713,20 @@ define DEF_CRATE_DOC_TEST # the source files so we can test crate documentation without # rebuilding any of the parent crates. ifeq ($(NO_REBUILD),) -DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = \ +CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4) = \ $$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \ $$(CRATE_FULLDEPS_$(1)_T_$(2)_H_$(3)_$(4)) \ - $$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3)) + $$(RUSTDOC_EXE_$(1)_T_$(2)_H_$(3)) else -DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(RSINPUTS_$(4)) +CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(RSINPUTS_$(4)) endif check-stage$(1)-T-$(2)-H-$(3)-doc-$(4)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)) ifeq ($(2),$$(CFG_BUILD)) -$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): $$(DOCTESTDEP_$(1)_$(2)_$(3)_$(4)) +$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): $$(CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4)) @$$(call E, run doc-$(4) [$(2)]) - $$(Q)$$(RPATH_VAR$(1)_T_$(2)_H_$(3)) \ - $$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3)) --test \ + $$(Q)$$(RUSTDOC_$(1)_T_$(2)_H_$(3)) --test \ $$(CRATEFILE_$(4)) --test-args "$$(TESTARGS)" && touch $$@ else $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): @@ -724,26 +741,6 @@ $(foreach host,$(CFG_HOST), \ $(foreach crate,$(TEST_DOC_CRATES), \ $(eval $(call DEF_CRATE_DOC_TEST,$(stage),$(target),$(host),$(crate))))))) -###################################################################### -# Extracting tests for docs -###################################################################### - -EXTRACT_TESTS := "$(CFG_PYTHON)" $(S)src/etc/extract-tests.py - -define DEF_DOC_TEST_HOST - -doc-$(2)-extract$(1): - @$$(call E, extract: $(2) tests) - $$(Q)rm -f $(1)/test/doc-$(2)/*.rs - $$(Q)$$(EXTRACT_TESTS) $$(D)/$(2).md $(1)/test/doc-$(2) - -endef - -$(foreach host,$(CFG_HOST), \ - $(foreach docname,$(DOC_TEST_NAMES), \ - $(eval $(call DEF_DOC_TEST_HOST,$(host),$(docname))))) - - ###################################################################### # Shortcut rules ###################################################################### @@ -762,7 +759,7 @@ TEST_GROUPS = \ debuginfo \ codegen \ doc \ - $(foreach docname,$(DOC_TEST_NAMES),doc-$(docname)) \ + $(foreach docname,$(DOCS),doc-$(docname)) \ pretty \ pretty-rpass \ pretty-rpass-full \ @@ -830,9 +827,9 @@ $(foreach stage,$(STAGES), \ $(eval $(call DEF_CHECK_FOR_STAGE_AND_HOSTS_AND_GROUP,$(stage),$(host),$(group)))))) define DEF_CHECK_DOC_FOR_STAGE -check-stage$(1)-docs: $$(foreach docname,$$(DOC_TEST_NAMES),\ +check-stage$(1)-docs: $$(foreach docname,$$(DOCS),\ check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-$$(docname)) \ - $$(foreach crate,$$(DOC_CRATE_NAMES),\ + $$(foreach crate,$$(TEST_DOC_CRATES),\ check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-$$(crate)) endef diff --git a/src/doc/rust.css b/src/doc/rust.css index 17100274688f7..26681adad6d78 100644 --- a/src/doc/rust.css +++ b/src/doc/rust.css @@ -142,25 +142,18 @@ pre code { } /* Code highlighting */ -.cm-s-default span.cm-keyword {color: #8959A8;} -.cm-s-default span.cm-atom {color: #219;} -.cm-s-default span.cm-number {color: #3E999F;} -.cm-s-default span.cm-def {color: #4271AE;} -/*.cm-s-default span.cm-variable {color: #C82829;}*/ -.cm-s-default span.cm-variable-2 {color: #6F906C;} -.cm-s-default span.cm-variable-3 {color: #B76514;} -.cm-s-default span.cm-property {color: black;} -.cm-s-default span.cm-operator {color: black;} -.cm-s-default span.cm-comment {color: #8E908C;} -.cm-s-default span.cm-string {color: #718C00;} -.cm-s-default span.cm-string-2 {color: #866544;} -.cm-s-default span.cm-meta {color: #555;} -/*.cm-s-default span.cm-error {color: #F00;}*/ -.cm-s-default span.cm-qualifier {color: #555;} -.cm-s-default span.cm-builtin {color: #30A;} -.cm-s-default span.cm-bracket {color: #CC7;} -.cm-s-default span.cm-tag {color: #C82829;} -.cm-s-default span.cm-attribute {color: #00C;} +pre.rust .kw { color: #8959A8; } +pre.rust .kw-2, pre.rust .prelude-ty { color: #4271AE; } +pre.rust .number { color: #718C00; } +pre.rust .self { color: #C13928; } +pre.rust .boolval { color: #C13928; } +pre.rust .prelude-val { color: #C13928; } +pre.rust .comment { color: #8E908C; } +pre.rust .doccomment { color: #4D4D4C; } +pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999f; } +pre.rust .string { color: #718C00; } +pre.rust .lifetime { color: #C13928; } +pre.rust .attribute, pre.rust .attribute .ident { color: #C82829; } /* The rest ========================================================================== */ diff --git a/src/etc/extract-tests.py b/src/etc/extract-tests.py deleted file mode 100644 index 2900023ea2b43..0000000000000 --- a/src/etc/extract-tests.py +++ /dev/null @@ -1,217 +0,0 @@ -# Copyright 2012-2014 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. - -""" -Script for extracting compilable fragments from markdown documentation. See -prep.js for a description of the format recognized by this tool. Expects -a directory fragments/ to exist under the current directory, and writes the -fragments in there as individual .rs files. -""" -from __future__ import print_function -from codecs import open -from collections import deque -from itertools import imap -import os -import re -import sys - -# regexes -CHAPTER_NAME_REGEX = re.compile(r'# (.*)') -CODE_BLOCK_DELIM_REGEX = re.compile(r'~~~') -COMMENT_REGEX = re.compile(r'^# ') -COMPILER_DIRECTIVE_REGEX = re.compile(r'\#\[(.*)\];') -ELLIPSES_REGEX = re.compile(r'\.\.\.') -EXTERN_CRATE_REGEX = re.compile(r'\bextern crate extra\b') -MAIN_FUNCTION_REGEX = re.compile(r'\bfn main\b') -TAGS_REGEX = re.compile(r'\.([\w-]*)') - -# tags to ignore -IGNORE_TAGS = \ - frozenset(["abnf", "ebnf", "field", "keyword", "notrust", "precedence"]) - -# header for code snippet files -OUTPUT_BLOCK_HEADER = '\n'.join(( - "#[ deny(warnings) ];", - "#[ allow(unused_variable) ];", - "#[ allow(dead_assignment) ];", - "#[ allow(unused_mut) ];", - "#[ allow(attribute_usage) ];", - "#[ allow(dead_code) ];", - "#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n",)) - - -def add_extern_mod(block): - if not has_extern_mod(block): - # add `extern crate extra;` after compiler directives - directives = [] - while len(block) and is_compiler_directive(block[0]): - directives.append(block.popleft()) - - block.appendleft("\nextern crate extra;\n\n") - block.extendleft(reversed(directives)) - - return block - - -def add_main_function(block): - if not has_main_function(block): - prepend_spaces = lambda x: ' ' + x - block = deque(imap(prepend_spaces, block)) - block.appendleft("\nfn main() {\n") - block.append("\n}\n") - return block - - -def extract_code_fragments(dest_dir, lines): - """ - Extracts all the code fragments from a file that do not have ignored tags - writing them to the following file: - - [dest dir]/[chapter name]_[chapter_index].rs - """ - chapter_name = None - chapter_index = 0 - - for line in lines: - if is_chapter_title(line): - chapter_name = get_chapter_name(line) - chapter_index = 1 - continue - - if not is_code_block_delim(line): - continue - - assert chapter_name, "Chapter name missing for code block." - tags = get_tags(line) - block = get_code_block(lines) - - if tags & IGNORE_TAGS: - continue - - block = add_extern_mod(add_main_function(block)) - block.appendleft(OUTPUT_BLOCK_HEADER) - - if "ignore" in tags: - block.appendleft("//ignore-test\n") - elif "should_fail" in tags: - block.appendleft("//should-fail\n") - - output_filename = os.path.join( - dest_dir, - chapter_name + '_' + str(chapter_index) + '.rs') - - write_file(output_filename, block) - chapter_index += 1 - - -def has_extern_mod(block): - """Checks if a code block has the line `extern crate extra`.""" - find_extern_mod = lambda x: re.search(EXTERN_CRATE_REGEX, x) - return any(imap(find_extern_mod, block)) - - -def has_main_function(block): - """Checks if a code block has a main function.""" - find_main_fn = lambda x: re.search(MAIN_FUNCTION_REGEX, x) - return any(imap(find_main_fn, block)) - - -def is_chapter_title(line): - return re.match(CHAPTER_NAME_REGEX, line) - - -def is_code_block_delim(line): - return re.match(CODE_BLOCK_DELIM_REGEX, line) - - -def is_compiler_directive(line): - return re.match(COMPILER_DIRECTIVE_REGEX, line) - - -def get_chapter_name(line): - """Get the chapter name from a `# Containers` line.""" - return re.sub( - r'\W', - '_', - re.match(CHAPTER_NAME_REGEX, line).group(1)).lower() - - -def get_code_block(lines): - """ - Get a code block surrounded by ~~~, for example: - - 1: ~~~ { .tag } - 2: let u: ~[u32] = ~[0, 1, 2]; - 3: let v: &[u32] = &[0, 1, 2, 3]; - 4: let w: [u32, .. 5] = [0, 1, 2, 3, 4]; - 5: - 6: println!("u: {}, v: {}, w: {}", u.len(), v.len(), w.len()); - 7: ~~~ - - Returns lines 2-6. Assumes line 1 has been consumed by the caller. - """ - strip_comments = lambda x: re.sub(COMMENT_REGEX, '', x) - strip_ellipses = lambda x: re.sub(ELLIPSES_REGEX, '', x) - - result = deque() - - for line in lines: - if is_code_block_delim(line): - break - result.append(strip_comments(strip_ellipses(line))) - return result - - -def get_lines(filename): - with open(filename) as f: - for line in f: - yield line - - -def get_tags(line): - """ - Retrieves all tags from the line format: - ~~~ { .tag1 .tag2 .tag3 } - """ - return set(re.findall(TAGS_REGEX, line)) - - -def write_file(filename, lines): - with open(filename, 'w', encoding='utf-8') as f: - for line in lines: - f.write(unicode(line, encoding='utf-8', errors='replace')) - - -def main(argv=None): - if not argv: - argv = sys.argv - - if len(sys.argv) < 2: - sys.stderr.write("Please provide an input filename.") - sys.exit(1) - elif len(sys.argv) < 3: - sys.stderr.write("Please provide a destination directory.") - sys.exit(1) - - input_file = sys.argv[1] - dest_dir = sys.argv[2] - - if not os.path.exists(input_file): - sys.stderr.write("Input file does not exist.") - sys.exit(1) - - if not os.path.exists(dest_dir): - os.mkdir(dest_dir) - - extract_code_fragments(dest_dir, get_lines(input_file)) - - -if __name__ == "__main__": - sys.exit(main()) From 3c4ff1b872e1d1fd4e905f9a9ac3aeec5397608d Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 9 Mar 2014 14:55:20 +1100 Subject: [PATCH 07/10] mk: rename `check-...-doc-` to `check-...-doc-crate-`. E.g. this stops check-...-doc rules for `rustdoc.md` and `librustdoc` from stamping on each other, so that they are correctly built and tested. (Previously only the rustdoc crate was tested.) --- mk/tests.mk | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mk/tests.mk b/mk/tests.mk index 8f48918d31aeb..23433a4705a39 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -309,7 +309,7 @@ endif check-stage$(1)-T-$(2)-H-$(3)-doc-crates-exec: \ $$(foreach crate,$$(TEST_DOC_CRATES), \ - check-stage$(1)-T-$(2)-H-$(3)-doc-$$(crate)-exec) + check-stage$(1)-T-$(2)-H-$(3)-doc-crate-$$(crate)-exec) check-stage$(1)-T-$(2)-H-$(3)-doc-exec: \ $$(foreach docname,$$(DOCS), \ @@ -721,15 +721,16 @@ else CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(RSINPUTS_$(4)) endif -check-stage$(1)-T-$(2)-H-$(3)-doc-$(4)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)) +check-stage$(1)-T-$(2)-H-$(3)-doc-crate-$(4)-exec: \ + $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-crate-$(4)) ifeq ($(2),$$(CFG_BUILD)) -$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): $$(CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4)) - @$$(call E, run doc-$(4) [$(2)]) +$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-crate-$(4)): $$(CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4)) + @$$(call E, run doc-crate-$(4) [$(2)]) $$(Q)$$(RUSTDOC_$(1)_T_$(2)_H_$(3)) --test \ $$(CRATEFILE_$(4)) --test-args "$$(TESTARGS)" && touch $$@ else -$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): +$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-crate-$(4)): touch $$@ endif @@ -748,7 +749,7 @@ $(foreach host,$(CFG_HOST), \ TEST_GROUPS = \ crates \ $(foreach crate,$(TEST_CRATES),$(crate)) \ - $(foreach crate,$(TEST_DOC_CRATES),doc-$(crate)) \ + $(foreach crate,$(TEST_DOC_CRATES),doc-crate-$(crate)) \ rpass \ rpass-full \ rfail \ @@ -830,7 +831,7 @@ define DEF_CHECK_DOC_FOR_STAGE check-stage$(1)-docs: $$(foreach docname,$$(DOCS),\ check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-$$(docname)) \ $$(foreach crate,$$(TEST_DOC_CRATES),\ - check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-$$(crate)) + check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-crate-$$(crate)) endef $(foreach stage,$(STAGES), \ From 6d6e2880d2f1b3e00d28957a5e6f540fcfda2556 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 9 Mar 2014 12:50:45 +1100 Subject: [PATCH 08/10] tutorial: hack a code snippet to make it compile. This is meant to be compiling a crate, but the crate_id attribute seems to be upsetting it if the attribute is actually on the crate. I.e. this makes this test compile by putting the crate_id attribute on a function and so it's ignored. Such a hack. :( --- src/doc/tutorial.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/doc/tutorial.md b/src/doc/tutorial.md index 476cc26fe6735..d103b9356d91a 100644 --- a/src/doc/tutorial.md +++ b/src/doc/tutorial.md @@ -3143,12 +3143,13 @@ Now for something that you can actually compile yourself. We define two crates, and use one of them as a library in the other. -~~~~{.ignore} +~~~~ // `world.rs` #[crate_id = "world#0.42"]; -# extern crate extra; + +# mod secret_module_to_make_this_test_run { pub fn explore() -> &'static str { "world" } -# fn main() {} +# } ~~~~ ~~~~ {.ignore} From bb8ac2159feb1739992b1b511a0a9afeaab10a8e Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 9 Mar 2014 14:14:07 +1100 Subject: [PATCH 09/10] docs: render rustdoc docs with rustdoc, hack around sundown code-fence parsing limitations. Sundown parses ``` ~~~ as a valid codeblock (i.e. mismatching delimiters), which made using rustdoc on its own documentation impossible (since it used nested codeblocks to demonstrate how testable codesnippets worked). This modifies those snippets so that they're delimited by indentation, but this then means they're tested by `rustdoc --test` & rendered as Rust code (because there's no way to add `notrust` to indentation-delimited code blocks). A comment is added to stop the compiler reading the text too closely, but this unfortunately has to be visible in the final docs, since that's the text on which the highlighting happens. --- mk/docs.mk | 2 +- src/doc/rustdoc.md | 93 +++++++++++++++++++++++++++------------------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/mk/docs.mk b/mk/docs.mk index f3fd94678ff13..72ef088704ce1 100644 --- a/mk/docs.mk +++ b/mk/docs.mk @@ -26,7 +26,7 @@ DOCS := index tutorial guide-ffi guide-macros guide-lifetimes \ guide-tasks guide-container guide-pointers \ complement-cheatsheet guide-runtime \ - rust + rust rustdoc RUSTDOC_DEPS_rust := doc/full-toc.inc RUSTDOC_FLAGS_rust := --markdown-in-header=doc/full-toc.inc diff --git a/src/doc/rustdoc.md b/src/doc/rustdoc.md index 415db46be5b10..3359cd4f40d95 100644 --- a/src/doc/rustdoc.md +++ b/src/doc/rustdoc.md @@ -20,6 +20,7 @@ comments": //! the crate index page. The ! makes it apply to the parent of the comment, //! rather than what follows). +# mod workaround_the_outer_function_rustdoc_inserts { /// Widgets are very common (this is a doc comment, and will show up on /// Widget's documentation). pub struct Widget { @@ -36,6 +37,7 @@ pub fn recalibrate() { //! `recalibrate`). /* ... */ } +# } ~~~ Doc comments are markdown, and are currently parsed with the @@ -94,7 +96,7 @@ source code. To test documentation, the `--test` argument is passed to rustdoc: -~~~ +~~~ {.notrust} rustdoc --test crate.rs ~~~ @@ -105,35 +107,44 @@ code blocks as testable-by-default. In order to not run a test over a block of code, the `ignore` string can be added to the three-backtick form of markdown code block. -~~~ -``` -// This is a testable code block -``` + /** + # nested codefences confuse sundown => indentation + comment to + # avoid failing tests + ``` + // This is a testable code block + ``` -```ignore -// This is not a testable code block -``` + ```ignore + // This is not a testable code block + ``` - // This is a testable code block (4-space indent) -~~~ + // This is a testable code block (4-space indent) + */ + # fn foo() {} You can specify that the test's execution should fail with the `should_fail` directive. -~~~ -```should_fail -// This code block is expected to generate a failure when run -``` -~~~ + /** + # nested codefences confuse sundown => indentation + comment to + # avoid failing tests + ```should_fail + // This code block is expected to generate a failure when run + ``` + */ + # fn foo() {} You can specify that the code block should be compiled but not run with the `no_run` directive. -~~~ -```no_run -// This code will be compiled but not executed -``` -~~~ + /** + # nested codefences confuse sundown => indentation + comment to + # avoid failing tests + ```no_run + // This code will be compiled but not executed + ``` + */ + # fn foo() {} Rustdoc also supplies some extra sugar for helping with some tedious documentation examples. If a line is prefixed with `# `, then the line @@ -141,20 +152,23 @@ will not show up in the HTML documentation, but it will be used when testing the code block (NB. the space after the `#` is required, so that one can still write things like `#[deriving(Eq)]`). -~~~ -```rust -# /!\ The three following lines are comments, which are usually stripped off by -# the doc-generating tool. In order to display them anyway in this particular -# case, the character following the leading '#' is not a usual space like in -# these first five lines but a non breakable one. -# -# // showing 'fib' in this documentation would just be tedious and detracts from -# // what's actualy being documented. -# fn fib(n: int) { n + 2 } - -do spawn { fib(200); } -``` -~~~ + /** + # nested codefences confuse sundown => indentation + comment to + # avoid failing tests + ```rust + # /!\ The three following lines are comments, which are usually stripped off by + # the doc-generating tool. In order to display them anyway in this particular + # case, the character following the leading '#' is not a usual space like in + # these first five lines but a non breakable one. + # + # // showing 'fib' in this documentation would just be tedious and detracts from + # // what's actualy being documented. + # fn fib(n: int) { n + 2 } + + do spawn { fib(200); } + ``` + */ + # fn foo() {} The documentation online would look like `do spawn { fib(200); }`, but when testing this code, the `fib` function will be included (so it can compile). @@ -167,12 +181,12 @@ uses is build on crate `test`, which is also used when you compile crates with rustc's `--test` flag. Extra arguments can be passed to rustdoc's test harness with the `--test-args` flag. -~~~ -// Only run tests containing 'foo' in their name -rustdoc --test lib.rs --test-args 'foo' +~~~ {.notrust} +$ # Only run tests containing 'foo' in their name +$ rustdoc --test lib.rs --test-args 'foo' -// See what's possible when running tests -rustdoc --test lib.rs --test-args '--help' +$ # See what's possible when running tests +$ rustdoc --test lib.rs --test-args '--help' ~~~ When testing a library, code examples will often show how functions are used, @@ -189,6 +203,7 @@ into HTML and testing the code snippets from them. A Markdown file is detected by a `.md` or `.markdown` extension. There are 4 options to modify the output that Rustdoc creates. + - `--markdown-css PATH`: adds a `` tag pointing to `PATH`. - `--markdown-in-header FILE`: includes the contents of `FILE` at the end of the `...` section. From b4815ad1baffb2603f7580cbdf1ac1b01879b767 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 9 Mar 2014 15:54:16 +1100 Subject: [PATCH 10/10] mk: only build PDFs of the manual and tutorial. This restores the old behaviour (as compared to building PDF versions of all standalone docs), because some of the guides use unicode characters, which seems to make pdftex unhappy. --- mk/docs.mk | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mk/docs.mk b/mk/docs.mk index 72ef088704ce1..00832e0821690 100644 --- a/mk/docs.mk +++ b/mk/docs.mk @@ -14,6 +14,9 @@ # # The DOCS variable is their names (with no file extension). # +# PDF_DOCS lists the targets for which PDF documentation should be +# build. +# # RUSTDOC_FLAGS_xyz variables are extra arguments to pass to the # rustdoc invocation for xyz. # @@ -28,6 +31,8 @@ DOCS := index tutorial guide-ffi guide-macros guide-lifetimes \ complement-cheatsheet guide-runtime \ rust rustdoc +PDF_DOCS := tutorial rust + RUSTDOC_DEPS_rust := doc/full-toc.inc RUSTDOC_FLAGS_rust := --markdown-in-header=doc/full-toc.inc @@ -45,8 +50,6 @@ PANDOC_TEX_OPTS = $(PANDOC_BASE_OPTS) --include-before-body=doc/version.md \ --from=markdown --include-before-body=doc/footer.tex --to=latex PANDOC_EPUB_OPTS = $(PANDOC_BASE_OPTS) --to=epub - - # The rustdoc executable... RUSTDOC_EXE = $(HBIN2_H_$(CFG_BUILD))/rustdoc$(X_$(CFG_BUILD)) # ...with rpath included in case --disable-rpath was provided to @@ -146,6 +149,11 @@ doc/footer.tex: $(D)/footer.tex | doc/ # The (english) documentation for each doc item. +define DEF_SHOULD_BUILD_PDF_DOC +SHOULD_BUILD_PDF_DOC_$(1) = 1 +endef +$(foreach docname,$(PDF_DOCS),$(eval $(call DEF_SHOULD_BUILD_PDF_DOC,$(docname)))) + define DEF_DOC # HTML (rustdoc) @@ -171,6 +179,7 @@ doc/$(1).tex: $$(D)/$(1).md doc/footer.tex doc/version.md | doc/ $$(CFG_PANDOC) $$(PANDOC_TEX_OPTS) --output=$$@ ifneq ($(NO_PDF_DOCS),1) +ifeq ($$(SHOULD_BUILD_PDF_DOC_$(1)),1) DOC_TARGETS += doc/$(1).pdf doc/$(1).pdf: doc/$(1).tex @$$(call E, pdflatex: $$@) @@ -178,6 +187,7 @@ doc/$(1).pdf: doc/$(1).tex -interaction=batchmode \ -output-directory=doc \ $$< +endif # SHOULD_BUILD_PDF_DOCS_$(1) endif # NO_PDF_DOCS endif # ONLY_HTML_DOCS