diff --git a/book.toml b/book.toml index 928a44453f..f702ce29fb 100644 --- a/book.toml +++ b/book.toml @@ -38,6 +38,7 @@ git-repository-url = "https://github.com/rust-lang/book" [preprocessor.trpl-note] [preprocessor.trpl-listing] +[preprocessor.trpl-listing-link] output-mode = "default" [rust] diff --git a/packages/mdbook-trpl/Cargo.lock b/packages/mdbook-trpl/Cargo.lock index e096e4cd67..035bfb0b00 100644 --- a/packages/mdbook-trpl/Cargo.lock +++ b/packages/mdbook-trpl/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -507,6 +507,7 @@ dependencies = [ "mdbook", "pulldown-cmark 0.12.2", "pulldown-cmark-to-cmark", + "regex", "serde_json", "thiserror", "toml 0.8.19", diff --git a/packages/mdbook-trpl/Cargo.toml b/packages/mdbook-trpl/Cargo.toml index 1eea3c426d..a775d21404 100644 --- a/packages/mdbook-trpl/Cargo.toml +++ b/packages/mdbook-trpl/Cargo.toml @@ -11,6 +11,10 @@ path = "src/bin/note.rs" name = "mdbook-trpl-listing" path = "src/bin/listing.rs" +[[bin]] +name = "mdbook-trpl-listing-link" +path = "src/bin/listing_link.rs" + [[bin]] name = "mdbook-trpl-heading" path = "src/bin/heading.rs" @@ -29,6 +33,7 @@ pulldown-cmark-to-cmark = "19" serde_json = "1" thiserror = "1.0.60" toml = "0.8.12" +regex = "1.5" [dev-dependencies] assert_cmd = "2" diff --git a/packages/mdbook-trpl/src/bin/listing_link.rs b/packages/mdbook-trpl/src/bin/listing_link.rs new file mode 100644 index 0000000000..88c6106ed1 --- /dev/null +++ b/packages/mdbook-trpl/src/bin/listing_link.rs @@ -0,0 +1,39 @@ +use std::io; + +use clap::{self, Parser, Subcommand}; +use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; + +use mdbook_trpl::listing_link::ListingLinkPreprocessor; + +fn main() -> Result<(), String> { + let cli = Cli::parse(); + if let Some(Command::Supports { renderer }) = cli.command { + return if ListingLinkPreprocessor.supports_renderer(&renderer) { + Ok(()) + } else { + Err(format!("Renderer '{renderer}' is unsupported")) + }; + } + + let (ctx, book) = CmdPreprocessor::parse_input(io::stdin()) + .map_err(|e| format!("{e}"))?; + let processed = ListingLinkPreprocessor + .run(&ctx, book) + .map_err(|e| format!("{e}"))?; + serde_json::to_writer(io::stdout(), &processed).map_err(|e| format!("{e}")) +} + +/// A simple preprocessor for handling listing links in _The Rust Programming Language_. +#[derive(Parser, Debug)] +struct Cli { + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand, Debug)] +enum Command { + /// Is the renderer supported? + /// + /// All renderers are supported! This is the contract for mdBook. + Supports { renderer: String }, +} diff --git a/packages/mdbook-trpl/src/lib.rs b/packages/mdbook-trpl/src/lib.rs index 2586a8ed7e..bb2fb6a125 100644 --- a/packages/mdbook-trpl/src/lib.rs +++ b/packages/mdbook-trpl/src/lib.rs @@ -2,6 +2,7 @@ mod config; mod figure; mod heading; mod listing; +pub mod listing_link; mod note; pub use config::Mode; diff --git a/packages/mdbook-trpl/src/listing_link/mod.rs b/packages/mdbook-trpl/src/listing_link/mod.rs new file mode 100644 index 0000000000..0579c2f741 --- /dev/null +++ b/packages/mdbook-trpl/src/listing_link/mod.rs @@ -0,0 +1,55 @@ +use mdbook::{ + book::Book, + errors::Result, + preprocess::{Preprocessor, PreprocessorContext}, + BookItem, +}; +use regex::Regex; +use std::collections::HashMap; + +pub struct ListingLinkPreprocessor; + +impl Preprocessor for ListingLinkPreprocessor { + fn name(&self) -> &str { + "trpl-listing-link" + } + + fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result { + let number_re = Regex::new(r#"id="listing-(\d+-\d+)"#).unwrap(); + let listing_re = Regex::new(r"Listing\s(\d+-\d+)").unwrap(); + let mut listings: HashMap = HashMap::new(); + + // Collect all number patterns and their corresponding filenames + book.for_each_mut(|item| { + if let BookItem::Chapter(ref mut chapter) = item { + if let Some(ref path) = chapter.path { + let filename = path.to_string_lossy().to_string(); + for cap in number_re.captures_iter(&chapter.content) { + let number = cap[1].to_string(); + listings.insert(number, filename.clone()); + } + } + } + }); + + // Replace listing references with absolute href with filename and listing number hash + book.for_each_mut(|item| { + if let BookItem::Chapter(ref mut chapter) = item { + chapter.content = listing_re.replace_all(&chapter.content, |caps: ®ex::Captures| { + let listing_number = &caps[1]; + let href = listings.get(listing_number).map_or_else( + || format!("#listing-{}", listing_number), + |file| format!("{}#listing-{}", file, listing_number) + ); + format!(r##"Listing {}"##, href, listing_number) + }).to_string(); + } + }); + + Ok(book) + } + + fn supports_renderer(&self, renderer: &str) -> bool { + renderer == "html" || renderer == "markdown" || renderer == "test" + } +} diff --git a/packages/mdbook-trpl/src/listing_link/tests.rs b/packages/mdbook-trpl/src/listing_link/tests.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ch16-02-message-passing.md b/src/ch16-02-message-passing.md index 7f723e7986..99ff2fcfdb 100644 --- a/src/ch16-02-message-passing.md +++ b/src/ch16-02-message-passing.md @@ -32,14 +32,13 @@ First, in Listing 16-6, we’ll create a channel but not do anything with it. Note that this won’t compile yet because Rust can’t tell what type of values we want to send over the channel. -Filename: src/main.rs + ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-06/src/main.rs}} ``` -Listing 16-6: Creating a channel and assigning the two -halves to `tx` and `rx` + We create a new channel using the `mpsc::channel` function; `mpsc` stands for _multiple producer, single consumer_. In short, the way Rust’s standard library