Skip to content

Commit 5e9f4a1

Browse files
committed
Merge branch 'master' of https://github.com/rust-lang/mdBook
2 parents 6816d47 + 94f7578 commit 5e9f4a1

File tree

6 files changed

+94
-27
lines changed

6 files changed

+94
-27
lines changed

guide/src/format/mdbook.md

+9
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,12 @@ Here is what a rendered code snippet looks like:
192192
{{#playground example.rs}}
193193

194194
[Rust Playground]: https://play.rust-lang.org/
195+
196+
## Controlling page \<title\>
197+
198+
A chapter can set a \<title\> that is different from its entry in the table of
199+
contents (sidebar) by including a `\{{#title ...}}` near the top of the page.
200+
201+
```hbs
202+
\{{#title My Title}}
203+
```

src/book/mod.rs

+6-9
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,20 @@ impl MDBook {
196196
}
197197
}
198198

199-
info!("Running the {} backend", renderer.name());
200-
self.render(&preprocessed_book, renderer)?;
201-
202-
Ok(())
203-
}
204-
205-
fn render(&self, preprocessed_book: &Book, renderer: &dyn Renderer) -> Result<()> {
206199
let name = renderer.name();
207200
let build_dir = self.build_dir_for(name);
208201

209-
let render_context = RenderContext::new(
202+
let mut render_context = RenderContext::new(
210203
self.root.clone(),
211-
preprocessed_book.clone(),
204+
preprocessed_book,
212205
self.config.clone(),
213206
build_dir,
214207
);
208+
render_context
209+
.chapter_titles
210+
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
215211

212+
info!("Running the {} backend", renderer.name());
216213
renderer
217214
.render(&render_context)
218215
.with_context(|| "Rendering failed")

src/preprocess/links.rs

+62-15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const MAX_LINK_NESTED_DEPTH: usize = 10;
2323
/// This hides the lines from initial display but shows them when the reader expands the code
2424
/// block and provides them to Rustdoc for testing.
2525
/// - `{{# playground}}` - Insert runnable Rust files
26+
/// - `{{# title}}` - Override \<title\> of a webpage.
2627
#[derive(Default)]
2728
pub struct LinkPreprocessor;
2829

@@ -51,8 +52,15 @@ impl Preprocessor for LinkPreprocessor {
5152
.map(|dir| src_dir.join(dir))
5253
.expect("All book items have a parent");
5354

54-
let content = replace_all(&ch.content, base, chapter_path, 0);
55+
let mut chapter_title = ch.name.clone();
56+
let content =
57+
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
5558
ch.content = content;
59+
if chapter_title != ch.name {
60+
ctx.chapter_titles
61+
.borrow_mut()
62+
.insert(chapter_path.clone(), chapter_title);
63+
}
5664
}
5765
}
5866
});
@@ -61,7 +69,13 @@ impl Preprocessor for LinkPreprocessor {
6169
}
6270
}
6371

64-
fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String
72+
fn replace_all<P1, P2>(
73+
s: &str,
74+
path: P1,
75+
source: P2,
76+
depth: usize,
77+
chapter_title: &mut String,
78+
) -> String
6579
where
6680
P1: AsRef<Path>,
6781
P2: AsRef<Path>,
@@ -77,11 +91,17 @@ where
7791
for link in find_links(s) {
7892
replaced.push_str(&s[previous_end_index..link.start_index]);
7993

80-
match link.render_with_path(&path) {
94+
match link.render_with_path(&path, chapter_title) {
8195
Ok(new_content) => {
8296
if depth < MAX_LINK_NESTED_DEPTH {
8397
if let Some(rel_path) = link.link_type.relative_path(path) {
84-
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1));
98+
replaced.push_str(&replace_all(
99+
&new_content,
100+
rel_path,
101+
source,
102+
depth + 1,
103+
chapter_title,
104+
));
85105
} else {
86106
replaced.push_str(&new_content);
87107
}
@@ -116,6 +136,7 @@ enum LinkType<'a> {
116136
Include(PathBuf, RangeOrAnchor),
117137
Playground(PathBuf, Vec<&'a str>),
118138
RustdocInclude(PathBuf, RangeOrAnchor),
139+
Title(&'a str),
119140
}
120141

121142
#[derive(PartialEq, Debug, Clone)]
@@ -185,6 +206,7 @@ impl<'a> LinkType<'a> {
185206
LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
186207
LinkType::Playground(p, _) => Some(return_relative_path(base, &p)),
187208
LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
209+
LinkType::Title(_) => None,
188210
}
189211
}
190212
}
@@ -255,6 +277,9 @@ struct Link<'a> {
255277
impl<'a> Link<'a> {
256278
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
257279
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
280+
(_, Some(typ), Some(title)) if typ.as_str() == "title" => {
281+
Some(LinkType::Title(title.as_str()))
282+
}
258283
(_, Some(typ), Some(rest)) => {
259284
let mut path_props = rest.as_str().split_whitespace();
260285
let file_arg = path_props.next();
@@ -291,7 +316,11 @@ impl<'a> Link<'a> {
291316
})
292317
}
293318

294-
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
319+
fn render_with_path<P: AsRef<Path>>(
320+
&self,
321+
base: P,
322+
chapter_title: &mut String,
323+
) -> Result<String> {
295324
let base = base.as_ref();
296325
match self.link_type {
297326
// omit the escape char
@@ -353,6 +382,10 @@ impl<'a> Link<'a> {
353382
contents
354383
))
355384
}
385+
LinkType::Title(title) => {
386+
*chapter_title = title.to_owned();
387+
Ok(String::new())
388+
}
356389
}
357390
}
358391
}
@@ -373,17 +406,17 @@ impl<'a> Iterator for LinkIter<'a> {
373406

374407
fn find_links(contents: &str) -> LinkIter<'_> {
375408
// lazily compute following regex
376-
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?;
409+
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
377410
lazy_static! {
378411
static ref RE: Regex = Regex::new(
379-
r"(?x) # insignificant whitespace mode
380-
\\\{\{\#.*\}\} # match escaped link
381-
| # or
382-
\{\{\s* # link opening parens and whitespace
383-
\#([a-zA-Z0-9_]+) # link type
384-
\s+ # separating whitespace
385-
([a-zA-Z0-9\s_.\-:/\\\+]+) # link target path and space separated properties
386-
\s*\}\} # whitespace and link closing parens"
412+
r"(?x) # insignificant whitespace mode
413+
\\\{\{\#.*\}\} # match escaped link
414+
| # or
415+
\{\{\s* # link opening parens and whitespace
416+
\#([a-zA-Z0-9_]+) # link type
417+
\s+ # separating whitespace
418+
([^}]+) # link target path and space separated properties
419+
\}\} # link closing parens"
387420
)
388421
.unwrap();
389422
}
@@ -406,7 +439,21 @@ mod tests {
406439
```hbs
407440
{{#include file.rs}} << an escaped link!
408441
```";
409-
assert_eq!(replace_all(start, "", "", 0), end);
442+
let mut chapter_title = "test_replace_all_escaped".to_owned();
443+
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
444+
}
445+
446+
#[test]
447+
fn test_set_chapter_title() {
448+
let start = r"{{#title My Title}}
449+
# My Chapter
450+
";
451+
let end = r"
452+
# My Chapter
453+
";
454+
let mut chapter_title = "test_set_chapter_title".to_owned();
455+
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
456+
assert_eq!(chapter_title, "My Title");
410457
}
411458

412459
#[test]

src/preprocess/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::book::Book;
1212
use crate::config::Config;
1313
use crate::errors::*;
1414

15+
use std::cell::RefCell;
16+
use std::collections::HashMap;
1517
use std::path::PathBuf;
1618

1719
/// Extra information for a `Preprocessor` to give them more context when
@@ -27,6 +29,8 @@ pub struct PreprocessorContext {
2729
/// The calling `mdbook` version.
2830
pub mdbook_version: String,
2931
#[serde(skip)]
32+
pub(crate) chapter_titles: RefCell<HashMap<PathBuf, String>>,
33+
#[serde(skip)]
3034
__non_exhaustive: (),
3135
}
3236

@@ -38,6 +42,7 @@ impl PreprocessorContext {
3842
config,
3943
renderer,
4044
mdbook_version: crate::MDBOOK_VERSION.to_string(),
45+
chapter_titles: RefCell::new(HashMap::new()),
4146
__non_exhaustive: (),
4247
}
4348
}

src/renderer/html_handlebars/hbs_renderer.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ impl HtmlHandlebars {
7070
.and_then(serde_json::Value::as_str)
7171
.unwrap_or("");
7272

73-
let title = match book_title {
74-
"" => ch.name.clone(),
75-
_ => ch.name.clone() + " - " + book_title,
73+
let title = if let Some(title) = ctx.chapter_titles.get(path) {
74+
title.clone()
75+
} else if book_title.is_empty() {
76+
ch.name.clone()
77+
} else {
78+
ch.name.clone() + " - " + book_title
7679
};
7780

7881
ctx.data.insert("path".to_owned(), json!(path));
@@ -507,6 +510,7 @@ impl Renderer for HtmlHandlebars {
507510
is_index,
508511
html_config: html_config.clone(),
509512
edition: ctx.config.rust.edition,
513+
chapter_titles: &ctx.chapter_titles,
510514
};
511515
self.render_item(item, ctx, &mut print_content)?;
512516
is_index = false;
@@ -922,6 +926,7 @@ struct RenderItemContext<'a> {
922926
is_index: bool,
923927
html_config: HtmlConfig,
924928
edition: Option<RustEdition>,
929+
chapter_titles: &'a HashMap<PathBuf, String>,
925930
}
926931

927932
#[cfg(test)]

src/renderer/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod html_handlebars;
1818
mod markdown_renderer;
1919

2020
use shlex::Shlex;
21+
use std::collections::HashMap;
2122
use std::fs;
2223
use std::io::{self, ErrorKind, Read};
2324
use std::path::{Path, PathBuf};
@@ -64,6 +65,8 @@ pub struct RenderContext {
6465
/// guaranteed to be empty or even exist.
6566
pub destination: PathBuf,
6667
#[serde(skip)]
68+
pub(crate) chapter_titles: HashMap<PathBuf, String>,
69+
#[serde(skip)]
6770
__non_exhaustive: (),
6871
}
6972

@@ -80,6 +83,7 @@ impl RenderContext {
8083
version: crate::MDBOOK_VERSION.to_string(),
8184
root: root.into(),
8285
destination: destination.into(),
86+
chapter_titles: HashMap::new(),
8387
__non_exhaustive: (),
8488
}
8589
}

0 commit comments

Comments
 (0)