Don't rely on relative path for docs preprocessor (#16883)
Reapplies #16700 with a corrected command. Now it no longer relies on a relative path. Thanks @maxdeviant for the quick help 🙏 Release Notes: - N/A
This commit is contained in:
parent
a87076e815
commit
7a964ff91a
16 changed files with 639 additions and 24 deletions
26
crates/docs_preprocessor/Cargo.toml
Normal file
26
crates/docs_preprocessor/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "docs_preprocessor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
mdbook = "0.4.40"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
regex.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/docs_preprocessor.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "docs_preprocessor"
|
||||
path = "src/main.rs"
|
1
crates/docs_preprocessor/LICENSE-GPL
Symbolic link
1
crates/docs_preprocessor/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-GPL
|
93
crates/docs_preprocessor/src/docs_preprocessor.rs
Normal file
93
crates/docs_preprocessor/src/docs_preprocessor.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
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,
|
||||
};
|
||||
|
||||
keymap.blocks().iter().find_map(|block| {
|
||||
block.bindings().iter().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"
|
||||
}
|
||||
}
|
58
crates/docs_preprocessor/src/main.rs
Normal file
58
crates/docs_preprocessor/src/main.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use anyhow::{Context, Result};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use docs_preprocessor::ZedDocsPreprocessor;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||
use std::io::{self, Read};
|
||||
use std::process;
|
||||
|
||||
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();
|
||||
|
||||
let preprocessor =
|
||||
ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?;
|
||||
|
||||
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
||||
handle_supports(&preprocessor, sub_args);
|
||||
} else {
|
||||
handle_preprocessing(&preprocessor)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preprocessing(pre: &dyn Preprocessor) -> 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 processed_book = pre.run(&ctx, book)?;
|
||||
|
||||
serde_json::to_writer(io::stdout(), &processed_book)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
|
||||
let renderer = sub_args
|
||||
.get_one::<String>("renderer")
|
||||
.expect("Required argument");
|
||||
let supported = pre.supports_renderer(renderer);
|
||||
|
||||
if supported {
|
||||
process::exit(0);
|
||||
} else {
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
25
crates/docs_preprocessor/src/templates.rs
Normal file
25
crates/docs_preprocessor/src/templates.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
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()
|
||||
}
|
||||
}
|
50
crates/docs_preprocessor/src/templates/action.rs
Normal file
50
crates/docs_preprocessor/src/templates/action.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
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)
|
||||
}
|
||||
}
|
36
crates/docs_preprocessor/src/templates/keybinding.rs
Normal file
36
crates/docs_preprocessor/src/templates/keybinding.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
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();
|
||||
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue