mirror of https://github.com/rust-lang-ru/book.git
22 changed files with 2600 additions and 171 deletions
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,14 @@
@@ -1,8 +1,14 @@
|
||||
# Sync any changes to this *other than where explicitly specified* with the copy |
||||
# in `nostarch/book.toml`! |
||||
|
||||
[book] |
||||
title = "The Rust Programming Language" |
||||
authors = ["Steve Klabnik", "Carol Nichols", "Contributions from the Rust Community"] |
||||
|
||||
[output.html] |
||||
additional-css = ["ferris.css", "theme/2018-edition.css"] |
||||
additional-css = ["ferris.css", "theme/2018-edition.css", "theme/semantic-notes.css"] |
||||
additional-js = ["ferris.js"] |
||||
git-repository-url = "https://github.com/rust-lang/book" |
||||
|
||||
# Do not sync this preprocessor; it is for the HTML renderer only. |
||||
[preprocessor.trpl-note] |
||||
|
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
[book] |
||||
title = "The Rust Programming Language" |
||||
authors = ["Steve Klabnik", "Carol Nichols", "Contributions from the Rust Community"] |
||||
src = "../src" # needs to be explicit (it is implicit in `/book.toml`). |
||||
|
||||
[output.html] |
||||
additional-css = ["../ferris.css", "../theme/2018-edition.css", "../theme/semantic-notes.css"] |
||||
additional-js = ["../ferris.js"] |
||||
git-repository-url = "https://github.com/rust-lang/book" |
||||
|
||||
[build] |
||||
build-dir = "../tmp" |
||||
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
[package] |
||||
name = "mdbook-trpl-note" |
||||
version = "1.0.0" |
||||
edition = "2021" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
clap = { version = "4.5.4", features = ["derive"] } |
||||
mdbook = { workspace = true } |
||||
pulldown-cmark = { version = "0.10.3", features = ["simd"] } |
||||
pulldown-cmark-to-cmark = "13.0.0" |
||||
serde_json = "1.0.116" |
||||
|
||||
[dev-dependencies] |
||||
assert_cmd = "2.0.14" |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
# mdbook-trpl-note |
||||
|
||||
This is a *very* simple [preprocessor][pre] for [mdBook][mdbook], focused specifically on the content of _The Rust Programming Language_ book. This preprocessor takes Markdown like this— |
||||
|
||||
```markdown |
||||
> Note: This is some material we want to provide more emphasis for, because it |
||||
> is important in some way! |
||||
|
||||
Some text. |
||||
|
||||
> ## Some subject |
||||
> |
||||
> Here is all the important things to know about that particular subject. |
||||
``` |
||||
|
||||
—and rewrites the Markdown to this: |
||||
|
||||
```html |
||||
<section class="note" aria-role="note"> |
||||
|
||||
This is some material we want to provide more emphasis for, because it is |
||||
important in some way! |
||||
|
||||
</section> |
||||
|
||||
Some text. |
||||
|
||||
<section class="note" aria-role="note"> |
||||
|
||||
## Some subject |
||||
|
||||
Here is all the important things to know about that particular subject. |
||||
|
||||
</section> |
||||
``` |
||||
|
||||
This allows using the relatively standard Markdown convention of (incorrectly!) using blockquotes for “callouts” or “notes” like this, while still producing semantic HTML which conveys the actual intent. |
||||
|
||||
> [!NOTE] |
||||
> This is *not* a full “admonition” preprocessor, and it is not remotely compliant with [the GitHub “alert” syntax][alerts]. It exists almost entirely for the sake of providing better semantic HTML for _The Rust Programming Language_ book with a minimum of disruption to existing workflows! |
||||
> |
||||
> You are probably better off using one of the other existing alert/admonition preprocessors: |
||||
> |
||||
> - [mdbook-alerts][mdbook-alerts] |
||||
> - [mdbook-admonish][mdbook-admonish] |
||||
|
||||
[pre]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html |
||||
[mdbook]: https://github.com/rust-lang/mdBook |
||||
[alerts]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts |
||||
[mdbook-alerts]: https://github.com/lambdalisue/rs-mdbook-alerts |
||||
[mdbook-admonish]: https://github.com/tommilligan/mdbook-admonish |
||||
@ -0,0 +1,285 @@
@@ -0,0 +1,285 @@
|
||||
use mdbook::{ |
||||
book::Book, |
||||
errors::Result, |
||||
preprocess::{Preprocessor, PreprocessorContext}, |
||||
BookItem, |
||||
}; |
||||
use pulldown_cmark::{ |
||||
Event::{self, *}, |
||||
Parser, Tag, TagEnd, |
||||
}; |
||||
use pulldown_cmark_to_cmark::cmark; |
||||
|
||||
/// A simple preprocessor for semantic notes in _The Rust Programming Language_.
|
||||
///
|
||||
/// Takes in Markdown like this:
|
||||
///
|
||||
/// ```markdown
|
||||
/// > Note: This is a note.
|
||||
/// ```
|
||||
///
|
||||
/// Spits out Markdown like this:
|
||||
///
|
||||
/// ```markdown
|
||||
/// <section class="note" aria-role="note">
|
||||
///
|
||||
/// This is a note.
|
||||
///
|
||||
/// </section>
|
||||
/// ```
|
||||
pub struct TrplNote; |
||||
|
||||
impl Preprocessor for TrplNote { |
||||
fn name(&self) -> &str { |
||||
"simple-note-preprocessor" |
||||
} |
||||
|
||||
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<mdbook::book::Book> { |
||||
book.for_each_mut(|item| { |
||||
if let BookItem::Chapter(ref mut chapter) = item { |
||||
chapter.content = rewrite(&chapter.content); |
||||
} |
||||
}); |
||||
Ok(book) |
||||
} |
||||
|
||||
fn supports_renderer(&self, renderer: &str) -> bool { |
||||
renderer == "html" |
||||
} |
||||
} |
||||
|
||||
pub fn rewrite(text: &str) -> String { |
||||
let parser = Parser::new(text); |
||||
|
||||
let mut events = Vec::new(); |
||||
let mut state = Default; |
||||
|
||||
for event in parser { |
||||
match (&mut state, event) { |
||||
(Default, Start(Tag::BlockQuote)) => { |
||||
state = StartingBlockquote(vec![Start(Tag::BlockQuote)]); |
||||
} |
||||
|
||||
(StartingBlockquote(blockquote_events), Text(content)) => { |
||||
if content.starts_with("Note: ") { |
||||
// This needs the "extra" `SoftBreak`s so that when the final rendering pass
|
||||
// happens, it does not end up treating the internal content as inline *or*
|
||||
// treating the HTML tags as inline tags:
|
||||
//
|
||||
// - Content inside HTML blocks is only rendered as Markdown when it is
|
||||
// separated from the block HTML elements: otherwise it gets treated as inline
|
||||
// HTML and *not* rendered.
|
||||
// - Along the same lines, an HTML tag that happens to be directly adjacent to
|
||||
// the end of a previous Markdown block will end up being rendered as part of
|
||||
// that block.
|
||||
events.extend([ |
||||
SoftBreak, |
||||
SoftBreak, |
||||
Html(r#"<section class="note" aria-role="note">"#.into()), |
||||
SoftBreak, |
||||
SoftBreak, |
||||
Start(Tag::Paragraph), |
||||
Text(content), |
||||
]); |
||||
state = InNote; |
||||
} else { |
||||
events.append(blockquote_events); |
||||
events.push(Text(content)); |
||||
state = Default; |
||||
} |
||||
} |
||||
|
||||
(StartingBlockquote(_blockquote_events), heading @ Start(Tag::Heading { .. })) => { |
||||
events.extend([ |
||||
SoftBreak, |
||||
SoftBreak, |
||||
Html(r#"<section class="note" aria-role="note">"#.into()), |
||||
SoftBreak, |
||||
SoftBreak, |
||||
heading, |
||||
]); |
||||
state = InNote; |
||||
} |
||||
|
||||
(StartingBlockquote(ref mut events), Start(Tag::Paragraph)) => { |
||||
events.push(Start(Tag::Paragraph)); |
||||
} |
||||
|
||||
(InNote, End(TagEnd::BlockQuote)) => { |
||||
// As with the start of the block HTML, the closing HTML must be
|
||||
// separated from the Markdown text by two newlines.
|
||||
events.extend([SoftBreak, SoftBreak, Html("</section>".into())]); |
||||
state = Default; |
||||
} |
||||
|
||||
(_, event) => { |
||||
events.push(event); |
||||
} |
||||
} |
||||
} |
||||
|
||||
let mut buf = String::new(); |
||||
cmark(events.into_iter(), &mut buf).unwrap(); |
||||
buf |
||||
} |
||||
|
||||
use State::*; |
||||
|
||||
#[derive(Debug)] |
||||
enum State<'e> { |
||||
Default, |
||||
StartingBlockquote(Vec<Event<'e>>), |
||||
InNote, |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
|
||||
#[test] |
||||
fn no_note() { |
||||
let text = "Hello, world.\n\nThis is some text."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<p>Hello, world.</p>\n<p>This is some text.</p>\n" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_note() { |
||||
let text = "> Note: This is some text.\n> It keeps going."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<p>Note: This is some text.\nIt keeps going.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn regular_blockquote() { |
||||
let text = "> This is some text.\n> It keeps going."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<blockquote>\n<p>This is some text.\nIt keeps going.</p>\n</blockquote>\n" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn combined() { |
||||
let text = "> Note: This is some text.\n> It keeps going.\n\nThis is regular text.\n\n> This is a blockquote.\n"; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<p>Note: This is some text.\nIt keeps going.</p>\n</section>\n<p>This is regular text.</p>\n<blockquote>\n<p>This is a blockquote.</p>\n</blockquote>\n" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn blockquote_then_note() { |
||||
let text = "> This is quoted.\n\n> Note: This is noted."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<blockquote>\n<p>This is quoted.</p>\n</blockquote>\n<section class=\"note\" aria-role=\"note\">\n<p>Note: This is noted.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn note_then_blockquote() { |
||||
let text = "> Note: This is noted.\n\n> This is quoted."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<p>Note: This is noted.</p>\n</section>\n<blockquote>\n<p>This is quoted.</p>\n</blockquote>\n" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_h1_note() { |
||||
let text = "> # Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h1>Header</h1>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_h2_note() { |
||||
let text = "> ## Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h2>Header</h2>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_h3_note() { |
||||
let text = "> ### Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h3>Header</h3>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_h4_note() { |
||||
let text = "> #### Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h4>Header</h4>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_h5_note() { |
||||
let text = "> ##### Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h5>Header</h5>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn with_h6_note() { |
||||
let text = "> ###### Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h6>Header</h6>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn h1_then_blockquote() { |
||||
let text = "> # Header\n > And then some note content.\n\n> This is quoted."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<section class=\"note\" aria-role=\"note\">\n<h1>Header</h1>\n<p>And then some note content.</p>\n</section>\n<blockquote>\n<p>This is quoted.</p>\n</blockquote>\n" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn blockquote_then_h1_note() { |
||||
let text = "> This is quoted.\n\n> # Header\n > And then some note content."; |
||||
let processed = rewrite(text); |
||||
assert_eq!( |
||||
render_markdown(&processed), |
||||
"<blockquote>\n<p>This is quoted.</p>\n</blockquote>\n<section class=\"note\" aria-role=\"note\">\n<h1>Header</h1>\n<p>And then some note content.</p>\n</section>" |
||||
); |
||||
} |
||||
|
||||
fn render_markdown(text: &str) -> String { |
||||
let parser = Parser::new(text); |
||||
let mut buf = String::new(); |
||||
pulldown_cmark::html::push_html(&mut buf, parser); |
||||
buf |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
use std::io; |
||||
|
||||
use clap::{self, Parser, Subcommand}; |
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; |
||||
|
||||
use mdbook_trpl_note::TrplNote; |
||||
|
||||
fn main() -> Result<(), String> { |
||||
let cli = Cli::parse(); |
||||
let simple_note = TrplNote; |
||||
if let Some(Command::Supports { renderer }) = cli.command { |
||||
return if simple_note.supports_renderer(&renderer) { |
||||
Ok(()) |
||||
} else { |
||||
Err(format!("Renderer '{renderer}' is unsupported")) |
||||
}; |
||||
} |
||||
|
||||
let (ctx, book) = |
||||
CmdPreprocessor::parse_input(io::stdin()).map_err(|e| format!("blah: {e}"))?; |
||||
let processed = simple_note.run(&ctx, book).map_err(|e| format!("{e}"))?; |
||||
serde_json::to_writer(io::stdout(), &processed).map_err(|e| format!("{e}")) |
||||
} |
||||
|
||||
/// A simple preprocessor for semantic notes in _The Rust Programming Language_.
|
||||
#[derive(Parser, Debug)] |
||||
struct Cli { |
||||
#[command(subcommand)] |
||||
command: Option<Command>, |
||||
} |
||||
|
||||
#[derive(Subcommand, Debug)] |
||||
enum Command { |
||||
/// Is the renderer supported?
|
||||
///
|
||||
/// All renderers are supported! This is the contract for mdBook.
|
||||
Supports { renderer: String }, |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
use assert_cmd::Command; |
||||
|
||||
#[test] |
||||
fn supports_html_renderer() { |
||||
let cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")) |
||||
.unwrap() |
||||
.args(["supports", "html"]) |
||||
.ok(); |
||||
assert!(cmd.is_ok()); |
||||
} |
||||
|
||||
#[test] |
||||
fn errors_for_other_renderers() { |
||||
let cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")) |
||||
.unwrap() |
||||
.args(["supports", "total-nonsense"]) |
||||
.ok(); |
||||
assert!(cmd.is_err()); |
||||
} |
||||
|
||||
// It would be nice to add an actual fixture for an mdbook, but doing *that* is
|
||||
// going to be a bit of a pain, and what I have should cover it for now.
|
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
[package] |
||||
name = "rust-book-tools" |
||||
version = "0.0.1" |
||||
description = "The Rust Book" |
||||
edition = "2021" |
||||
|
||||
[[bin]] |
||||
name = "concat_chapters" |
||||
path = "src/bin/concat_chapters.rs" |
||||
|
||||
[[bin]] |
||||
name = "convert_quotes" |
||||
path = "src/bin/convert_quotes.rs" |
||||
|
||||
[[bin]] |
||||
name = "lfp" |
||||
path = "src/bin/lfp.rs" |
||||
|
||||
[[bin]] |
||||
name = "link2print" |
||||
path = "src/bin/link2print.rs" |
||||
|
||||
[[bin]] |
||||
name = "release_listings" |
||||
path = "src/bin/release_listings.rs" |
||||
|
||||
[[bin]] |
||||
name = "remove_hidden_lines" |
||||
path = "src/bin/remove_hidden_lines.rs" |
||||
|
||||
[[bin]] |
||||
name = "remove_links" |
||||
path = "src/bin/remove_links.rs" |
||||
|
||||
[[bin]] |
||||
name = "remove_markup" |
||||
path = "src/bin/remove_markup.rs" |
||||
|
||||
[dependencies] |
||||
walkdir = { workspace = true } |
||||
docopt = { workspace = true } |
||||
serde = { workspace = true } |
||||
regex = { workspace = true } |
||||
lazy_static = { workspace = true } |
||||
flate2 = { workspace = true } |
||||
tar = { workspace = true } |
||||
@ -1,11 +1,12 @@
@@ -1,11 +1,12 @@
|
||||
// We have some long regex literals, so:
|
||||
// ignore-tidy-linelength
|
||||
|
||||
use docopt::Docopt; |
||||
use serde::Deserialize; |
||||
use std::io::BufRead; |
||||
use std::{fs, io, path}; |
||||
|
||||
use docopt::Docopt; |
||||
use serde::Deserialize; |
||||
|
||||
fn main() { |
||||
let args: Args = Docopt::new(USAGE) |
||||
.and_then(|d| d.deserialize()) |
||||
@ -1,11 +1,12 @@
@@ -1,11 +1,12 @@
|
||||
// FIXME: we have some long lines that could be refactored, but it's not a big deal.
|
||||
// ignore-tidy-linelength
|
||||
|
||||
use regex::{Captures, Regex}; |
||||
use std::collections::HashMap; |
||||
use std::io; |
||||
use std::io::Read; |
||||
|
||||
use regex::{Captures, Regex}; |
||||
|
||||
fn main() { |
||||
write_md(parse_links(parse_references(read_md()))); |
||||
} |
||||
@ -1,5 +1,3 @@
@@ -1,5 +1,3 @@
|
||||
extern crate regex; |
||||
|
||||
use regex::{Captures, Regex}; |
||||
use std::collections::HashSet; |
||||
use std::io; |
||||
@ -1,9 +1,8 @@
@@ -1,9 +1,8 @@
|
||||
extern crate regex; |
||||
|
||||
use regex::{Captures, Regex}; |
||||
use std::io; |
||||
use std::io::Read; |
||||
|
||||
use regex::{Captures, Regex}; |
||||
|
||||
fn main() { |
||||
write_md(remove_markup(read_md())); |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
/* |
||||
This is copied directly from the styles for blockquotes, because notes were |
||||
historically rendered *as* blockquotes. This keeps the presentation of them |
||||
identical while updating the presentation. |
||||
*/ |
||||
.note { |
||||
margin: 20px 0; |
||||
padding: 0 20px; |
||||
color: var(--fg); |
||||
background-color: var(--quote-bg); |
||||
border-block-start: 0.1em solid var(--quote-border); |
||||
border-block-end: 0.1em solid var(--quote-border); |
||||
} |
||||
Loading…
Reference in new issue