Skip to content

Commit 59e9164

Browse files
authored
feat: create mdbook backend for LAD files (#287)
* feat: add global instances to LAD format * link to test data in readme * feat: create initial mdbook `LAD` backend * add debug logging for integration tests * feat: intial work on the markdown plugin
1 parent 2da36d5 commit 59e9164

File tree

15 files changed

+1141
-7
lines changed

15 files changed

+1141
-7
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ Cargo.lock
1313
**/doc
1414
assets/scripts/tlconfig.lua
1515
**.log
16-
**build/
16+
**build/
17+
.html

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ members = [
8989
"crates/xtask",
9090
"crates/script_integration_test_harness",
9191
"crates/bevy_mod_scripting_derive",
92-
"crates/ladfile",
92+
"crates/ladfile", "crates/lad_backends/mdbook_lad_preprocessor",
9393
]
9494
resolver = "2"
9595
exclude = ["crates/bevy_api_gen", "crates/macro_tests"]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "mdbook_lad_preprocessor"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["Maksymilian Mozolewski <[email protected]>"]
6+
license = "MIT OR Apache-2.0"
7+
description = "Language Agnostic Declaration (LAD) file format for the bevy_mod_scripting crate"
8+
repository = "https://github.com/makspll/bevy_mod_scripting"
9+
homepage = "https://github.com/makspll/bevy_mod_scripting"
10+
keywords = ["bevy", "gamedev", "scripting", "documentation", "generator"]
11+
categories = ["game-development", "development-tools"]
12+
include = ["readme.md", "/src"]
13+
readme = "readme.md"
14+
15+
[dependencies]
16+
clap = "4"
17+
mdbook = "0.4"
18+
ladfile = { path = "../../ladfile", version = "0.1.1" }
19+
env_logger = "0.11"
20+
log = "0.4"
21+
serde_json = "1.0"
22+
23+
[dev-dependencies]
24+
assert_cmd = "2.0"
25+
pretty_assertions = "1.4.1"
26+
27+
[lints]
28+
workspace = true
29+
30+
[[bin]]
31+
name = "mdbook-lad-preprocessor"
32+
path = "src/main.rs"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# LAD Preprocessor for mdbook
2+
3+
This is a preprocessor for `mdbook` that allows you to include `LAD` files in your markdown files.
4+
5+
## Usage
6+
7+
Add the following to your `book.toml`:
8+
9+
```toml
10+
[preprocessor.lad_preprocessor]
11+
```
12+
13+
Then any files with the `.lad.json` extension will be processed by the preprocessor.
14+
15+
So for example if you have the following structure:
16+
17+
```markdown
18+
- [Normal file](normal_file.md)
19+
- [LAD file](lad_file.lad.json)
20+
```
21+
22+
The `lad_file.lad.json` will be processed by the preprocessor, and appropriate nested markdown will be generated from there on out using the `LAD file` chapter as the parent page.
23+
24+
If the file is not found
25+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! The library crate for the mdbook LAD preprocessor.
2+
#![allow(missing_docs)]
3+
4+
use mdbook::{errors::Error, preprocess::Preprocessor};
5+
mod markdown;
6+
mod sections;
7+
8+
const LAD_EXTENSION: &str = "lad.json";
9+
10+
pub struct LADPreprocessor;
11+
12+
impl Preprocessor for LADPreprocessor {
13+
fn name(&self) -> &str {
14+
"lad-preprocessor"
15+
}
16+
17+
fn run(
18+
&self,
19+
_ctx: &mdbook::preprocess::PreprocessorContext,
20+
mut book: mdbook::book::Book,
21+
) -> mdbook::errors::Result<mdbook::book::Book> {
22+
let mut errors = Vec::default();
23+
24+
book.for_each_mut(|item| {
25+
if let mdbook::BookItem::Chapter(chapter) = item {
26+
let is_lad_chapter = chapter
27+
.source_path
28+
.as_ref()
29+
.and_then(|a| a.file_name())
30+
.is_some_and(|a| a.to_string_lossy().ends_with(LAD_EXTENSION));
31+
32+
if !is_lad_chapter {
33+
log::debug!("Skipping non-LAD chapter: {:?}", chapter.source_path);
34+
return;
35+
}
36+
37+
let chapter_title = chapter.name.clone();
38+
39+
let lad = match ladfile::parse_lad_file(&chapter.content) {
40+
Ok(lad) => lad,
41+
Err(e) => {
42+
log::debug!("Failed to parse LAD file: {:?}", e);
43+
errors.push(Error::new(e).context("Failed to parse LAD file"));
44+
return;
45+
}
46+
};
47+
48+
log::debug!("Parsed LAD file: {:?}", lad);
49+
50+
let sections = sections::lad_file_to_sections(&lad, Some(chapter_title));
51+
52+
let new_chapter = sections::section_to_chapter(
53+
sections,
54+
Some(chapter),
55+
chapter.parent_names.clone(),
56+
chapter.number.clone(),
57+
None,
58+
None,
59+
);
60+
61+
log::debug!("New chapter: {:?}", new_chapter);
62+
63+
*chapter = new_chapter;
64+
}
65+
});
66+
67+
if !errors.is_empty() {
68+
// return on first error
69+
for error in errors {
70+
log::error!("{}", error);
71+
Err(error)?;
72+
}
73+
}
74+
75+
Ok(book)
76+
}
77+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#![allow(missing_docs)]
2+
use clap::{Arg, Command};
3+
use env_logger::Builder;
4+
use log::LevelFilter;
5+
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
6+
use mdbook_lad_preprocessor::LADPreprocessor;
7+
use std::{env, fs::File, io, process::exit};
8+
9+
// use mdbook_lad_preprocessor::LADPreprocessor;
10+
11+
fn init_logger() {
12+
let mut builder = Builder::new();
13+
14+
if let Ok(var) = env::var("RUST_LOG") {
15+
builder.parse_filters(&var);
16+
} else {
17+
builder.filter(None, LevelFilter::Info);
18+
}
19+
20+
// target lad.log file in current directory
21+
// print pwd
22+
if let Ok(file) = File::create("./lad.log") {
23+
let target = Box::new(file);
24+
builder.target(env_logger::Target::Pipe(target));
25+
}
26+
27+
builder.init();
28+
29+
log::debug!("Debug logging enabled");
30+
}
31+
32+
pub fn make_app() -> Command {
33+
Command::new("nop-preprocessor")
34+
.about("A mdbook preprocessor which does precisely nothing")
35+
.subcommand(
36+
Command::new("supports")
37+
.arg(Arg::new("renderer").required(true))
38+
.about("Check whether a renderer is supported by this preprocessor"),
39+
)
40+
}
41+
42+
fn main() -> Result<(), Box<dyn std::error::Error>> {
43+
init_logger();
44+
let matches = make_app().get_matches();
45+
if let Some(sub_args) = matches.subcommand_matches("supports") {
46+
let renderer = match sub_args.get_one::<String>("renderer") {
47+
Some(r) => r,
48+
None => {
49+
log::error!("No renderer specified");
50+
exit(1)
51+
}
52+
};
53+
54+
if LADPreprocessor.supports_renderer(renderer) {
55+
exit(0)
56+
} else {
57+
exit(1)
58+
}
59+
} else {
60+
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
61+
let processed_book = LADPreprocessor.run(&ctx, book)?;
62+
serde_json::to_writer(io::stdout(), &processed_book)?;
63+
exit(0)
64+
}
65+
}

0 commit comments

Comments
 (0)