Skip to content

Add page title override: {{#title My Title}} #1381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions guide/src/format/mdbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,12 @@ Here is what a rendered code snippet looks like:
{{#playground example.rs}}

[Rust Playground]: https://play.rust-lang.org/

## Controlling page \<title\>

A chapter can set a \<title\> that is different from its entry in the table of
contents (sidebar) by including a `\{{#title ...}}` near the top of the page.

```hbs
\{{#title My Title}}
```
15 changes: 6 additions & 9 deletions src/book/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,20 @@ impl MDBook {
}
}

info!("Running the {} backend", renderer.name());
self.render(&preprocessed_book, renderer)?;

Ok(())
}

fn render(&self, preprocessed_book: &Book, renderer: &dyn Renderer) -> Result<()> {
let name = renderer.name();
let build_dir = self.build_dir_for(name);

let render_context = RenderContext::new(
let mut render_context = RenderContext::new(
self.root.clone(),
preprocessed_book.clone(),
preprocessed_book,
self.config.clone(),
build_dir,
);
render_context
.chapter_titles
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());

info!("Running the {} backend", renderer.name());
renderer
.render(&render_context)
.with_context(|| "Rendering failed")
Expand Down
77 changes: 62 additions & 15 deletions src/preprocess/links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const MAX_LINK_NESTED_DEPTH: usize = 10;
/// This hides the lines from initial display but shows them when the reader expands the code
/// block and provides them to Rustdoc for testing.
/// - `{{# playground}}` - Insert runnable Rust files
/// - `{{# title}}` - Override \<title\> of a webpage.
#[derive(Default)]
pub struct LinkPreprocessor;

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

let content = replace_all(&ch.content, base, chapter_path, 0);
let mut chapter_title = ch.name.clone();
let content =
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
ch.content = content;
if chapter_title != ch.name {
ctx.chapter_titles
.borrow_mut()
.insert(chapter_path.clone(), chapter_title);
}
}
}
});
Expand All @@ -61,7 +69,13 @@ impl Preprocessor for LinkPreprocessor {
}
}

fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String
fn replace_all<P1, P2>(
s: &str,
path: P1,
source: P2,
depth: usize,
chapter_title: &mut String,
) -> String
where
P1: AsRef<Path>,
P2: AsRef<Path>,
Expand All @@ -77,11 +91,17 @@ where
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);

match link.render_with_path(&path) {
match link.render_with_path(&path, chapter_title) {
Ok(new_content) => {
if depth < MAX_LINK_NESTED_DEPTH {
if let Some(rel_path) = link.link_type.relative_path(path) {
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1));
replaced.push_str(&replace_all(
&new_content,
rel_path,
source,
depth + 1,
chapter_title,
));
} else {
replaced.push_str(&new_content);
}
Expand Down Expand Up @@ -116,6 +136,7 @@ enum LinkType<'a> {
Include(PathBuf, RangeOrAnchor),
Playground(PathBuf, Vec<&'a str>),
RustdocInclude(PathBuf, RangeOrAnchor),
Title(&'a str),
}

#[derive(PartialEq, Debug, Clone)]
Expand Down Expand Up @@ -185,6 +206,7 @@ impl<'a> LinkType<'a> {
LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
LinkType::Playground(p, _) => Some(return_relative_path(base, &p)),
LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
LinkType::Title(_) => None,
}
}
}
Expand Down Expand Up @@ -255,6 +277,9 @@ struct Link<'a> {
impl<'a> Link<'a> {
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
(_, Some(typ), Some(title)) if typ.as_str() == "title" => {
Some(LinkType::Title(title.as_str()))
}
(_, Some(typ), Some(rest)) => {
let mut path_props = rest.as_str().split_whitespace();
let file_arg = path_props.next();
Expand Down Expand Up @@ -291,7 +316,11 @@ impl<'a> Link<'a> {
})
}

fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
fn render_with_path<P: AsRef<Path>>(
&self,
base: P,
chapter_title: &mut String,
) -> Result<String> {
let base = base.as_ref();
match self.link_type {
// omit the escape char
Expand Down Expand Up @@ -353,6 +382,10 @@ impl<'a> Link<'a> {
contents
))
}
LinkType::Title(title) => {
*chapter_title = title.to_owned();
Ok(String::new())
}
}
}
}
Expand All @@ -373,17 +406,17 @@ impl<'a> Iterator for LinkIter<'a> {

fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?;
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
lazy_static! {
static ref RE: Regex = Regex::new(
r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([a-zA-Z0-9\s_.\-:/\\\+]+) # link target path and space separated properties
\s*\}\} # whitespace and link closing parens"
r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([^}]+) # link target path and space separated properties
\}\} # link closing parens"
)
.unwrap();
}
Expand All @@ -406,7 +439,21 @@ mod tests {
```hbs
{{#include file.rs}} << an escaped link!
```";
assert_eq!(replace_all(start, "", "", 0), end);
let mut chapter_title = "test_replace_all_escaped".to_owned();
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
}

#[test]
fn test_set_chapter_title() {
let start = r"{{#title My Title}}
# My Chapter
";
let end = r"
# My Chapter
";
let mut chapter_title = "test_set_chapter_title".to_owned();
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
assert_eq!(chapter_title, "My Title");
}

#[test]
Expand Down
5 changes: 5 additions & 0 deletions src/preprocess/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::book::Book;
use crate::config::Config;
use crate::errors::*;

use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;

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

Expand All @@ -38,6 +42,7 @@ impl PreprocessorContext {
config,
renderer,
mdbook_version: crate::MDBOOK_VERSION.to_string(),
chapter_titles: RefCell::new(HashMap::new()),
__non_exhaustive: (),
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/renderer/html_handlebars/hbs_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ impl HtmlHandlebars {
.and_then(serde_json::Value::as_str)
.unwrap_or("");

let title = match book_title {
"" => ch.name.clone(),
_ => ch.name.clone() + " - " + book_title,
let title = if let Some(title) = ctx.chapter_titles.get(path) {
title.clone()
} else if book_title.is_empty() {
ch.name.clone()
} else {
ch.name.clone() + " - " + book_title
};

ctx.data.insert("path".to_owned(), json!(path));
Expand Down Expand Up @@ -501,6 +504,7 @@ impl Renderer for HtmlHandlebars {
is_index,
html_config: html_config.clone(),
edition: ctx.config.rust.edition,
chapter_titles: &ctx.chapter_titles,
};
self.render_item(item, ctx, &mut print_content)?;
is_index = false;
Expand Down Expand Up @@ -916,6 +920,7 @@ struct RenderItemContext<'a> {
is_index: bool,
html_config: HtmlConfig,
edition: Option<RustEdition>,
chapter_titles: &'a HashMap<PathBuf, String>,
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod html_handlebars;
mod markdown_renderer;

use shlex::Shlex;
use std::collections::HashMap;
use std::fs;
use std::io::{self, ErrorKind, Read};
use std::path::PathBuf;
Expand Down Expand Up @@ -67,6 +68,8 @@ pub struct RenderContext {
/// guaranteed to be empty or even exist.
pub destination: PathBuf,
#[serde(skip)]
pub(crate) chapter_titles: HashMap<PathBuf, String>,
#[serde(skip)]
__non_exhaustive: (),
}

Expand All @@ -83,6 +86,7 @@ impl RenderContext {
version: crate::MDBOOK_VERSION.to_string(),
root: root.into(),
destination: destination.into(),
chapter_titles: HashMap::new(),
__non_exhaustive: (),
}
}
Expand Down