1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#![doc = include_str!("../README.md")]

mod angular;
pub(crate) mod codeblock;
pub(crate) mod config;
mod js;
mod markdown;
mod utils;

/// The version of mdbook-angular
pub const MDBOOK_ANGULAR_VERSION: &str = env!("CARGO_PKG_VERSION");

/// The expected version of mdbook
///
/// This crate can be used with any mdbook version that are semver compatible
/// with this expected version.
pub const EXPECTED_MDBOOK_VERSION: &str = mdbook::MDBOOK_VERSION;

pub use angular::stop_background_process;
pub use config::Config;

use angular::build;
use log::debug;
use markdown::process_markdown;
use markdown::ChapterWithCodeBlocks;
use mdbook::{
	renderer::{HtmlHandlebars, RenderContext},
	BookItem, Renderer,
};

fn validate_version(ctx: &RenderContext) -> Result<()> {
	let req = semver::VersionReq::parse(EXPECTED_MDBOOK_VERSION).unwrap();

	if semver::Version::parse(&ctx.version).map_or(false, |version| req.matches(&version)) {
		Ok(())
	} else {
		bail!("Invalid mdbook version {}, expected {}", &ctx.version, req);
	}
}

pub(crate) use anyhow::{bail, Context, Error, Result};

/// An mdbook [`Renderer`] for including live angular code samples
pub struct AngularRenderer {}

impl Renderer for AngularRenderer {
	fn name(&self) -> &str {
		"angular"
	}

	/// Prefer [`Self::render_mut`]
	///
	/// The [`AngularRenderer`] has to modify the book passed in with the context,
	/// so this function has to clone the given context in order to mutate it.
	/// Using [`Self::render_mut`] can prevent a needless copy.
	#[inline]
	fn render(&self, ctx: &RenderContext) -> Result<()> {
		self.render_mut(&mut ctx.clone())
	}
}

impl AngularRenderer {
	pub fn new() -> Self {
		Self {}
	}

	/// Renders the given [`RenderContext`]
	///
	/// This function can make changes to the context, notably to edit the markdown
	/// files to insert angular code blocks, live angular applications, and
	/// playground tables.
	#[allow(clippy::missing_errors_doc)]
	pub fn render_mut(&self, ctx: &mut RenderContext) -> Result<()> {
		validate_version(ctx)?;

		let config = Config::new(ctx)?;

		let mut chapters_with_codeblocks = Vec::new();
		let mut result: Result<()> = Ok(());

		ctx.book.for_each_mut(|item| {
			if result.is_err() {
				return;
			}

			if let BookItem::Chapter(chapter) = item {
				debug!("Processing chapter {}", &chapter.name);
				match process_markdown(&config, chapter) {
					Ok(processed) => {
						debug!("Processed chapter {}", &chapter.name);
						if let Some(processed) = processed {
							chapters_with_codeblocks.push(processed);
						}
					}
					Err(error) => result = Err(error),
				};
			}
		});

		debug!("Processed chapters");

		HtmlHandlebars::new().render(ctx)?;

		debug!("Finished rendering");

		if !chapters_with_codeblocks.is_empty() {
			build(ctx, &config, chapters_with_codeblocks)?;
		}

		debug!("Finished");

		Ok(())
	}
}

impl Default for AngularRenderer {
	fn default() -> Self {
		Self::new()
	}
}