From 4815264e2dd4bca61e635eaa0ef7d99c2365b374 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 20 Sep 2023 10:01:53 -0400 Subject: [PATCH] Add mdbook-course to handle parsing frontmatter (#1224) In v2 of the course, I'd like to include an estimate of the time to be spent on each segment in the Markdown file. I think a good place for such metadata is in the frontmatter. For review purposes, though, I just want to display that information. So, this is a start at a new mdbook preprocessor that just separates out the frontmatter and includes it in a `
` block. Eventually, I'd like
to parse it and put the time in the speaker notes.

---------

Co-authored-by: Martin Geisler 
---
 .github/workflows/install-mdbook/action.yml |  6 +-
 Cargo.lock                                  | 78 +++++++++------------
 Cargo.toml                                  |  1 +
 README.md                                   |  2 +
 book.toml                                   |  8 ++-
 frontmatter.css                             |  7 ++
 mdbook-course/Cargo.toml                    | 18 +++++
 mdbook-course/README.md                     | 17 +++++
 mdbook-course/src/frontmatter.rs            | 41 +++++++++++
 mdbook-course/src/lib.rs                    | 15 ++++
 mdbook-course/src/main.rs                   | 46 ++++++++++++
 11 files changed, 194 insertions(+), 45 deletions(-)
 create mode 100644 frontmatter.css
 create mode 100644 mdbook-course/Cargo.toml
 create mode 100644 mdbook-course/README.md
 create mode 100644 mdbook-course/src/frontmatter.rs
 create mode 100644 mdbook-course/src/lib.rs
 create mode 100644 mdbook-course/src/main.rs

diff --git a/.github/workflows/install-mdbook/action.yml b/.github/workflows/install-mdbook/action.yml
index e7e93dd3..9d1f62fc 100644
--- a/.github/workflows/install-mdbook/action.yml
+++ b/.github/workflows/install-mdbook/action.yml
@@ -22,6 +22,10 @@ runs:
       run: cargo install mdbook-i18n-helpers --locked --version 0.2.3
       shell: bash
 
-    - name: Install exerciser
+    - name: Install mdbook-exerciser
       run: cargo install --path mdbook-exerciser --locked
       shell: bash
+
+    - name: Install mdbook-course
+      run: cargo install --path mdbook-course --locked
+      shell: bash
diff --git a/Cargo.lock b/Cargo.lock
index aede60bb..9571f98e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -257,18 +257,18 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.2"
+version = "4.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
+checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
 dependencies = [
  "clap_builder",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.4.2"
+version = "4.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
+checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
 dependencies = [
  "anstream",
  "anstyle",
@@ -963,17 +963,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "io-lifetimes"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
-dependencies = [
- "hermit-abi 0.3.2",
- "libc",
- "windows-sys",
-]
-
 [[package]]
 name = "ipnet"
 version = "2.8.0"
@@ -987,7 +976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
  "hermit-abi 0.3.2",
- "rustix 0.38.13",
+ "rustix",
  "windows-sys",
 ]
 
@@ -1038,12 +1027,6 @@ version = "0.2.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
 
-[[package]]
-name = "linux-raw-sys"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
-
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.7"
@@ -1098,6 +1081,16 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
 
+[[package]]
+name = "matter"
+version = "0.1.0-alpha4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc16e839c57e0ad77957c42d39baab3692a1c6fa47692066470cddc24a5b0cd0"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
 [[package]]
 name = "mdbook"
 version = "0.4.34"
@@ -1132,6 +1125,19 @@ dependencies = [
  "warp",
 ]
 
+[[package]]
+name = "mdbook-course"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "log",
+ "matter",
+ "mdbook",
+ "pretty_env_logger",
+ "serde_json",
+]
+
 [[package]]
 name = "mdbook-exerciser"
 version = "0.1.0"
@@ -1781,20 +1787,6 @@ dependencies = [
  "semver",
 ]
 
-[[package]]
-name = "rustix"
-version = "0.37.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
-dependencies = [
- "bitflags 1.3.2",
- "errno",
- "io-lifetimes",
- "libc",
- "linux-raw-sys 0.3.8",
- "windows-sys",
-]
-
 [[package]]
 name = "rustix"
 version = "0.38.13"
@@ -1804,7 +1796,7 @@ dependencies = [
  "bitflags 2.4.0",
  "errno",
  "libc",
- "linux-raw-sys 0.4.7",
+ "linux-raw-sys",
  "windows-sys",
 ]
 
@@ -1938,9 +1930,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.106"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
 dependencies = [
  "itoa",
  "ryu",
@@ -2131,7 +2123,7 @@ dependencies = [
  "cfg-if",
  "fastrand",
  "redox_syscall",
- "rustix 0.38.13",
+ "rustix",
  "windows-sys",
 ]
 
@@ -2157,11 +2149,11 @@ dependencies = [
 
 [[package]]
 name = "terminal_size"
-version = "0.2.6"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
+checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
 dependencies = [
- "rustix 0.37.23",
+ "rustix",
  "windows-sys",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 7f1b06da..0c36b41e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
 [workspace]
 members = [
   "mdbook-exerciser",
+  "mdbook-course",
   "src/exercises",
   "src/bare-metal/useful-crates/allocator-example",
   "src/bare-metal/useful-crates/zerocopy-example",
diff --git a/README.md b/README.md
index b5ced429..54617a24 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ The course is built using a few tools:
 - [mdbook-svgbob](https://github.com/boozook/mdbook-svgbob)
 - [mdbook-i18n-helpers](https://github.com/google/mdbook-i18n-helpers)
 - [mdbook-exerciser](mdbook-exerciser/)
+- [mdbook-course](mdbook-course/)
 
 First clone the repository:
 
@@ -51,6 +52,7 @@ cargo install mdbook
 cargo install mdbook-svgbob
 cargo install mdbook-i18n-helpers
 cargo install --path mdbook-exerciser
+cargo install --path mdbook-course
 ```
 
 Run
diff --git a/book.toml b/book.toml
index 92d09724..a563aa30 100644
--- a/book.toml
+++ b/book.toml
@@ -19,6 +19,7 @@ renderers = ["html"]
 after = ["gettext"]
 class = "bob"
 
+[preprocessor.course]
 # Enable this preprocessor to overlay a large red rectangle on the
 # pages. This will show you an estimate of what the course
 # participants can see during the presentation.
@@ -29,7 +30,12 @@ class = "bob"
 [output.html]
 curly-quotes = true
 additional-js = ["speaker-notes.js"]
-additional-css = ["svgbob.css", "speaker-notes.css", "language-picker.css"]
+additional-css = [
+  "svgbob.css",
+  "speaker-notes.css",
+  "language-picker.css",
+  "frontmatter.css",
+]
 site-url = "/comprehensive-rust/"
 git-repository-url = "https://github.com/google/comprehensive-rust"
 edit-url-template = "https://github.com/google/comprehensive-rust/edit/main/{path}"
diff --git a/frontmatter.css b/frontmatter.css
new file mode 100644
index 00000000..ab367ad4
--- /dev/null
+++ b/frontmatter.css
@@ -0,0 +1,7 @@
+pre.frontmatter {
+  float: right;
+  font-size: 80%;
+  width: 40%;
+  background: var(--sidebar-bg);
+  padding: 0.25em;
+}
diff --git a/mdbook-course/Cargo.toml b/mdbook-course/Cargo.toml
new file mode 100644
index 00000000..dd3b9e48
--- /dev/null
+++ b/mdbook-course/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "mdbook-course"
+version = "0.1.0"
+authors = ["Dustin Mitchell "]
+edition = "2021"
+license = "Apache-2.0"
+publish = false
+repository = "https://github.com/google/comprehensive-rust"
+description = "An mdbook preprocessor for comprehensive-rust."
+
+[dependencies]
+anyhow = "1.0.68"
+clap = "4.4.4"
+log = "0.4.17"
+matter = "0.1.0-alpha4"
+mdbook = "0.4.25"
+pretty_env_logger = "0.4.0"
+serde_json = "1.0.107"
diff --git a/mdbook-course/README.md b/mdbook-course/README.md
new file mode 100644
index 00000000..c349630c
--- /dev/null
+++ b/mdbook-course/README.md
@@ -0,0 +1,17 @@
+# mdbook-course
+
+This is an mdBook preprocessor to handle some specific details of Comprehensive
+Rust.
+
+## Frontmatter
+
+The preprocessor parses "frontmatter" -- YAML between `---` at the beginning of
+a Markdown file -- and removes it from the rendered result. At the moment, to
+aid review of the new course, it places this content in a `
` block.
+
+## Future Work
+
+- Parse the `minutes` property from frontmatter and
+  - Generate a course timeline
+  - Include timing information in the speaker notes
+- Generate per-segment tables of contents.
diff --git a/mdbook-course/src/frontmatter.rs b/mdbook-course/src/frontmatter.rs
new file mode 100644
index 00000000..842cdfb7
--- /dev/null
+++ b/mdbook-course/src/frontmatter.rs
@@ -0,0 +1,41 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use matter::matter;
+use mdbook::book::{Book, BookItem};
+use mdbook::preprocess::PreprocessorContext;
+
+pub fn remove_frontmatter(
+    ctx: &PreprocessorContext,
+    book: &mut Book,
+) -> anyhow::Result<()> {
+    let is_html = ctx.renderer == "html";
+    book.for_each_mut(|chapter| {
+        let BookItem::Chapter(chapter) = chapter else {
+            return;
+        };
+        if let Some((frontmatter, content)) = matter(&chapter.content) {
+            if is_html {
+                // For the moment, include the frontmatter in the slide in a floating 
, for review
+                // purposes.
+                let pre = format!(r#"
{frontmatter}
"#); + chapter.content = format!("{pre}\n\n{content}"); + } else { + // For non-HTML renderers, just strip the frontmatter. + chapter.content = content; + } + } + }); + Ok(()) +} diff --git a/mdbook-course/src/lib.rs b/mdbook-course/src/lib.rs new file mode 100644 index 00000000..f9175571 --- /dev/null +++ b/mdbook-course/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod frontmatter; diff --git a/mdbook-course/src/main.rs b/mdbook-course/src/main.rs new file mode 100644 index 00000000..1da819e0 --- /dev/null +++ b/mdbook-course/src/main.rs @@ -0,0 +1,46 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::{Arg, Command}; +use mdbook::preprocess::CmdPreprocessor; +use mdbook_course::frontmatter::remove_frontmatter; +use std::io::{stdin, stdout}; +use std::process; + +fn main() { + pretty_env_logger::init(); + let app = Command::new("mdbook-course") + .about("mdbook preprocessor for Comprehensive Rust") + .subcommand(Command::new("supports").arg(Arg::new("renderer").required(true))); + let matches = app.get_matches(); + + if let Some(_) = matches.subcommand_matches("supports") { + // Support all renderers. + process::exit(0); + } + + if let Err(e) = preprocess() { + eprintln!("{}", e); + process::exit(1); + } +} + +fn preprocess() -> anyhow::Result<()> { + let (ctx, mut book) = CmdPreprocessor::parse_input(stdin())?; + + remove_frontmatter(&ctx, &mut book)?; + + serde_json::to_writer(stdout(), &book)?; + Ok(()) +}