Skip to main content

mdbook_angular/
lib.rs

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
23/// The version of mdbook-angular
24pub const MDBOOK_ANGULAR_VERSION: &str = env!("CARGO_PKG_VERSION");
25
26/// The expected version of mdbook
27///
28/// This crate can be used with any mdbook version that are semver compatible
29/// with this expected version.
30pub 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
57/// An mdbook [`Renderer`] for including live angular code samples
58pub struct AngularRenderer {}
59
60impl Renderer for AngularRenderer {
61	fn name(&self) -> &'static str {
62		"angular"
63	}
64
65	/// Prefer [`Self::render_mut`]
66	///
67	/// The [`AngularRenderer`] has to modify the book passed in with the context,
68	/// so this function has to clone the given context in order to mutate it.
69	/// Using [`Self::render_mut`] can prevent a needless copy.
70	#[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	/// Renders the given [`RenderContext`]
82	///
83	/// This function can make changes to the context, notably to edit the markdown
84	/// files to insert angular code blocks, live angular applications, and
85	/// playground tables.
86	#[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}