1#![doc = include_str!("../README.md")]
2#![warn(clippy::cargo, clippy::pedantic)]
3#![warn(
4 clippy::exit,
5 clippy::expect_used,
6 clippy::panic,
7 clippy::semicolon_inside_block,
8 clippy::str_to_string,
9 clippy::implicit_clone,
10 clippy::unnecessary_self_imports,
11 clippy::use_debug
12)]
13#![deny(clippy::print_stderr, clippy::print_stdout)]
14#![allow(clippy::must_use_candidate)]
15
16mod angular;
17pub(crate) mod codeblock;
18pub(crate) mod config;
19mod js;
20mod markdown;
21mod utils;
22
23pub const MDBOOK_ANGULAR_VERSION: &str = env!("CARGO_PKG_VERSION");
25
26pub const EXPECTED_MDBOOK_VERSION: &str = mdbook_renderer::MDBOOK_VERSION;
31
32use std::{env, fs};
33
34pub use angular::stop_background_process;
35pub use config::{Builder, Config};
36
37use angular::build;
38use log::debug;
39use log::warn;
40use markdown::process_markdown;
41use markdown::ChapterWithCodeBlocks;
42use mdbook_html::HtmlHandlebars;
43use mdbook_renderer::{RenderContext, Renderer};
44
45fn validate_version(ctx: &RenderContext) -> Result<()> {
46 let req = semver::VersionReq::parse(EXPECTED_MDBOOK_VERSION).unwrap();
47
48 if semver::Version::parse(&ctx.version).is_ok_and(|version| req.matches(&version)) {
49 Ok(())
50 } else {
51 bail!("Invalid mdbook version {}, expected {}", &ctx.version, req);
52 }
53}
54
55pub(crate) use anyhow::{bail, Context, Error, Result};
56
57pub struct AngularRenderer {}
59
60impl Renderer for AngularRenderer {
61 fn name(&self) -> &'static str {
62 "angular"
63 }
64
65 #[inline]
71 fn render(&self, ctx: &RenderContext) -> Result<()> {
72 self.render_mut(&mut ctx.clone())
73 }
74}
75
76impl AngularRenderer {
77 pub fn new() -> Self {
78 Self {}
79 }
80
81 #[allow(clippy::missing_errors_doc)]
87 pub fn render_mut(&self, ctx: &mut RenderContext) -> Result<()> {
88 validate_version(ctx)?;
89
90 let config = Config::new(ctx)?;
91 let mut chapters_with_codeblocks = Vec::new();
92 let mut result: Result<()> = Ok(());
93
94 ctx.book.for_each_chapter_mut(|chapter| {
95 if result.is_err() {
96 return;
97 }
98
99 debug!("Processing chapter {}", &chapter.name);
100 match process_markdown(&config, chapter) {
101 Ok(processed) => {
102 debug!("Processed chapter {}", &chapter.name);
103 if let Some(processed) = processed {
104 chapters_with_codeblocks.push(processed);
105 }
106 }
107 Err(error) => result = Err(error),
108 }
109 });
110
111 debug!("Processed chapters");
112
113 if let Some(html) = &config.html {
114 ctx.config.set("output.html", html)?;
115 }
116
117 HtmlHandlebars::new().render(ctx)?;
118
119 fs::write(
120 config.target_folder.join("playground-io.min.js"),
121 crate::js::PLAYGROUND_SCRIPT,
122 )?;
123
124 debug!("Finished rendering");
125
126 #[allow(unused_mut)]
127 let mut run_build = !chapters_with_codeblocks.is_empty();
128
129 #[cfg(debug_assertions)]
130 if env::var("MDBOOK_ANGULAR_SKIP_BUILD").is_ok() {
131 run_build = false;
132 }
133
134 if run_build {
135 build(ctx, &config, chapters_with_codeblocks)?;
136 }
137
138 debug!("Finished");
139
140 Ok(())
141 }
142}
143
144impl Default for AngularRenderer {
145 fn default() -> Self {
146 Self::new()
147 }
148}