Auto merge of #28499 - semmaz:doc-anchor-fix, r=steveklabnik
This changes how rustic generate `id` and `href` attributes for section header anchor. Now they are more github-like. Also fixes breakage in docs caused by this and broken links in "Error Handling" section of book. r? @steveklabnik cc @alexcrichton
This commit is contained in:
commit
f5a64a678f
@ -57,7 +57,7 @@ fn write_info(info: &Info) -> Result<(), IoError> {
|
||||
```
|
||||
|
||||
See
|
||||
[the `result` module documentation](https://doc.rust-lang.org/stable/std/result/index.html#the-try!-macro)
|
||||
[the `result` module documentation](https://doc.rust-lang.org/stable/std/result/index.html#the-try-macro)
|
||||
for more details.
|
||||
|
||||
### The `Result`-`impl` pattern [FIXME]
|
||||
|
@ -27,7 +27,7 @@ explicitly implement to be used by this generic function.
|
||||
* _Inference_. Since the type parameters to generic functions can usually be
|
||||
inferred, generic functions can help cut down on verbosity in code where
|
||||
explicit conversions or other method calls would usually be necessary. See the
|
||||
[overloading/implicits use case](#use-case:-limited-overloading-and/or-implicit-conversions)
|
||||
[overloading/implicits use case](#use-case-limited-overloading-andor-implicit-conversions)
|
||||
below.
|
||||
* _Precise types_. Because generics give a _name_ to the specific type
|
||||
implementing a trait, it is possible to be precise about places where that
|
||||
@ -51,7 +51,7 @@ explicitly implement to be used by this generic function.
|
||||
a `Vec<T>` contains elements of a single concrete type (and, indeed, the
|
||||
vector representation is specialized to lay these out in line). Sometimes
|
||||
heterogeneous collections are useful; see
|
||||
[trait objects](#use-case:-trait-objects) below.
|
||||
[trait objects](#use-case-trait-objects) below.
|
||||
* _Signature verbosity_. Heavy use of generics can bloat function signatures.
|
||||
**[Ed. note]** This problem may be mitigated by some language improvements; stay tuned.
|
||||
|
||||
|
@ -24,28 +24,28 @@ systems may want to jump around.
|
||||
* [The Basics](#the-basics)
|
||||
* [Unwrapping explained](#unwrapping-explained)
|
||||
* [The `Option` type](#the-option-type)
|
||||
* [Composing `Option<T>` values](#composing-option<t>-values)
|
||||
* [Composing `Option<T>` values](#composing-optiont-values)
|
||||
* [The `Result` type](#the-result-type)
|
||||
* [Parsing integers](#parsing-integers)
|
||||
* [The `Result` type alias idiom](#the-result-type-alias-idiom)
|
||||
* [A brief interlude: unwrapping isn't evil](#a-brief-interlude:-unwrapping-isn't-evil)
|
||||
* [A brief interlude: unwrapping isn't evil](#a-brief-interlude-unwrapping-isn't-evil)
|
||||
* [Working with multiple error types](#working-with-multiple-error-types)
|
||||
* [Composing `Option` and `Result`](#composing-option-and-result)
|
||||
* [The limits of combinators](#the-limits-of-combinators)
|
||||
* [Early returns](#early-returns)
|
||||
* [The `try!` macro](#the-try!-macro)
|
||||
* [The `try!` macro](#the-try-macro)
|
||||
* [Defining your own error type](#defining-your-own-error-type)
|
||||
* [Standard library traits used for error handling](#standard-library-traits-used-for-error-handling)
|
||||
* [The `Error` trait](#the-error-trait)
|
||||
* [The `From` trait](#the-from-trait)
|
||||
* [The real `try!` macro](#the-real-try!-macro)
|
||||
* [The real `try!` macro](#the-real-try-macro)
|
||||
* [Composing custom error types](#composing-custom-error-types)
|
||||
* [Advice for library writers](#advice-for-library-writers)
|
||||
* [Case study: A program to read population data](#case-study:-a-program-to-read-population-data)
|
||||
* [Case study: A program to read population data](#case-study-a-program-to-read-population-data)
|
||||
* [Initial setup](#initial-setup)
|
||||
* [Argument parsing](#argument-parsing)
|
||||
* [Writing the logic](#writing-the-logic)
|
||||
* [Error handling with `Box<Error>`](#error-handling-with-box%3Cerror%3E)
|
||||
* [Error handling with `Box<Error>`](#error-handling-with-boxerror)
|
||||
* [Reading from stdin](#reading-from-stdin)
|
||||
* [Error handling with a custom type](#error-handling-with-a-custom-type)
|
||||
* [Adding functionality](#adding-functionality)
|
||||
@ -87,7 +87,7 @@ thread '<main>' panicked at 'Invalid number: 11', src/bin/panic-simple.rs:5
|
||||
Here's another example that is slightly less contrived. A program that accepts
|
||||
an integer as an argument, doubles it and prints it.
|
||||
|
||||
<a name="code-unwrap-double"></a>
|
||||
<span id="code-unwrap-double"></span>
|
||||
|
||||
```rust,should_panic
|
||||
use std::env;
|
||||
@ -139,7 +139,7 @@ system is an important concept because it will cause the compiler to force the
|
||||
programmer to handle that absence. Let's take a look at an example that tries
|
||||
to find a character in a string:
|
||||
|
||||
<a name="code-option-ex-string-find"></a>
|
||||
<span id="code-option-ex-string-find"></span>
|
||||
|
||||
```rust
|
||||
// Searches `haystack` for the Unicode character `needle`. If one is found, the
|
||||
@ -186,7 +186,7 @@ But wait, what about `unwrap` used in [`unwrap-double`](#code-unwrap-double)?
|
||||
There was no case analysis there! Instead, the case analysis was put inside the
|
||||
`unwrap` method for you. You could define it yourself if you want:
|
||||
|
||||
<a name="code-option-def-unwrap"></a>
|
||||
<span id="code-option-def-unwrap"></span>
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
@ -253,7 +253,7 @@ option is `None`, in which case, just return `None`.
|
||||
Rust has parametric polymorphism, so it is very easy to define a combinator
|
||||
that abstracts this pattern:
|
||||
|
||||
<a name="code-option-map"></a>
|
||||
<span id="code-option-map"></span>
|
||||
|
||||
```rust
|
||||
fn map<F, T, A>(option: Option<T>, f: F) -> Option<A> where F: FnOnce(T) -> A {
|
||||
@ -394,7 +394,7 @@ remove choices because they will panic if `Option<T>` is `None`.
|
||||
The `Result` type is also
|
||||
[defined in the standard library][6]:
|
||||
|
||||
<a name="code-result-def-1"></a>
|
||||
<span id="code-result-def"></span>
|
||||
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
@ -562,7 +562,7 @@ combinators that affect only the error type, such as
|
||||
### The `Result` type alias idiom
|
||||
|
||||
In the standard library, you may frequently see types like
|
||||
`Result<i32>`. But wait, [we defined `Result`](#code-result-def-1) to
|
||||
`Result<i32>`. But wait, [we defined `Result`](#code-result-def) to
|
||||
have two type parameters. How can we get away with only specifying
|
||||
one? The key is to define a `Result` type alias that *fixes* one of
|
||||
the type parameters to a particular type. Usually the fixed type is
|
||||
@ -672,7 +672,7 @@ with both an `Option` and a `Result`, the solution is *usually* to convert the
|
||||
(from `env::args()`) means the user didn't invoke the program correctly. We
|
||||
could just use a `String` to describe the error. Let's try:
|
||||
|
||||
<a name="code-error-double-string"></a>
|
||||
<span id="code-error-double-string"></span>
|
||||
|
||||
```rust
|
||||
use std::env;
|
||||
@ -906,7 +906,7 @@ seen above.
|
||||
|
||||
Here is a simplified definition of a `try!` macro:
|
||||
|
||||
<a nama name="code-try-def-simple"></a>
|
||||
<span id="code-try-def-simple"></span>
|
||||
|
||||
```rust
|
||||
macro_rules! try {
|
||||
@ -1168,7 +1168,7 @@ The `std::convert::From` trait is
|
||||
[defined in the standard
|
||||
library](../std/convert/trait.From.html):
|
||||
|
||||
<a name="code-from-def"></a>
|
||||
<span id="code-from-def"></span>
|
||||
|
||||
```rust
|
||||
trait From<T> {
|
||||
@ -1250,7 +1250,7 @@ macro_rules! try {
|
||||
This is not its real definition. Its real definition is
|
||||
[in the standard library](../std/macro.try!.html):
|
||||
|
||||
<a name="code-try-def"></a>
|
||||
<span id="code-try-def"></span>
|
||||
|
||||
```rust
|
||||
macro_rules! try {
|
||||
@ -1515,7 +1515,7 @@ and [`rustc-serialize`](https://crates.io/crates/rustc-serialize) crates.
|
||||
|
||||
We're not going to spend a lot of time on setting up a project with
|
||||
Cargo because it is already covered well in [the Cargo
|
||||
chapter](../book/hello-cargo) and [Cargo's documentation][14].
|
||||
chapter](../book/hello-cargo.html) and [Cargo's documentation][14].
|
||||
|
||||
To get started from scratch, run `cargo new --bin city-pop` and make sure your
|
||||
`Cargo.toml` looks something like this:
|
||||
@ -1573,7 +1573,7 @@ fn main() {
|
||||
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "Show this usage message.");
|
||||
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => { m }
|
||||
Err(e) => { panic!(e.to_string()) }
|
||||
@ -1584,7 +1584,7 @@ fn main() {
|
||||
}
|
||||
let data_path = args[1].clone();
|
||||
let city = args[2].clone();
|
||||
|
||||
|
||||
// Do stuff with information
|
||||
}
|
||||
```
|
||||
@ -1647,27 +1647,27 @@ fn main() {
|
||||
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "Show this usage message.");
|
||||
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => { m }
|
||||
Err(e) => { panic!(e.to_string()) }
|
||||
};
|
||||
|
||||
|
||||
if matches.opt_present("h") {
|
||||
print_usage(&program, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let data_file = args[1].clone();
|
||||
let data_path = Path::new(&data_file);
|
||||
let city = args[2].clone();
|
||||
|
||||
|
||||
let file = fs::File::open(data_path).unwrap();
|
||||
let mut rdr = csv::Reader::from_reader(file);
|
||||
|
||||
|
||||
for row in rdr.decode::<Row>() {
|
||||
let row = row.unwrap();
|
||||
|
||||
|
||||
if row.city == city {
|
||||
println!("{}, {}: {:?}",
|
||||
row.city, row.country,
|
||||
@ -1773,7 +1773,7 @@ fn main() {
|
||||
print_usage(&program, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let data_file = args[1].clone();
|
||||
let data_path = Path::new(&data_file);
|
||||
let city = args[2].clone();
|
||||
@ -1882,7 +1882,7 @@ opts.optflag("h", "help", "Show this usage message.");
|
||||
...
|
||||
let file = matches.opt_str("f");
|
||||
let data_file = file.as_ref().map(Path::new);
|
||||
|
||||
|
||||
let city = if !matches.free.is_empty() {
|
||||
matches.free[0].clone()
|
||||
} else {
|
||||
|
@ -207,9 +207,7 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
|
||||
///
|
||||
/// Any leading or trailing whitespace will be trimmed.
|
||||
fn collapse_whitespace(s: &str) -> String {
|
||||
s.split(|c: char| c.is_whitespace()).filter(|s| {
|
||||
!s.is_empty()
|
||||
}).collect::<Vec<_>>().join(" ")
|
||||
s.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||
}
|
||||
|
||||
thread_local!(static USED_HEADER_MAP: RefCell<HashMap<String, usize>> = {
|
||||
@ -277,25 +275,44 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||
|
||||
// Extract the text provided
|
||||
let s = if text.is_null() {
|
||||
"".to_string()
|
||||
"".to_owned()
|
||||
} else {
|
||||
let s = unsafe { (*text).as_bytes() };
|
||||
str::from_utf8(s).unwrap().to_string()
|
||||
str::from_utf8(&s).unwrap().to_owned()
|
||||
};
|
||||
|
||||
// Transform the contents of the header into a hyphenated string
|
||||
let id = s.split_whitespace().map(|s| s.to_ascii_lowercase())
|
||||
.collect::<Vec<String>>().join("-");
|
||||
|
||||
// Discard '<em>', '<code>' tags and some escaped characters,
|
||||
// transform the contents of the header into a hyphenated string
|
||||
// without non-alphanumeric characters other than '-' and '_'.
|
||||
//
|
||||
// This is a terrible hack working around how hoedown gives us rendered
|
||||
// html for text rather than the raw text.
|
||||
let mut id = s.clone();
|
||||
let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
|
||||
"<strong>", "</strong>",
|
||||
"<", ">", "&", "'", """];
|
||||
for sub in repl_sub {
|
||||
id = id.replace(sub, "");
|
||||
}
|
||||
let id = id.chars().filter_map(|c| {
|
||||
if c.is_alphanumeric() || c == '-' || c == '_' {
|
||||
if c.is_ascii() {
|
||||
Some(c.to_ascii_lowercase())
|
||||
} else {
|
||||
Some(c)
|
||||
}
|
||||
} else if c.is_whitespace() && c.is_ascii() {
|
||||
Some('-')
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect::<String>();
|
||||
|
||||
let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
|
||||
let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
|
||||
|
||||
// Make sure our hyphenated ID is unique for this page
|
||||
let id = USED_HEADER_MAP.with(|map| {
|
||||
let id = id.replace("<code>", "").replace("</code>", "").to_string();
|
||||
let id = match map.borrow_mut().get_mut(&id) {
|
||||
None => id,
|
||||
Some(a) => { *a += 1; format!("{}-{}", id, *a - 1) }
|
||||
@ -304,22 +321,15 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||
id
|
||||
});
|
||||
|
||||
let sec = match opaque.toc_builder {
|
||||
Some(ref mut builder) => {
|
||||
builder.push(level as u32, s.clone(), id.clone())
|
||||
}
|
||||
None => {""}
|
||||
};
|
||||
|
||||
let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
|
||||
format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
|
||||
});
|
||||
|
||||
// Render the HTML
|
||||
let text = format!(r##"<h{lvl} id="{id}" class='section-header'><a
|
||||
href="#{id}">{sec}{}</a></h{lvl}>"##,
|
||||
s, lvl = level, id = id,
|
||||
sec = if sec.is_empty() {
|
||||
sec.to_string()
|
||||
} else {
|
||||
format!("{} ", sec)
|
||||
});
|
||||
let text = format!("<h{lvl} id='{id}' class='section-header'>\
|
||||
<a href='#{id}'>{sec}{}</a></h{lvl}>",
|
||||
s, lvl = level, id = id, sec = sec);
|
||||
|
||||
let text = CString::new(text).unwrap();
|
||||
unsafe { hoedown_buffer_puts(ob, text.as_ptr()) }
|
||||
@ -333,7 +343,7 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||
_: *const hoedown_renderer_data,
|
||||
) -> libc::c_int {
|
||||
let content = if text.is_null() {
|
||||
"".to_string()
|
||||
"".to_owned()
|
||||
} else {
|
||||
let bytes = unsafe { (*text).as_bytes() };
|
||||
let s = str::from_utf8(bytes).unwrap();
|
||||
@ -367,10 +377,9 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||
|
||||
hoedown_html_renderer_free(renderer);
|
||||
|
||||
let mut ret = match opaque.toc_builder {
|
||||
Some(b) => write!(w, "<nav id=\"TOC\">{}</nav>", b.into_toc()),
|
||||
None => Ok(())
|
||||
};
|
||||
let mut ret = opaque.toc_builder.map_or(Ok(()), |builder| {
|
||||
write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
|
||||
});
|
||||
|
||||
if ret.is_ok() {
|
||||
let buf = (*ob).as_bytes();
|
||||
@ -404,7 +413,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
|
||||
stripped_filtered_line(l).unwrap_or(l)
|
||||
});
|
||||
let text = lines.collect::<Vec<&str>>().join("\n");
|
||||
tests.add_test(text.to_string(),
|
||||
tests.add_test(text.to_owned(),
|
||||
block_info.should_panic, block_info.no_run,
|
||||
block_info.ignore, block_info.test_harness);
|
||||
}
|
||||
@ -560,10 +569,7 @@ pub fn plain_summary_line(md: &str) -> String {
|
||||
md.len() as libc::size_t);
|
||||
hoedown_document_free(document);
|
||||
let plain_slice = (*ob).as_bytes();
|
||||
let plain = match str::from_utf8(plain_slice) {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
let plain = str::from_utf8(plain_slice).unwrap_or("").to_owned();
|
||||
hoedown_buffer_free(ob);
|
||||
plain
|
||||
}
|
||||
@ -572,7 +578,7 @@ pub fn plain_summary_line(md: &str) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{LangString, Markdown};
|
||||
use super::{collapse_whitespace, plain_summary_line};
|
||||
use super::plain_summary_line;
|
||||
|
||||
#[test]
|
||||
fn test_lang_string_parse() {
|
||||
@ -607,6 +613,27 @@ mod tests {
|
||||
format!("{}", Markdown(markdown));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
fn t(input: &str, expect: &str) {
|
||||
let output = format!("{}", Markdown(input));
|
||||
assert_eq!(output, expect);
|
||||
}
|
||||
|
||||
t("# Foo bar", "\n<h1 id='foo-bar' class='section-header'>\
|
||||
<a href='#foo-bar'>Foo bar</a></h1>");
|
||||
t("## Foo-bar_baz qux", "\n<h2 id='foo-bar_baz-qux' class=\'section-\
|
||||
header'><a href='#foo-bar_baz-qux'>Foo-bar_baz qux</a></h2>");
|
||||
t("### **Foo** *bar* baz!?!& -_qux_-%",
|
||||
"\n<h3 id='foo-bar-baz--_qux_-' class='section-header'>\
|
||||
<a href='#foo-bar-baz--_qux_-'><strong>Foo</strong> \
|
||||
<em>bar</em> baz!?!& -_qux_-%</a></h3>");
|
||||
t("####**Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
|
||||
"\n<h4 id='foo--bar--baz--qux' class='section-header'>\
|
||||
<a href='#foo--bar--baz--qux'><strong>Foo?</strong> & *bar?!* \
|
||||
<em><code>baz</code></em> ❤ #qux</a></h4>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plain_summary_line() {
|
||||
fn t(input: &str, expect: &str) {
|
||||
@ -620,18 +647,4 @@ mod tests {
|
||||
t("# top header", "top header");
|
||||
t("## header", "header");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collapse_whitespace() {
|
||||
fn t(input: &str, expected: &str) {
|
||||
let actual = collapse_whitespace(input);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
t("foo", "foo");
|
||||
t("foo bar baz", "foo bar baz");
|
||||
t(" foo bar", "foo bar");
|
||||
t("\tfoo bar\nbaz", "foo bar baz");
|
||||
t("foo bar \n baz\t\tqux\n", "foo bar baz qux");
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
//! language primitives](#primitives), [standard macros](#macros),
|
||||
//! [I/O](io/index.html) and [multithreading](thread/index.html), among
|
||||
//! [many other
|
||||
//! things](#what-is-in-the-standard-library-documentation?).
|
||||
//! things](#what-is-in-the-standard-library-documentation).
|
||||
//!
|
||||
//! `std` is available to all Rust crates by default, just as if each
|
||||
//! one contained an `extern crate std` import at the [crate
|
||||
|
Loading…
Reference in New Issue
Block a user