1use std::path::{Path, PathBuf};
2
3use anyhow::{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 #[default]
18 Foreground,
19 Experimental,
20 Background,
29 Slow,
33}
34
35#[derive(Deserialize)]
36#[serde(rename_all = "kebab-case", deny_unknown_fields)]
37struct DeConfig {
38 #[allow(unused)] 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 zoneless: Option<bool>,
49 polyfills: Option<Vec<String>>,
50 workdir: Option<String>,
51
52 html: Option<Table>,
53}
54
55#[allow(clippy::struct_excessive_bools)]
57pub struct Config {
58 pub builder: Builder,
62 pub collapsed: bool,
72 pub playgrounds: bool,
79 pub tsconfig: Option<PathBuf>,
81 pub inline_style_language: String,
85 pub optimize: bool,
91 pub zoneless: bool,
97 pub polyfills: Vec<String>,
103
104 pub html: Option<Table>,
109
110 pub(crate) book_source_folder: PathBuf,
111 pub(crate) book_theme_folder: PathBuf,
112 pub(crate) angular_root_folder: PathBuf,
113 pub(crate) target_folder: PathBuf,
114}
115
116impl Config {
117 pub fn read<P: AsRef<Path>>(root: P) -> Result<Self> {
124 let root = root.as_ref();
125 let mut cfg =
126 mdbook::Config::from_disk(root.join("book.toml")).context("Error reading book.toml")?;
127 cfg.update_from_env();
128
129 Self::from_config(
130 &cfg,
131 root,
132 root.join(&cfg.build.build_dir),
134 )
135 }
136
137 pub fn new(ctx: &RenderContext) -> Result<Self> {
143 Self::from_config(&ctx.config, &ctx.root, ctx.destination.clone())
144 }
145
146 fn from_config(config: &mdbook::Config, root: &Path, destination: PathBuf) -> Result<Self> {
147 let angular_renderer_config = config
148 .get_renderer("angular")
149 .map_or_else(Default::default, ToOwned::to_owned);
150 let de_config: DeConfig = toml::Value::from(angular_renderer_config)
151 .try_into()
152 .context("Failed to parse mdbook-angular configuration")?;
153
154 let book_source_folder = root.join(&config.book.src);
155 let book_theme_folder = book_source_folder.join("../theme");
156
157 let angular_root_folder =
158 PathBuf::from(de_config.workdir.unwrap_or("mdbook_angular".to_owned()));
159 let angular_root_folder = if angular_root_folder.is_absolute() {
160 angular_root_folder
161 } else {
162 root.join(angular_root_folder)
163 };
164
165 let target_folder = destination;
166
167 let zoneless = de_config.zoneless.unwrap_or(false);
168 let mut polyfills = de_config.polyfills.unwrap_or_default();
169
170 let zone_polyfill = "zone.js".to_owned();
171 let has_zone_polyfill = polyfills.contains(&zone_polyfill);
172 if zoneless && has_zone_polyfill {
173 return Err(anyhow!(
174 "The zone.js polyfill cannot be included if zoneless is enabled"
175 ));
176 }
177 if !zoneless && !has_zone_polyfill {
178 polyfills.push(zone_polyfill);
179 }
180
181 Ok(Config {
182 builder: de_config.builder,
183 collapsed: de_config.collapsed.unwrap_or(false),
184 playgrounds: de_config.playgrounds.unwrap_or(true),
185 tsconfig: de_config.tsconfig.map(|tsconfig| root.join(tsconfig)),
186 inline_style_language: de_config.inline_style_language.unwrap_or("css".to_owned()),
187 optimize: de_config.optimize.unwrap_or(false),
188 zoneless,
189 polyfills,
190
191 html: de_config.html,
192
193 book_source_folder,
194 book_theme_folder,
195 angular_root_folder,
196 target_folder,
197 })
198 }
199}