use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
const DEFAULT_ENABLED_PLUGINS: &[&str] = &[
"collapsible-chapters",
"back-to-top-button",
"mermaid-md-adoc",
"fontsettings",
];
#[derive(Debug, Clone, Deserialize, Default)]
pub struct BookConfig {
#[serde(default)]
pub title: String,
#[serde(default)]
pub plugins: Vec<String>,
#[serde(default)]
pub styles: HashMap<String, String>,
#[serde(default)]
pub variables: HashMap<String, serde_json::Value>,
#[serde(default)]
pub hardbreaks: bool,
#[serde(default)]
pub math: bool,
#[serde(default)]
pub externalize_svg: Option<bool>,
#[serde(default)]
pub inline_svg: Option<bool>,
#[serde(default, rename = "fetchRemoteImages")]
pub fetch_remote_images: bool,
#[serde(default)]
pub openapi: Option<OpenApiConfig>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum OpenApiConfig {
Single(String),
Multiple(HashMap<String, String>),
}
impl BookConfig {
pub fn load(book_dir: &Path) -> Result<Self> {
let config_path = book_dir.join("book.json");
if !config_path.exists() {
let default_json = r#"{
"title": "My Book",
"plugins": [
"collapsible-chapters",
"back-to-top-button",
"mermaid-md-adoc",
"fontsettings"
]
}
"#;
fs::write(&config_path, default_json)?;
println!(" Created default book.json");
}
let content = fs::read_to_string(&config_path)?;
let value: serde_json::Value = serde_json::from_str(&content)?;
let config: BookConfig = serde_json::from_value(value)?;
Ok(config)
}
pub fn is_plugin_enabled(&self, name: &str) -> bool {
if self.plugins.iter().any(|p| *p == format!("-{}", name)) {
return false;
}
if self.plugins.iter().any(|p| p == name) {
return true;
}
DEFAULT_ENABLED_PLUGINS.contains(&name)
}
pub fn get_website_style(&self) -> Option<&String> {
self.styles.get("website")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_book_json() {
let json = r#"{
"title": "Test Book",
"plugins": ["back-to-top-button", "-search"],
"styles": {
"website": "styles/website.css"
}
}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.title, "Test Book");
assert!(config.is_plugin_enabled("back-to-top-button"));
assert!(!config.is_plugin_enabled("search")); assert_eq!(
config.get_website_style(),
Some(&"styles/website.css".to_string())
);
}
#[test]
fn test_default_enabled_plugins() {
let json = r#"{"title": "Test"}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(config.is_plugin_enabled("collapsible-chapters"));
assert!(config.is_plugin_enabled("back-to-top-button"));
assert!(config.is_plugin_enabled("mermaid-md-adoc"));
assert!(config.is_plugin_enabled("fontsettings"));
assert!(!config.is_plugin_enabled("some-other-plugin"));
}
#[test]
fn test_explicitly_disable_default_plugin() {
let json = r#"{"plugins": ["-collapsible-chapters"]}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(!config.is_plugin_enabled("collapsible-chapters"));
assert!(config.is_plugin_enabled("back-to-top-button"));
assert!(config.is_plugin_enabled("mermaid-md-adoc"));
assert!(config.is_plugin_enabled("fontsettings"));
}
#[test]
fn test_explicitly_disable_fontsettings() {
let json = r#"{"plugins": ["-fontsettings"]}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(!config.is_plugin_enabled("fontsettings"));
assert!(config.is_plugin_enabled("back-to-top-button"));
assert!(config.is_plugin_enabled("mermaid-md-adoc"));
assert!(config.is_plugin_enabled("collapsible-chapters"));
}
#[test]
fn test_parse_variables() {
let json = r#"{
"title": "Test Book",
"variables": {
"version": "1.0.0",
"author": "Guide Inc",
"year": 2024
}
}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.variables.get("version").unwrap(), "1.0.0");
assert_eq!(config.variables.get("author").unwrap(), "Guide Inc");
assert_eq!(config.variables.get("year").unwrap(), 2024);
}
#[test]
fn test_empty_variables() {
let json = r#"{"title": "Test"}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(config.variables.is_empty());
}
#[test]
fn test_math_enabled() {
let json = r#"{"title": "Test", "math": true}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(config.math);
}
#[test]
fn test_math_disabled_by_default() {
let json = r#"{"title": "Test"}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(!config.math);
}
#[test]
fn test_fetch_remote_images_enabled() {
let json = r#"{"title": "Test", "fetchRemoteImages": true}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(config.fetch_remote_images);
}
#[test]
fn test_fetch_remote_images_disabled_by_default() {
let json = r#"{"title": "Test"}"#;
let config: BookConfig = serde_json::from_str(json).unwrap();
assert!(!config.fetch_remote_images);
}
#[test]
fn test_duplicate_keys_allowed() {
let json = r#"{
"title": "Test",
"hardbreaks": false,
"hardbreaks": true
}"#;
let value: serde_json::Value = serde_json::from_str(json).unwrap();
let config: BookConfig = serde_json::from_value(value).unwrap();
assert!(config.hardbreaks); }
}