use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use serde::{Deserialize, Serialize};
use crate::{BookyardConfig, BookyardError, Result, build_catalog, error::io_error};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildPlan {
pub output_dir: PathBuf,
pub items: Vec<BuildItem>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildItem {
pub id: String,
pub source_dir: PathBuf,
pub dest_dir: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuildOptions {
pub run_mdbook: bool,
}
impl Default for BuildOptions {
fn default() -> Self {
Self { run_mdbook: true }
}
}
pub fn create_build_plan(root: &Path, config: &BookyardConfig) -> Result<BuildPlan> {
config.validate()?;
let output_dir = root.join(&config.workspace.output);
let items = config
.books
.iter()
.map(|book| BuildItem {
id: book.id.clone(),
source_dir: root.join(&book.source),
dest_dir: output_dir.join("books").join(&book.id),
})
.collect();
Ok(BuildPlan { output_dir, items })
}
pub fn build_shelf(
root: &Path,
config: &BookyardConfig,
options: &BuildOptions,
) -> Result<BuildPlan> {
let plan = create_build_plan(root, config)?;
fs::create_dir_all(&plan.output_dir).map_err(|source| io_error(&plan.output_dir, source))?;
fs::create_dir_all(plan.output_dir.join("books"))
.map_err(|source| io_error(plan.output_dir.join("books"), source))?;
for item in &plan.items {
if options.run_mdbook {
build_mdbook(item)?;
} else {
fs::create_dir_all(&item.dest_dir)
.map_err(|source| io_error(&item.dest_dir, source))?;
}
}
let catalog = build_catalog(config);
let catalog_json = serde_json::to_string_pretty(&catalog).expect("catalog is serializable");
let catalog_path = plan.output_dir.join("catalog.json");
fs::write(&catalog_path, catalog_json).map_err(|source| io_error(catalog_path, source))?;
Ok(plan)
}
fn build_mdbook(item: &BuildItem) -> Result<()> {
let status = Command::new("mdbook")
.arg("build")
.arg(&item.source_dir)
.arg("--dest-dir")
.arg(&item.dest_dir)
.status()
.map_err(|source| io_error("mdbook", source))?;
if status.success() {
Ok(())
} else {
Err(BookyardError::MdBookBuildFailed {
id: item.id.clone(),
status: status.to_string(),
})
}
}