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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
use std::path::{Path, PathBuf};
use anyhow::Context;
use mdbook::renderer::RenderContext;
use serde::Deserialize;
use toml::value::Table;
use crate::Result;
#[derive(Deserialize, PartialEq, Eq, Debug, Default, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum Builder {
/// Build all chapters in a single angular build.
///
/// This is fast, but uses internal Angular APIs to use the currently
/// experimental "application" builder Angular provides as of 16.2.0.
#[default]
Experimental,
/// Build via [`Builder::Experimental`] in a background process.
///
/// This allows the angular process to keep running after the renderer exits.
/// This builder option enables watching, which significantly speeds up
/// rebuilds.
///
/// This option is not supported on Windows, where this option is considered
/// an alias to [`Builder::Experimental`].
Background,
/// Build every chapter as a separate angular application.
///
/// This uses stable Angular APIs and should work for Angular 14.0.0 and up.
Slow,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct DeConfig {
#[allow(unused)] // the command option is defined by mdbook
command: Option<String>,
#[serde(default)]
builder: Builder,
collapsed: Option<bool>,
playgrounds: Option<bool>,
tsconfig: Option<PathBuf>,
inline_style_language: Option<String>,
optimize: Option<bool>,
polyfills: Option<Vec<String>>,
workdir: Option<String>,
html: Option<Table>,
}
/// Configuration for mdbook-angular
pub struct Config {
/// Builder to use to compile the angular code
///
/// Default value: [`Builder::Experimental`]
pub builder: Builder,
/// Whether code blocks should be collapsed by default
///
/// This can be overridden via `collapsed` or `uncollapsed` tag on every
/// individual code block or `{{#angular}}` tag
///
/// Note this only takes effect on code blocks tagged with "angular", it
/// doesn't affect other code blocks.
///
/// Default value: `false`
pub collapsed: bool,
/// Whether playgrounds are enabled by default
///
/// This can be overridden via `playground` or `no-playground` tag on every
/// individual code block or `{{#angular}}` tag.
///
/// Default value: `true`
pub playgrounds: bool,
/// Path to a tsconfig to use for building, relative to the `book.toml` file
pub tsconfig: Option<PathBuf>,
/// The inline style language the angular compiler should use
///
/// Default value: `"css"`
pub inline_style_language: String,
/// Whether to optimize the angular applications
///
/// This option is ignored if background is active
///
/// Default value: `false`
pub optimize: bool,
/// Polyfills to import, if any
///
/// Note: zone.js is always included as polyfill.
///
/// This only supports bare specifiers, you can't add relative imports here.
pub polyfills: Vec<String>,
/// Configuration to pass to the HTML renderer
///
/// Use this intead of the `output.html` table itself to configure the HTML
/// renderer without having mdbook run the HTML renderer standalone.
pub html: Option<Table>,
pub(crate) book_source_folder: PathBuf,
pub(crate) book_theme_folder: PathBuf,
pub(crate) angular_root_folder: PathBuf,
pub(crate) target_folder: PathBuf,
}
impl Config {
/// Read mdbook-angular [`Config`] from the `book.toml` file inside the given folder.
///
/// # Errors
///
/// This function will return an error if reading the `book.toml` fails or if
/// the book contains an invalid configuration.
pub fn read<P: AsRef<Path>>(root: P) -> Result<Self> {
let root = root.as_ref();
let mut cfg =
mdbook::Config::from_disk(root.join("book.toml")).context("Error reading book.toml")?;
cfg.update_from_env();
Self::from_config(
&cfg,
root,
// Incorrect if there are multiple backends, but... good enough?
root.join(&cfg.build.build_dir),
)
}
/// Create mdbook-angular configuration [`Config`] from the given render context.
///
/// # Errors
///
/// This function fails if the context contains an invalid configuration.
pub fn new(ctx: &RenderContext) -> Result<Self> {
Self::from_config(&ctx.config, &ctx.root, ctx.destination.clone())
}
fn from_config(config: &mdbook::Config, root: &Path, destination: PathBuf) -> Result<Self> {
let angular_renderer_config = config
.get_renderer("angular")
.map_or_else(Default::default, ToOwned::to_owned);
let de_config: DeConfig = toml::Value::from(angular_renderer_config)
.try_into()
.context("Failed to parse mdbook-angular configuration")?;
let book_source_folder = root.join(&config.book.src);
let book_theme_folder = book_source_folder.join("../theme");
let angular_root_folder =
PathBuf::from(de_config.workdir.unwrap_or("mdbook_angular".to_owned()));
let angular_root_folder = if angular_root_folder.is_absolute() {
angular_root_folder
} else {
root.join(angular_root_folder)
};
let target_folder = destination;
Ok(Config {
builder: de_config.builder,
collapsed: de_config.collapsed.unwrap_or(false),
playgrounds: de_config.playgrounds.unwrap_or(true),
tsconfig: de_config.tsconfig.map(|tsconfig| root.join(tsconfig)),
inline_style_language: de_config.inline_style_language.unwrap_or("css".to_owned()),
optimize: de_config.optimize.unwrap_or(false),
polyfills: de_config.polyfills.unwrap_or_default(),
html: de_config.html,
book_source_folder,
book_theme_folder,
angular_root_folder,
target_folder,
})
}
}