
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 ...
152 lines
4.4 KiB
Rust
152 lines
4.4 KiB
Rust
use anyhow::Result;
|
|
use clap::{Arg, ArgMatches, Command};
|
|
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")
|
|
.about("Preprocesses Zed Docs content to provide rich action & keybinding support and more")
|
|
.subcommand(
|
|
Command::new("supports")
|
|
.arg(Arg::new("renderer").required(true))
|
|
.about("Check whether a renderer is supported by this preprocessor"),
|
|
)
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
let matches = make_app().get_matches();
|
|
|
|
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
|
handle_supports(sub_args);
|
|
} else {
|
|
handle_preprocessing()?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_preprocessing() -> Result<()> {
|
|
let mut stdin = io::stdin();
|
|
let mut input = String::new();
|
|
stdin.read_to_string(&mut input)?;
|
|
|
|
let (_ctx, mut book) = CmdPreprocessor::parse_input(input.as_bytes())?;
|
|
|
|
template_keybinding(&mut book);
|
|
template_action(&mut book);
|
|
|
|
serde_json::to_writer(io::stdout(), &book)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_supports(sub_args: &ArgMatches) -> ! {
|
|
let renderer = sub_args
|
|
.get_one::<String>("renderer")
|
|
.expect("Required argument");
|
|
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);
|
|
});
|
|
}
|