From 2feda84544cb3aea6089594c8b2c17b5583a2b09 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 29 Oct 2026 13:58:52 -0700 Subject: [PATCH 1/3] Add support for showing chapter indices --- .github/workflows/main.yml | 4 +- Cargo.lock | 14 +- book.toml | 6 +- src/appendices.md | 2 + src/lexical-structure.md | 2 +- src/type-system.md | 2 + tools/mdbook-chapter-index/Cargo.toml | 17 ++ tools/mdbook-chapter-index/src/lib.rs | 207 ++++++++++++++++ tools/mdbook-chapter-index/src/main.rs | 24 ++ tools/mdbook-chapter-index/tests/usage.rs | 280 ++++++++++++++++++++++ 10 files changed, 553 insertions(+), 5 deletions(-) create mode 100644 tools/mdbook-chapter-index/Cargo.toml create mode 100644 tools/mdbook-chapter-index/src/lib.rs create mode 100644 tools/mdbook-chapter-index/src/main.rs create mode 100644 tools/mdbook-chapter-index/tests/usage.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1789e82235..d4b7432ddb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -107,7 +107,7 @@ jobs: - name: Install Rust nightly run: | rustup set profile minimal - rustup toolchain install nightly -c rustc-dev -c llvm-tools + rustup toolchain install nightly -c rustc-dev -c llvm-tools -c clippy rustup default nightly - name: Report versions run: | @@ -117,6 +117,8 @@ jobs: run: cargo update -p mdbook-spec --locked - name: Test tools run: cargo test --workspace --exclude grammar-check + - name: Check clippy (mdbook-chapter-index only) + run: cargo clippy -p mdbook-chapter-index --all-targets -- -Dwarnings dev-guide: name: dev-guide build check diff --git a/Cargo.lock b/Cargo.lock index 47e2054d73..5c176ac48d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "mdbook-chapter-index" +version = "0.1.0" +dependencies = [ + "mdbook-preprocessor", + "pathdiff", + "serde_json", + "toml", +] + [[package]] name = "mdbook-core" version = "0.5.4" @@ -665,9 +675,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", diff --git a/book.toml b/book.toml index b792c714be..396f948e46 100644 --- a/book.toml +++ b/book.toml @@ -100,5 +100,9 @@ edition = "2024" [preprocessor.spec] command = "cargo run --release --manifest-path tools/mdbook-spec/Cargo.toml" +[preprocessor.chapter-index] +command = "cargo run --release --manifest-path tools/mdbook-chapter-index/Cargo.toml" +headings = { Appendices = "Appendices" } + [build] -extra-watch-dirs = ["tools/mdbook-spec/src", "tools/grammar/src"] +extra-watch-dirs = ["tools/mdbook-spec/src", "tools/grammar/src", "tools/mdbook-chapter-summary/src"] diff --git a/src/appendices.md b/src/appendices.md index 28acb81ce3..65faae3e6c 100644 --- a/src/appendices.md +++ b/src/appendices.md @@ -1 +1,3 @@ # Appendices + + diff --git a/src/lexical-structure.md b/src/lexical-structure.md index d70e97ac31..c95f1dc79a 100644 --- a/src/lexical-structure.md +++ b/src/lexical-structure.md @@ -1,3 +1,3 @@ # Lexical structure - + diff --git a/src/type-system.md b/src/type-system.md index bed7f128e5..37c93e9c60 100644 --- a/src/type-system.md +++ b/src/type-system.md @@ -1 +1,3 @@ # Type system + + diff --git a/tools/mdbook-chapter-index/Cargo.toml b/tools/mdbook-chapter-index/Cargo.toml new file mode 100644 index 0000000000..91f43f8a67 --- /dev/null +++ b/tools/mdbook-chapter-index/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mdbook-chapter-index" +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" +description = "mdBook preprocessor to include chapter indices with links." +repository = "https://github.com/rust-lang/reference/" +publish = false + +[dependencies] +mdbook-preprocessor = "0.5.4" +pathdiff = "0.2.3" +serde_json = "1.0.154" +toml = "1.1" + +[lints.clippy] +pedantic = { level = "warn", priority = -1 } diff --git a/tools/mdbook-chapter-index/src/lib.rs b/tools/mdbook-chapter-index/src/lib.rs new file mode 100644 index 0000000000..c64b9c16aa --- /dev/null +++ b/tools/mdbook-chapter-index/src/lib.rs @@ -0,0 +1,207 @@ +//! Preprocessor to add chapter indices to chapter pages. + +use std::io; +use std::path::PathBuf; + +use mdbook_preprocessor::book::{Book, BookItem, Chapter}; +use mdbook_preprocessor::config::Config; +use mdbook_preprocessor::errors::Result as PreprocessorResult; +use mdbook_preprocessor::{MDBOOK_VERSION, Preprocessor, PreprocessorContext, parse_input}; + +use pathdiff::diff_paths; + +/// Marker to look for and replace in chapter pages. +const ADD_INDEX_MARKER: &str = ""; + +/// A page in a chapter, which chapter it is in should be tracked separately. +struct ChapterPage { + /// Chapter name, to display, including number if applicable. + name: String, + /// Chapter path, to link. + path: PathBuf, +} + +/// Overall hierarchy of chapters. +enum ChapterHierarchy { + /// This chapter has no sub-chapters. + Leaf(ChapterPage), + /// This chapter has sub-chapters. + Nested(ChapterPage, Vec), +} + +impl ChapterHierarchy { + /// Convert an mdbook chapter into its hierarchy, ignoring draft chapters. + fn from_mdbook_chapter(chapter: &Chapter) -> Option { + let Some(ref path) = chapter.path else { + // This is a draft chapter. + return None; + }; + let current_page = ChapterPage { + name: chapter.to_string(), + path: path.clone(), + }; + let inner: Vec = chapter + .sub_items + .iter() + .filter_map(|item| match item { + BookItem::Chapter(inner_chapter) => Some(inner_chapter), + _ => None, + }) + .filter_map(ChapterHierarchy::from_mdbook_chapter) + .collect(); + if inner.is_empty() { + Some(ChapterHierarchy::Leaf(current_page)) + } else { + Some(ChapterHierarchy::Nested(current_page, inner)) + } + } + + /// Get the markdown to represent the current hierarchy. + /// + /// The markdown list is nested four spaces for each level of `nesting`. + /// + /// Links to chapters are created relative to the provided `root`. + fn to_markdown_links(&self, nesting: usize, root: &PathBuf) -> String { + let current_indent = " ".repeat(nesting); + match self { + ChapterHierarchy::Leaf(current_page) => { + let Some(relative_path) = diff_paths(¤t_page.path, root) else { + // Something went wrong + return String::new(); + }; + format!( + "{current_indent}* [{display}]({link})\n", + display = current_page.name, + link = relative_path.display() + ) + } + ChapterHierarchy::Nested(current_page, inner) => { + let Some(relative_path) = diff_paths(¤t_page.path, root) else { + // Something went wrong + return String::new(); + }; + let inner_display: String = inner + .iter() + .map(|hierarchy| hierarchy.to_markdown_links(nesting + 1, root)) + .collect(); + if inner_display.is_empty() { + // Something went wrong - don't add the extra colon if there + // is no nested list. + return format!( + "{current_indent}* [{display}]({link})\n", + display = current_page.name, + link = relative_path.display() + ); + } + format!( + "{current_indent}* [{display}]({link}):\n{inner_display}", + display = current_page.name, + link = relative_path.display() + ) + } + } + } +} + +/// Process the chapter indices using the [`ChapterIndices`] preprocessor. +/// +/// # Errors +/// +/// Errors come from one of +/// +/// - [`mdbook_preprocessor::parse_input()`][mdbook_preprocessor::parse_input] +/// - [`ChapterIndices::run()`][ChapterIndices::run] +/// - [`serde_json::to_writer()`][serde_json::to_writer] +pub fn process_chapter_indices() -> PreprocessorResult<()> { + let preprocessor = ChapterIndices; + let (context, book) = parse_input(io::stdin())?; + + if context.mdbook_version != MDBOOK_VERSION { + eprintln!( + "Warning: The {} plugin was built against version {} of mdbook, \ + but is being called from version {}", + preprocessor.name(), + MDBOOK_VERSION, + context.mdbook_version + ); + } + + let processed_book = preprocessor.run(&context, book)?; + serde_json::to_writer(io::stdout(), &processed_book)?; + + Ok(()) +} + +/// Check the configuration for a custom header to be configured for the chapter +/// index. +/// +/// The `headings` configuration can be: +/// - a string to use for all headings +/// - a table mapping chapter names to the string to use +/// +/// anything else is ignored and results in the default. +fn get_configured_header(config: &Config, chapter_name: &String) -> Option { + let value: toml::Value = config.get("preprocessor.chapter-index.headings").ok()??; + match value { + toml::Value::String(fixed_header) => Some(fixed_header), + toml::Value::Table(custom_headers) => match custom_headers.get(chapter_name)? { + toml::Value::String(chapter_header) => Some(chapter_header.to_owned()), + _ => None, + }, + _ => None, + } +} + +/// Implementation of the preprocessor. +#[derive(Default, Copy, Clone, Debug)] +#[non_exhaustive] +pub struct ChapterIndices; + +impl ChapterIndices { + /// Construct a new instance of the preprocessor. + #[must_use] + pub fn new() -> Self { + ChapterIndices + } + + /// Handle a single chapter in the book, updating its content if requested. + fn process_chapter(config: &Config, chapter: &mut Chapter) { + let Some(ref root_path) = chapter.path else { + // Draft chapter. + return; + }; + if !chapter.content.contains(ADD_INDEX_MARKER) { + return; + } + let mut index: String = chapter + .sub_items + .iter() + .filter_map(|item| match item { + BookItem::Chapter(inner_chapter) => Some(inner_chapter), + _ => None, + }) + .filter_map(ChapterHierarchy::from_mdbook_chapter) + .map(|hierarchy| hierarchy.to_markdown_links(0, root_path)) + .collect(); + if !index.is_empty() { + // Support for custom headings for different pages + let index_header = match get_configured_header(config, &chapter.name) { + Some(header) => header, + None => "Chapter contents".to_owned(), + }; + index = format!("{index_header}:\n{index}"); + } + chapter.content = chapter.content.replace(ADD_INDEX_MARKER, &index); + } +} + +impl Preprocessor for ChapterIndices { + fn name(&self) -> &'static str { + "mdbook-chapter-index" + } + + fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> PreprocessorResult { + book.for_each_chapter_mut(|chapter| Self::process_chapter(&ctx.config, chapter)); + Ok(book) + } +} diff --git a/tools/mdbook-chapter-index/src/main.rs b/tools/mdbook-chapter-index/src/main.rs new file mode 100644 index 0000000000..e89664bf40 --- /dev/null +++ b/tools/mdbook-chapter-index/src/main.rs @@ -0,0 +1,24 @@ +//! Preprocessor to add chapter indices to chapter pages. + +use std::env; +use std::error::Error; + +use mdbook_preprocessor::errors::Error as PreprocessorError; +use mdbook_preprocessor::errors::Result as PreprocessorResult; + +fn main() -> PreprocessorResult<()> { + // Skip index 0 which is the path + let mut args = env::args().skip(1); + if let Some(command) = args.next() { + return match command.as_str() { + "supports" => Ok(()), + unknown => { + let error: Box = + format!("Unknown_command: {unknown}").into(); + Err(PreprocessorError::from_boxed(error)) + } + }; + } + + mdbook_chapter_index::process_chapter_indices() +} diff --git a/tools/mdbook-chapter-index/tests/usage.rs b/tools/mdbook-chapter-index/tests/usage.rs new file mode 100644 index 0000000000..63d07daf96 --- /dev/null +++ b/tools/mdbook-chapter-index/tests/usage.rs @@ -0,0 +1,280 @@ +//! Usage tests for the preprocessor. + +use mdbook_chapter_index::ChapterIndices; +use mdbook_preprocessor::book::{Book, BookItem, Chapter}; +use mdbook_preprocessor::{Preprocessor as _, PreprocessorContext, config::Config}; +use std::path::PathBuf; + +macro_rules! make_context { + ( $( $key:literal => $value:literal ),* $(,)? ) => {{ + let mut context = PreprocessorContext::new( + PathBuf::from("/path/to/book"), + Config::default(), + "test".to_owned(), + ); + context.config.set("preprocessor.chapter-index", "{}") + .expect("Able to set preprocessor.chapter-index"); + $( + context.config.set($key, $value).expect(concat!("Able to set ", $key)); + )* + context + }} +} + +macro_rules! make_chapter { + ($name:literal, $content:literal, $parents:tt) => { + make_chapter!($name, $content, path = concat!("/path/to/book/", $name, ".md"), $parents) + }; + ($name:literal, $content:literal, path = $path:expr, [ $( $parent:literal ),* ]) => { + Chapter::new( + $name, + $content.to_owned(), + $path, + vec![ + $( $parent.to_owned(), )* + ] + ) + }; +} + +fn check_preprocessor(ctx: &PreprocessorContext, original: Vec, expected: Vec) { + let original_book = Book::new_with_items(original.into_iter().map(BookItem::Chapter).collect()); + + let result = ChapterIndices::new().run(ctx, original_book); + + let actual_book = result.expect("ChapterIndices::run() should succeed"); + let expected_book = Book::new_with_items(expected.into_iter().map(BookItem::Chapter).collect()); + assert_eq!(expected_book, actual_book, "Books should match"); +} + +#[test] +fn no_change() { + let context = make_context!(); + let chapters = vec![make_chapter!("MAIN_PAGE", "# Main page", [])]; + // No change + check_preprocessor(&context, chapters.clone(), chapters); +} + +#[test] +fn add_index() { + let context = make_context!(); + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(make_chapter!("Foo", "# Foo", ["Examples"])), + BookItem::Chapter(make_chapter!("Bar", "# Bar", ["Examples"])), + ]; + + let mut expected = examples.clone(); + expected.content = concat!( + "# Examples\n\n", + "Chapter contents:\n", + "* [Foo](../Foo.md)\n", + "* [Bar](../Bar.md)\n" + ) + .to_owned(); + + check_preprocessor(&context, vec![examples], vec![expected]); +} + +#[test] +fn skip_draft() { + let context = make_context!(); + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(make_chapter!("Foo", "# Foo", ["Examples"])), + BookItem::Chapter(make_chapter!("Bar", "# Bar", ["Examples"])), + BookItem::Chapter(Chapter::new_draft("Baz", vec!["Examples".to_owned()])), + ]; + + let mut expected = examples.clone(); + expected.content = concat!( + "# Examples\n\n", + "Chapter contents:\n", + "* [Foo](../Foo.md)\n", + "* [Bar](../Bar.md)\n" + ) + .to_owned(); + + check_preprocessor(&context, vec![examples], vec![expected]); +} + +#[test] +fn only_drafts() { + let context = make_context!(); + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(Chapter::new_draft("Foo", vec!["Examples".to_owned()])), + BookItem::Chapter(Chapter::new_draft("Bar", vec!["Examples".to_owned()])), + BookItem::Chapter(Chapter::new_draft("Baz", vec!["Examples".to_owned()])), + ]; + + let mut expected = examples.clone(); + expected.content = "# Examples\n\n".to_owned(); + + check_preprocessor(&context, vec![examples], vec![expected]); +} + +#[test] +fn nested_chapters() { + let context = make_context!(); + let mut numbers = make_chapter!("Numbers", "# Numbers", ["Examples"]); + numbers.sub_items = vec![ + BookItem::Chapter(make_chapter!("One", "# One", ["Examples", "Numbers"])), + BookItem::Chapter(make_chapter!("Two", "# Two", ["Examples", "Numbers"])), + ]; + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(make_chapter!("Foo", "# Foo", ["Examples"])), + BookItem::Chapter(make_chapter!("Bar", "# Bar", ["Examples"])), + BookItem::Chapter(numbers), + ]; + + let mut expected = examples.clone(); + expected.content = concat!( + "# Examples\n\n", + "Chapter contents:\n", + "* [Foo](../Foo.md)\n", + "* [Bar](../Bar.md)\n", + "* [Numbers](../Numbers.md):\n", + " * [One](../One.md)\n", + " * [Two](../Two.md)\n", + ) + .to_owned(); + + check_preprocessor(&context, vec![examples], vec![expected]); +} + +#[test] +fn weird_paths() { + let context = make_context!(); + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(make_chapter!( + "Foo", + "# Foo", + path = "/some/absolute/path/Foo.md", + ["Examples"] + )), + BookItem::Chapter(make_chapter!( + "Bar", + "# Bar", + path = "/path/to/next-to-book/Bar.md", + ["Examples"] + )), + ]; + + let mut expected = examples.clone(); + expected.content = concat!( + "# Examples\n\n", + "Chapter contents:\n", + "* [Foo](../../../../some/absolute/path/Foo.md)\n", + "* [Bar](../../next-to-book/Bar.md)\n", + ) + .to_owned(); + + check_preprocessor(&context, vec![examples], vec![expected]); +} + +#[test] +fn custom_header() { + let context = make_context!("preprocessor.chapter-index.headings" => "Example pages"); + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(make_chapter!("Foo", "# Foo", ["Examples"])), + BookItem::Chapter(make_chapter!("Bar", "# Bar", ["Examples"])), + ]; + + let mut expected = examples.clone(); + expected.content = concat!( + "# Examples\n\n", + "Example pages:\n", + "* [Foo](../Foo.md)\n", + "* [Bar](../Bar.md)\n", + ) + .to_owned(); + + check_preprocessor(&context, vec![examples], vec![expected]); +} + +#[test] +fn different_headers() { + let context = make_context!( + "preprocessor.chapter-index.headings" => "{}", + "preprocessor.chapter-index.headings.Examples" => "Example pages", + "preprocessor.chapter-index.headings.Numbers" => "Some numbers", + ); + + let mut examples = make_chapter!( + "Examples", + "# Examples\n\n", + [] + ); + examples.sub_items = vec![ + BookItem::Chapter(make_chapter!("Foo", "# Foo", ["Examples"])), + BookItem::Chapter(make_chapter!("Bar", "# Bar", ["Examples"])), + ]; + + let mut numbers = make_chapter!( + "Numbers", + "# Numbers\n\n", + [] + ); + numbers.sub_items = vec![ + BookItem::Chapter(make_chapter!("One", "# One", ["Numbers"])), + BookItem::Chapter(make_chapter!("Two", "# Two", ["Numbers"])), + ]; + + let mut expected_examples = examples.clone(); + expected_examples.content = concat!( + "# Examples\n\n", + "Example pages:\n", + "* [Foo](../Foo.md)\n", + "* [Bar](../Bar.md)\n", + ) + .to_owned(); + + let mut expected_numbers = numbers.clone(); + expected_numbers.content = concat!( + "# Numbers\n\n", + "Some numbers:\n", + "* [One](../One.md)\n", + "* [Two](../Two.md)\n", + ) + .to_owned(); + + check_preprocessor( + &context, + vec![examples, numbers], + vec![expected_examples, expected_numbers], + ); +} From 32cc34b1fdcf034c36039cee1139f758db458520 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 29 Oct 2026 14:31:45 -0700 Subject: [PATCH 2/3] Relative to parent directory --- tools/mdbook-chapter-index/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/mdbook-chapter-index/src/lib.rs b/tools/mdbook-chapter-index/src/lib.rs index c64b9c16aa..d5d0816c9d 100644 --- a/tools/mdbook-chapter-index/src/lib.rs +++ b/tools/mdbook-chapter-index/src/lib.rs @@ -1,7 +1,7 @@ //! Preprocessor to add chapter indices to chapter pages. use std::io; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use mdbook_preprocessor::book::{Book, BookItem, Chapter}; use mdbook_preprocessor::config::Config; @@ -61,7 +61,7 @@ impl ChapterHierarchy { /// The markdown list is nested four spaces for each level of `nesting`. /// /// Links to chapters are created relative to the provided `root`. - fn to_markdown_links(&self, nesting: usize, root: &PathBuf) -> String { + fn to_markdown_links(&self, nesting: usize, root: &Path) -> String { let current_indent = " ".repeat(nesting); match self { ChapterHierarchy::Leaf(current_page) => { @@ -170,6 +170,10 @@ impl ChapterIndices { // Draft chapter. return; }; + let Some(root_directory) = root_path.parent() else { + // Something weird is going on... + return; + }; if !chapter.content.contains(ADD_INDEX_MARKER) { return; } @@ -181,7 +185,7 @@ impl ChapterIndices { _ => None, }) .filter_map(ChapterHierarchy::from_mdbook_chapter) - .map(|hierarchy| hierarchy.to_markdown_links(0, root_path)) + .map(|hierarchy| hierarchy.to_markdown_links(0, root_directory)) .collect(); if !index.is_empty() { // Support for custom headings for different pages From 677b7ff9e18a410b592dfe3e92cfe8c15c4a7968 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 29 Oct 2026 14:36:48 -0700 Subject: [PATCH 3/3] Update tests --- tools/mdbook-chapter-index/tests/usage.rs | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/mdbook-chapter-index/tests/usage.rs b/tools/mdbook-chapter-index/tests/usage.rs index 63d07daf96..62bc5620e3 100644 --- a/tools/mdbook-chapter-index/tests/usage.rs +++ b/tools/mdbook-chapter-index/tests/usage.rs @@ -73,8 +73,8 @@ fn add_index() { expected.content = concat!( "# Examples\n\n", "Chapter contents:\n", - "* [Foo](../Foo.md)\n", - "* [Bar](../Bar.md)\n" + "* [Foo](Foo.md)\n", + "* [Bar](Bar.md)\n" ) .to_owned(); @@ -100,8 +100,8 @@ fn skip_draft() { expected.content = concat!( "# Examples\n\n", "Chapter contents:\n", - "* [Foo](../Foo.md)\n", - "* [Bar](../Bar.md)\n" + "* [Foo](Foo.md)\n", + "* [Bar](Bar.md)\n" ) .to_owned(); @@ -153,11 +153,11 @@ fn nested_chapters() { expected.content = concat!( "# Examples\n\n", "Chapter contents:\n", - "* [Foo](../Foo.md)\n", - "* [Bar](../Bar.md)\n", - "* [Numbers](../Numbers.md):\n", - " * [One](../One.md)\n", - " * [Two](../Two.md)\n", + "* [Foo](Foo.md)\n", + "* [Bar](Bar.md)\n", + "* [Numbers](Numbers.md):\n", + " * [One](One.md)\n", + " * [Two](Two.md)\n", ) .to_owned(); @@ -192,8 +192,8 @@ fn weird_paths() { expected.content = concat!( "# Examples\n\n", "Chapter contents:\n", - "* [Foo](../../../../some/absolute/path/Foo.md)\n", - "* [Bar](../../next-to-book/Bar.md)\n", + "* [Foo](../../../some/absolute/path/Foo.md)\n", + "* [Bar](../next-to-book/Bar.md)\n", ) .to_owned(); @@ -218,8 +218,8 @@ fn custom_header() { expected.content = concat!( "# Examples\n\n", "Example pages:\n", - "* [Foo](../Foo.md)\n", - "* [Bar](../Bar.md)\n", + "* [Foo](Foo.md)\n", + "* [Bar](Bar.md)\n", ) .to_owned(); @@ -258,8 +258,8 @@ fn different_headers() { expected_examples.content = concat!( "# Examples\n\n", "Example pages:\n", - "* [Foo](../Foo.md)\n", - "* [Bar](../Bar.md)\n", + "* [Foo](Foo.md)\n", + "* [Bar](Bar.md)\n", ) .to_owned(); @@ -267,8 +267,8 @@ fn different_headers() { expected_numbers.content = concat!( "# Numbers\n\n", "Some numbers:\n", - "* [One](../One.md)\n", - "* [Two](../Two.md)\n", + "* [One](One.md)\n", + "* [Two](Two.md)\n", ) .to_owned();