mdbook_angular/
config.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::Context;
4use mdbook::renderer::RenderContext;
5use serde::Deserialize;
6use toml::value::Table;
7
8use crate::Result;
9
10#[derive(Deserialize, PartialEq, Eq, Debug, Default, Clone, Copy)]
11#[serde(rename_all = "lowercase")]
12pub enum Builder {
13	/// Build all chapters in a single angular build.
14	///
15	/// This is fast, but uses internal Angular APIs to use the currently
16	/// experimental "application" builder Angular provides as of 16.2.0.
17	#[default]
18	Foreground,
19	Experimental,
20	/// Build via [`Builder::Experimental`] in a background process.
21	///
22	/// This allows the angular process to keep running after the renderer exits.
23	/// This builder option enables watching, which significantly speeds up
24	/// rebuilds.
25	///
26	/// This option is not supported on Windows, where this option is considered
27	/// an alias to [`Builder::Experimental`].
28	Background,
29	/// Build every chapter as a separate angular application.
30	///
31	/// This uses stable Angular APIs and should work for Angular 14.0.0 and up.
32	Slow,
33}
34
35#[derive(Deserialize)]
36#[serde(rename_all = "kebab-case", deny_unknown_fields)]
37struct DeConfig {
38	#[allow(unused)] // the command option is defined by mdbook
39	command: Option<String>,
40
41	#[serde(default)]
42	builder: Builder,
43	collapsed: Option<bool>,
44	playgrounds: Option<bool>,
45	tsconfig: Option<PathBuf>,
46	inline_style_language: Option<String>,
47	optimize: Option<bool>,
48	polyfills: Option<Vec<String>>,
49	workdir: Option<String>,
50
51	html: Option<Table>,
52}
53
54/// Configuration for mdbook-angular
55pub struct Config {
56	/// Builder to use to compile the angular code
57	///
58	/// Default value: [`Builder::Experimental`]
59	pub builder: Builder,
60	/// Whether code blocks should be collapsed by default
61	///
62	/// This can be overridden via `collapsed` or `uncollapsed` tag on every
63	/// individual code block or `{{#angular}}` tag
64	///
65	/// Note this only takes effect on code blocks tagged with "angular", it
66	/// doesn't affect other code blocks.
67	///
68	/// Default value: `false`
69	pub collapsed: bool,
70	/// Whether playgrounds are enabled by default
71	///
72	/// This can be overridden via `playground` or `no-playground` tag on every
73	/// individual code block or `{{#angular}}` tag.
74	///
75	/// Default value: `true`
76	pub playgrounds: bool,
77	/// Path to a tsconfig to use for building, relative to the `book.toml` file
78	pub tsconfig: Option<PathBuf>,
79	/// The inline style language the angular compiler should use
80	///
81	/// Default value: `"css"`
82	pub inline_style_language: String,
83	/// Whether to optimize the angular applications
84	///
85	/// This option is ignored if background is active
86	///
87	/// Default value: `false`
88	pub optimize: bool,
89	/// Polyfills to import, if any
90	///
91	/// Note: zone.js is always included as polyfill.
92	///
93	/// This only supports bare specifiers, you can't add relative imports here.
94	pub polyfills: Vec<String>,
95
96	/// Configuration to pass to the HTML renderer
97	///
98	/// Use this intead of the `output.html` table itself to configure the HTML
99	/// renderer without having mdbook run the HTML renderer standalone.
100	pub html: Option<Table>,
101
102	pub(crate) book_source_folder: PathBuf,
103	pub(crate) book_theme_folder: PathBuf,
104	pub(crate) angular_root_folder: PathBuf,
105	pub(crate) target_folder: PathBuf,
106}
107
108impl Config {
109	/// Read mdbook-angular [`Config`] from the `book.toml` file inside the given folder.
110	///
111	/// # Errors
112	///
113	/// This function will return an error if reading the `book.toml` fails or if
114	/// the book contains an invalid configuration.
115	pub fn read<P: AsRef<Path>>(root: P) -> Result<Self> {
116		let root = root.as_ref();
117		let mut cfg =
118			mdbook::Config::from_disk(root.join("book.toml")).context("Error reading book.toml")?;
119		cfg.update_from_env();
120
121		Self::from_config(
122			&cfg,
123			root,
124			// Incorrect if there are multiple backends, but... good enough?
125			root.join(&cfg.build.build_dir),
126		)
127	}
128
129	/// Create mdbook-angular configuration [`Config`] from the given render context.
130	///
131	/// # Errors
132	///
133	/// This function fails if the context contains an invalid configuration.
134	pub fn new(ctx: &RenderContext) -> Result<Self> {
135		Self::from_config(&ctx.config, &ctx.root, ctx.destination.clone())
136	}
137
138	fn from_config(config: &mdbook::Config, root: &Path, destination: PathBuf) -> Result<Self> {
139		let angular_renderer_config = config
140			.get_renderer("angular")
141			.map_or_else(Default::default, ToOwned::to_owned);
142		let de_config: DeConfig = toml::Value::from(angular_renderer_config)
143			.try_into()
144			.context("Failed to parse mdbook-angular configuration")?;
145
146		let book_source_folder = root.join(&config.book.src);
147		let book_theme_folder = book_source_folder.join("../theme");
148
149		let angular_root_folder =
150			PathBuf::from(de_config.workdir.unwrap_or("mdbook_angular".to_owned()));
151		let angular_root_folder = if angular_root_folder.is_absolute() {
152			angular_root_folder
153		} else {
154			root.join(angular_root_folder)
155		};
156
157		let target_folder = destination;
158
159		Ok(Config {
160			builder: de_config.builder,
161			collapsed: de_config.collapsed.unwrap_or(false),
162			playgrounds: de_config.playgrounds.unwrap_or(true),
163			tsconfig: de_config.tsconfig.map(|tsconfig| root.join(tsconfig)),
164			inline_style_language: de_config.inline_style_language.unwrap_or("css".to_owned()),
165			optimize: de_config.optimize.unwrap_or(false),
166			polyfills: de_config.polyfills.unwrap_or_default(),
167
168			html: de_config.html,
169
170			book_source_folder,
171			book_theme_folder,
172			angular_root_folder,
173			target_folder,
174		})
175	}
176}