Skip to content

Add an example admonition #1812

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 6 commits into from
May 6, 2025
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
8 changes: 7 additions & 1 deletion docs/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,15 @@ Admonitions use a style similar to GitHub-flavored markdown, where the style nam

> [!NOTE]
> This is a note.

> [!EDITION-2024]
> This is an edition-specific difference.

> [!EXAMPLE]
> This is an example.
```

The color and styling is defined in [`theme/reference.css`](https://github.com/rust-lang/reference/blob/master/theme/reference.css) and the transformation and icons are in [`mdbook-spec/src/lib.rs`](https://github.com/rust-lang/reference/blob/HEAD/mdbook-spec/src/lib.rs).
The color and styling is defined in [`theme/reference.css`](https://github.com/rust-lang/reference/blob/master/theme/reference.css) and the transformation and icons are in [`mdbook-spec/src/admonitions.rs`](https://github.com/rust-lang/reference/blob/HEAD/mdbook-spec/src/admonitions.rs).

## Style

Expand Down
106 changes: 106 additions & 0 deletions mdbook-spec/src/admonitions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! Support for admonitions using markdown blockquotes.
//!
//! To add support for a new admonition:
//!
//! 1. Modify the [`admonitions`] function below to include an icon.
//! 2. Modify `theme/reference.css` to set the color for the different themes.
//! Look at one of the other admonitions as a guide.
//! 3. Update `src/introduction.md` and describe what this new block is for
//! with an example.
//! 4. Update `docs/authoring.md` to show an example of your new admonition.

use crate::{Diagnostics, warn_or_err};
use mdbook::book::Chapter;
use regex::{Captures, Regex};
use std::sync::LazyLock;

/// The Regex for the syntax for blockquotes that have a specific CSS class,
/// like `> [!WARNING]`.
static ADMONITION_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *>.*\n)+)").unwrap()
});

// This icon is from GitHub, MIT License, see https://github.com/primer/octicons
const ICON_NOTE: &str = r#"<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path>"#;

// This icon is from GitHub, MIT License, see https://github.com/primer/octicons
const ICON_WARNING: &str = r#"<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>"#;

// This icon is from GitHub, MIT License, see https://github.com/primer/octicons
const ICON_EXAMPLE: &str = r#"<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"></path>"#;

/// Converts blockquotes with special headers into admonitions.
///
/// The blockquote should look something like:
///
/// ```markdown
/// > [!WARNING]
/// > ...
/// ```
///
/// This will add a `<div class="alert alert-warning">` around the
/// blockquote so that it can be styled differently, and injects an icon.
/// The actual styling needs to be added in the `reference.css` CSS file.
pub fn admonitions(chapter: &Chapter, diag: &mut Diagnostics) -> String {
ADMONITION_RE
.replace_all(&chapter.content, |caps: &Captures<'_>| {
let lower = caps["admon"].to_lowercase();
let term = to_initial_case(&caps["admon"]);
let blockquote = &caps["blockquote"];
let initial_spaces = blockquote.chars().position(|ch| ch != ' ').unwrap_or(0);
let space = &blockquote[..initial_spaces];

let format_div = |class, content| {
format!(
"{space}<div class=\"alert alert-{class}\">\n\
\n\
{space}> <p class=\"alert-title\">\
{content}</p>\n\
{space} >\n\
{blockquote}\n\
\n\
{space}</div>\n",
)
};

if lower.starts_with("edition-") {
let edition = &lower[8..];
return format_div(
"edition",
format!(
"<span class=\"alert-title-edition\">{edition}</span> Edition differences"
),
);
}

let svg = match lower.as_str() {
"note" => ICON_NOTE,
"warning" => ICON_WARNING,
"example" => ICON_EXAMPLE,
_ => {
warn_or_err!(
diag,
"admonition `{lower}` in {:?} is incorrect or not yet supported",
chapter.path.as_ref().unwrap()
);
""
}
};
format_div(
&lower,
format!(
"<svg viewBox=\"0 0 16 16\" width=\"18\" height=\"18\">\
{svg}\
</svg>{term}"
),
)
})
.to_string()
}

fn to_initial_case(s: &str) -> String {
let mut chars = s.chars();
let first = chars.next().expect("not empty").to_uppercase();
let rest = chars.as_str().to_lowercase();
format!("{first}{rest}")
}
77 changes: 2 additions & 75 deletions mdbook-spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,12 @@ use std::io;
use std::ops::Range;
use std::path::PathBuf;

mod admonitions;
pub mod grammar;
mod rules;
mod std_links;
mod test_links;

/// The Regex for the syntax for blockquotes that have a specific CSS class,
/// like `> [!WARNING]`.
static ADMONITION_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *>.*\n)+)").unwrap()
});

/// A primitive regex to find link reference definitions.
static MD_LINK_REFERENCE_DEFINITION: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\[(?<label>[^]]+)]: +(?<dest>.*)").unwrap());
Expand Down Expand Up @@ -167,74 +162,6 @@ impl Spec {
chapter.content
)
}

/// Converts blockquotes with special headers into admonitions.
///
/// The blockquote should look something like:
///
/// ```markdown
/// > [!WARNING]
/// > ...
/// ```
///
/// This will add a `<div class="alert alert-warning">` around the
/// blockquote so that it can be styled differently, and injects an icon.
/// The actual styling needs to be added in the `reference.css` CSS file.
fn admonitions(&self, chapter: &Chapter, diag: &mut Diagnostics) -> String {
ADMONITION_RE
.replace_all(&chapter.content, |caps: &Captures<'_>| {
let lower = caps["admon"].to_lowercase();
let term = to_initial_case(&caps["admon"]);
let blockquote = &caps["blockquote"];
let initial_spaces = blockquote.chars().position(|ch| ch != ' ').unwrap_or(0);
let space = &blockquote[..initial_spaces];
if lower.starts_with("edition-") {
let edition = &lower[8..];
return format!("{space}<div class=\"alert alert-edition\">\n\
\n\
{space}> <p class=\"alert-title\">\
<span class=\"alert-title-edition\">{edition}</span> Edition differences</p>\n\
{space} >\n\
{blockquote}\n\
\n\
{space}</div>\n");
}

// These icons are from GitHub, MIT License, see https://github.com/primer/octicons
let svg = match lower.as_str() {
"note" => "<path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path>",
"warning" => "<path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path>",
_ => {
warn_or_err!(
diag,
"admonition `{lower}` in {:?} is incorrect or not yet supported",
chapter.path.as_ref().unwrap()
);
""
}
};
format!(
"{space}<div class=\"alert alert-{lower}\">\n\
\n\
{space}> <p class=\"alert-title\">\
<svg viewBox=\"0 0 16 16\" width=\"18\" height=\"18\">\
{svg}\
</svg>{term}</p>\n\
{space} >\n\
{blockquote}\n\
\n\
{space}</div>\n",
)
})
.to_string()
}
}

fn to_initial_case(s: &str) -> String {
let mut chars = s.chars();
let first = chars.next().expect("not empty").to_uppercase();
let rest = chars.as_str().to_lowercase();
format!("{first}{rest}")
}

/// Determines the git ref used for linking to a particular branch/tag in GitHub.
Expand Down Expand Up @@ -288,7 +215,7 @@ impl Preprocessor for Spec {
if ch.is_draft_chapter() {
return;
}
ch.content = self.admonitions(&ch, &mut diag);
ch.content = admonitions::admonitions(&ch, &mut diag);
ch.content = self.rule_link_references(&ch, &rules);
ch.content = self.auto_link_references(&ch, &rules);
ch.content = self.render_rule_definitions(&ch.content, &tests, &git_ref);
Expand Down
8 changes: 8 additions & 0 deletions src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ These conventions are documented here.
> [!NOTE]
> This is an example note.

* Example blocks show an example that demonstrates some rule or points out some interesting aspect. Some examples may have hidden lines which can be viewed by clicking the eye icon that appears when hovering or tapping the example.

> [!EXAMPLE]
> This is a code example.
> ```rust
> println!("hello world");
> ```

* Warnings that show unsound behavior in the language or possibly confusing interactions of language features are in a special warning box.

> [!WARNING]
Expand Down
12 changes: 11 additions & 1 deletion theme/reference.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Admonitions are defined with blockquotes:
> [!WARNING]
> This is bad!

See mdbook-spec/src/lib.rs.
See mdbook-spec/src/admonitions.rs.
*/
.alert blockquote {
/* Add some padding to make the vertical bar a little taller than the text.*/
Expand Down Expand Up @@ -62,22 +62,26 @@ See mdbook-spec/src/lib.rs.
--alert-note-color: #0969da;
--alert-warning-color: #9a6700;
--alert-edition-color: #1a7f37;
--alert-example-color: #8250df;
}
.ayu .alert {
--alert-note-color: #74b9ff;
--alert-warning-color: #f0b72f;
--alert-edition-color: #2bd853;
--alert-example-color: #d3abff;
}
.rust .alert {
--alert-note-color: #023b95;
--alert-warning-color: #603700;
--alert-edition-color: #008200;
--alert-example-color: #8250df;
}
.coal .alert,
.navy .alert {
--alert-note-color: #4493f8;
--alert-warning-color: #d29922;
--alert-edition-color: #3fb950;
--alert-example-color: #ab7df8;
}
.alert-note blockquote {
border-inline-start-color: var(--alert-note-color);
Expand All @@ -88,6 +92,9 @@ See mdbook-spec/src/lib.rs.
.alert-edition blockquote {
border-inline-start-color: var(--alert-edition-color);
}
.alert-example blockquote {
border-inline-start-color: var(--alert-example-color);
}
.alert-note .alert-title {
color: var(--alert-note-color);
}
Expand All @@ -106,6 +113,9 @@ See mdbook-spec/src/lib.rs.
font-weight: bold;
color: var(--alert-edition-color);
}
.alert-example .alert-title {
color: var(--alert-example-color);
}

/* <kbd> tags can be used to highlight specific character elements. */
kbd {
Expand Down