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"
Ben Kunkle created
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
crates/docs_preprocessor/src/docs_preprocessor.rs | 94 ----------
crates/docs_preprocessor/src/main.rs | 124 ++++++++++++-
crates/docs_preprocessor/src/templates.rs | 25 --
crates/docs_preprocessor/src/templates/action.rs | 50 -----
crates/docs_preprocessor/src/templates/keybinding.rs | 41 ----
docs/README.md | 2
7 files changed, 110 insertions(+), 229 deletions(-)
@@ -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"
@@ -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<KeymapFile>,
- linux_keymap: Arc<KeymapFile>,
-}
-
-impl PreprocessorContext {
- pub fn new() -> Result<Self> {
- 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<String> {
- 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<KeymapFile> {
- let content = asset_str::<settings::SettingsAssets>(asset_path);
- KeymapFile::parse(content.as_ref())
-}
-
-pub struct ZedDocsPreprocessor {
- context: PreprocessorContext,
- templates: Vec<Box<dyn Template>>,
-}
-
-impl ZedDocsPreprocessor {
- pub fn new() -> Result<Self> {
- let context = PreprocessorContext::new()?;
- let templates: Vec<Box<dyn Template>> = 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, Error> {
- 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"
- }
-}
@@ -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<KeymapFile> = LazyLock::new(|| {
+ load_keymap("keymaps/default-macos.json").expect("Failed to load MacOS keymap")
+});
+
+static KEYMAP_LINUX: LazyLock<KeymapFile> = 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::<String>("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 "<div>No default binding</div>".to_string();
+ }
+
+ format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
+ })
+ .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::<String>()
+ .trim()
+ .to_string()
+ .replace("::", ":");
+
+ format!("<code class=\"hljs\">{}</code>", formatted_name)
+ })
+ .into_owned()
+ });
+}
+
+fn find_binding(os: &str, action: &str) -> Option<String> {
+ 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<KeymapFile> {
+ let content = util::asset_str::<settings::SettingsAssets>(asset_path);
+ KeymapFile::parse(content.as_ref())
+}
+
+fn for_each_chapter_mut<F>(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);
+ });
+}
@@ -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<String, String>;
- fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> 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()
- }
-}
@@ -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<String, String> {
- let mut map = HashMap::new();
- map.insert("name".to_string(), args.trim().to_string());
- map
- }
-
- fn render(&self, _context: &PreprocessorContext, args: &HashMap<String, String>) -> 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::<String>()
- .trim()
- .to_string()
- .replace("::", ":");
-
- format!("<code class=\"hljs\">{}</code>", formatted_name)
- }
-}
@@ -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<String, String> {
- let mut map = HashMap::new();
- map.insert("action".to_string(), args.trim().to_string());
- map
- }
-
- fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> 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 "<div>No default binding</div>".to_string();
- }
-
- format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
- }
-}
@@ -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