From b057b4697f10ec38582e0b920d26760da7a2a536 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Mon, 19 May 2025 07:16:14 -0500 Subject: [PATCH] Simplify docs preprocessing (#30947) Closes #ISSUE This was done as part of experimental work towards better validation of our docs. The validation ended up being not worth it, however, I believe this refactoring is Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/docs_preprocessor/Cargo.toml | 3 - .../src/docs_preprocessor.rs | 94 ------------- crates/docs_preprocessor/src/main.rs | 124 +++++++++++++++--- crates/docs_preprocessor/src/templates.rs | 25 ---- .../docs_preprocessor/src/templates/action.rs | 50 ------- .../src/templates/keybinding.rs | 41 ------ docs/README.md | 2 +- 7 files changed, 110 insertions(+), 229 deletions(-) delete mode 100644 crates/docs_preprocessor/src/docs_preprocessor.rs delete mode 100644 crates/docs_preprocessor/src/templates.rs delete mode 100644 crates/docs_preprocessor/src/templates/action.rs delete mode 100644 crates/docs_preprocessor/src/templates/keybinding.rs diff --git a/crates/docs_preprocessor/Cargo.toml b/crates/docs_preprocessor/Cargo.toml index f50b9ce4ff..a77965ce1d 100644 --- a/crates/docs_preprocessor/Cargo.toml +++ b/crates/docs_preprocessor/Cargo.toml @@ -19,9 +19,6 @@ workspace-hack.workspace = true [lints] workspace = true -[lib] -path = "src/docs_preprocessor.rs" - [[bin]] name = "docs_preprocessor" path = "src/main.rs" diff --git a/crates/docs_preprocessor/src/docs_preprocessor.rs b/crates/docs_preprocessor/src/docs_preprocessor.rs deleted file mode 100644 index 0511aedb43..0000000000 --- a/crates/docs_preprocessor/src/docs_preprocessor.rs +++ /dev/null @@ -1,94 +0,0 @@ -use anyhow::Result; -use mdbook::book::{Book, BookItem}; -use mdbook::errors::Error; -use mdbook::preprocess::{Preprocessor, PreprocessorContext as MdBookContext}; -use settings::KeymapFile; -use std::sync::Arc; -use util::asset_str; - -mod templates; - -use templates::{ActionTemplate, KeybindingTemplate, Template}; - -pub struct PreprocessorContext { - macos_keymap: Arc, - linux_keymap: Arc, -} - -impl PreprocessorContext { - pub fn new() -> Result { - let macos_keymap = Arc::new(load_keymap("keymaps/default-macos.json")?); - let linux_keymap = Arc::new(load_keymap("keymaps/default-linux.json")?); - Ok(Self { - macos_keymap, - linux_keymap, - }) - } - - pub fn find_binding(&self, os: &str, action: &str) -> Option { - let keymap = match os { - "macos" => &self.macos_keymap, - "linux" => &self.linux_keymap, - _ => return None, - }; - - // Find the binding in reverse order, as the last binding takes precedence. - keymap.sections().rev().find_map(|section| { - section.bindings().rev().find_map(|(keystroke, a)| { - if a.to_string() == action { - Some(keystroke.to_string()) - } else { - None - } - }) - }) - } -} - -fn load_keymap(asset_path: &str) -> Result { - let content = asset_str::(asset_path); - KeymapFile::parse(content.as_ref()) -} - -pub struct ZedDocsPreprocessor { - context: PreprocessorContext, - templates: Vec>, -} - -impl ZedDocsPreprocessor { - pub fn new() -> Result { - let context = PreprocessorContext::new()?; - let templates: Vec> = vec![ - Box::new(KeybindingTemplate::new()), - Box::new(ActionTemplate::new()), - ]; - Ok(Self { context, templates }) - } - - fn process_content(&self, content: &str) -> String { - let mut processed = content.to_string(); - for template in &self.templates { - processed = template.process(&self.context, &processed); - } - processed - } -} - -impl Preprocessor for ZedDocsPreprocessor { - fn name(&self) -> &str { - "zed-docs-preprocessor" - } - - fn run(&self, _ctx: &MdBookContext, mut book: Book) -> Result { - book.for_each_mut(|item| { - if let BookItem::Chapter(chapter) = item { - chapter.content = self.process_content(&chapter.content); - } - }); - Ok(book) - } - - fn supports_renderer(&self, renderer: &str) -> bool { - renderer != "not-supported" - } -} diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index f1e862851b..ff4a9fc8ed 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -1,9 +1,21 @@ -use anyhow::{Context as _, Result}; +use anyhow::Result; use clap::{Arg, ArgMatches, Command}; -use docs_preprocessor::ZedDocsPreprocessor; -use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; +use mdbook::BookItem; +use mdbook::book::{Book, Chapter}; +use mdbook::preprocess::CmdPreprocessor; +use regex::Regex; +use settings::KeymapFile; use std::io::{self, Read}; use std::process; +use std::sync::LazyLock; + +static KEYMAP_MACOS: LazyLock = LazyLock::new(|| { + load_keymap("keymaps/default-macos.json").expect("Failed to load MacOS keymap") +}); + +static KEYMAP_LINUX: LazyLock = LazyLock::new(|| { + load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap") +}); pub fn make_app() -> Command { Command::new("zed-docs-preprocessor") @@ -18,41 +30,123 @@ pub fn make_app() -> Command { fn main() -> Result<()> { let matches = make_app().get_matches(); - let preprocessor = - ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?; - if let Some(sub_args) = matches.subcommand_matches("supports") { - handle_supports(&preprocessor, sub_args); + handle_supports(sub_args); } else { - handle_preprocessing(&preprocessor)?; + handle_preprocessing()?; } Ok(()) } -fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> { +fn handle_preprocessing() -> Result<()> { let mut stdin = io::stdin(); let mut input = String::new(); stdin.read_to_string(&mut input)?; - let (ctx, book) = CmdPreprocessor::parse_input(input.as_bytes())?; + let (_ctx, mut book) = CmdPreprocessor::parse_input(input.as_bytes())?; - let processed_book = pre.run(&ctx, book)?; + template_keybinding(&mut book); + template_action(&mut book); - serde_json::to_writer(io::stdout(), &processed_book)?; + serde_json::to_writer(io::stdout(), &book)?; Ok(()) } -fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! { +fn handle_supports(sub_args: &ArgMatches) -> ! { let renderer = sub_args .get_one::("renderer") .expect("Required argument"); - let supported = pre.supports_renderer(renderer); - + let supported = renderer != "not-supported"; if supported { process::exit(0); } else { process::exit(1); } } + +fn template_keybinding(book: &mut Book) { + let regex = Regex::new(r"\{#kb (.*?)\}").unwrap(); + + for_each_chapter_mut(book, |chapter| { + chapter.content = regex + .replace_all(&chapter.content, |caps: ®ex::Captures| { + let action = caps[1].trim(); + let macos_binding = find_binding("macos", action).unwrap_or_default(); + let linux_binding = find_binding("linux", action).unwrap_or_default(); + + if macos_binding.is_empty() && linux_binding.is_empty() { + return "
No default binding
".to_string(); + } + + format!("{macos_binding}|{linux_binding}") + }) + .into_owned() + }); +} + +fn template_action(book: &mut Book) { + let regex = Regex::new(r"\{#action (.*?)\}").unwrap(); + + for_each_chapter_mut(book, |chapter| { + chapter.content = regex + .replace_all(&chapter.content, |caps: ®ex::Captures| { + let name = caps[1].trim(); + + let formatted_name = name + .chars() + .enumerate() + .map(|(i, c)| { + if i > 0 && c.is_uppercase() { + format!(" {}", c.to_lowercase()) + } else { + c.to_string() + } + }) + .collect::() + .trim() + .to_string() + .replace("::", ":"); + + format!("{}", formatted_name) + }) + .into_owned() + }); +} + +fn find_binding(os: &str, action: &str) -> Option { + let keymap = match os { + "macos" => &KEYMAP_MACOS, + "linux" => &KEYMAP_LINUX, + _ => unreachable!("Not a valid OS: {}", os), + }; + + // Find the binding in reverse order, as the last binding takes precedence. + keymap.sections().rev().find_map(|section| { + section.bindings().rev().find_map(|(keystroke, a)| { + if a.to_string() == action { + Some(keystroke.to_string()) + } else { + None + } + }) + }) +} + +fn load_keymap(asset_path: &str) -> Result { + let content = util::asset_str::(asset_path); + KeymapFile::parse(content.as_ref()) +} + +fn for_each_chapter_mut(book: &mut Book, mut func: F) +where + F: FnMut(&mut Chapter), +{ + book.for_each_mut(|item| { + let BookItem::Chapter(chapter) = item else { + return; + }; + func(chapter); + }); +} diff --git a/crates/docs_preprocessor/src/templates.rs b/crates/docs_preprocessor/src/templates.rs deleted file mode 100644 index fc169951ab..0000000000 --- a/crates/docs_preprocessor/src/templates.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::PreprocessorContext; -use regex::Regex; -use std::collections::HashMap; - -mod action; -mod keybinding; - -pub use action::*; -pub use keybinding::*; - -pub trait Template { - fn key(&self) -> &'static str; - fn regex(&self) -> Regex; - fn parse_args(&self, args: &str) -> HashMap; - fn render(&self, context: &PreprocessorContext, args: &HashMap) -> String; - - fn process(&self, context: &PreprocessorContext, content: &str) -> String { - self.regex() - .replace_all(content, |caps: ®ex::Captures| { - let args = self.parse_args(&caps[1]); - self.render(context, &args) - }) - .into_owned() - } -} diff --git a/crates/docs_preprocessor/src/templates/action.rs b/crates/docs_preprocessor/src/templates/action.rs deleted file mode 100644 index 7f67065c67..0000000000 --- a/crates/docs_preprocessor/src/templates/action.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::PreprocessorContext; -use regex::Regex; -use std::collections::HashMap; - -use super::Template; - -pub struct ActionTemplate; - -impl ActionTemplate { - pub fn new() -> Self { - ActionTemplate - } -} - -impl Template for ActionTemplate { - fn key(&self) -> &'static str { - "action" - } - - fn regex(&self) -> Regex { - Regex::new(&format!(r"\{{#{}(.*?)\}}", self.key())).unwrap() - } - - fn parse_args(&self, args: &str) -> HashMap { - let mut map = HashMap::new(); - map.insert("name".to_string(), args.trim().to_string()); - map - } - - fn render(&self, _context: &PreprocessorContext, args: &HashMap) -> String { - let name = args.get("name").map(String::as_str).unwrap_or_default(); - - let formatted_name = name - .chars() - .enumerate() - .map(|(i, c)| { - if i > 0 && c.is_uppercase() { - format!(" {}", c.to_lowercase()) - } else { - c.to_string() - } - }) - .collect::() - .trim() - .to_string() - .replace("::", ":"); - - format!("{}", formatted_name) - } -} diff --git a/crates/docs_preprocessor/src/templates/keybinding.rs b/crates/docs_preprocessor/src/templates/keybinding.rs deleted file mode 100644 index 6523502e54..0000000000 --- a/crates/docs_preprocessor/src/templates/keybinding.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::PreprocessorContext; -use regex::Regex; -use std::collections::HashMap; - -use super::Template; - -pub struct KeybindingTemplate; - -impl KeybindingTemplate { - pub fn new() -> Self { - KeybindingTemplate - } -} - -impl Template for KeybindingTemplate { - fn key(&self) -> &'static str { - "kb" - } - - fn regex(&self) -> Regex { - Regex::new(&format!(r"\{{#{}(.*?)\}}", self.key())).unwrap() - } - - fn parse_args(&self, args: &str) -> HashMap { - let mut map = HashMap::new(); - map.insert("action".to_string(), args.trim().to_string()); - map - } - - fn render(&self, context: &PreprocessorContext, args: &HashMap) -> String { - let action = args.get("action").map(String::as_str).unwrap_or(""); - let macos_binding = context.find_binding("macos", action).unwrap_or_default(); - let linux_binding = context.find_binding("linux", action).unwrap_or_default(); - - if macos_binding.is_empty() && linux_binding.is_empty() { - return "
No default binding
".to_string(); - } - - format!("{macos_binding}|{linux_binding}") - } -} diff --git a/docs/README.md b/docs/README.md index e78346f579..7fa5fc4531 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,7 +62,7 @@ This will render a human-readable version of the action name, e.g., "zed: open s ### Creating New Templates -New templates can be created by implementing the `Template` trait for your desired template in the `docs_preprocessor` crate. +Templates are just functions that modify the source of the docs pages (usually with a regex match & replace). You can see how the actions and keybindings are templated in `crates/docs_preprocessor/src/main.rs` for reference on how to create new templates. ### References