Skip to main content

bookyard_core/
build.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4    process::Command,
5};
6
7use serde::{Deserialize, Serialize};
8
9use crate::{BookyardConfig, BookyardError, Result, build_catalog, error::io_error};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct BuildPlan {
13    pub output_dir: PathBuf,
14    pub items: Vec<BuildItem>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct BuildItem {
19    pub id: String,
20    pub source_dir: PathBuf,
21    pub dest_dir: PathBuf,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct BuildOptions {
26    pub run_mdbook: bool,
27}
28
29impl Default for BuildOptions {
30    fn default() -> Self {
31        Self { run_mdbook: true }
32    }
33}
34
35pub fn create_build_plan(root: &Path, config: &BookyardConfig) -> Result<BuildPlan> {
36    config.validate()?;
37    let output_dir = root.join(&config.workspace.output);
38    let items = config
39        .books
40        .iter()
41        .map(|book| BuildItem {
42            id: book.id.clone(),
43            source_dir: root.join(&book.source),
44            dest_dir: output_dir.join("books").join(&book.id),
45        })
46        .collect();
47    Ok(BuildPlan { output_dir, items })
48}
49
50pub fn build_shelf(
51    root: &Path,
52    config: &BookyardConfig,
53    options: &BuildOptions,
54) -> Result<BuildPlan> {
55    let plan = create_build_plan(root, config)?;
56    fs::create_dir_all(&plan.output_dir).map_err(|source| io_error(&plan.output_dir, source))?;
57    fs::create_dir_all(plan.output_dir.join("books"))
58        .map_err(|source| io_error(plan.output_dir.join("books"), source))?;
59
60    for item in &plan.items {
61        if options.run_mdbook {
62            build_mdbook(item)?;
63        } else {
64            fs::create_dir_all(&item.dest_dir)
65                .map_err(|source| io_error(&item.dest_dir, source))?;
66        }
67    }
68
69    let catalog = build_catalog(config);
70    let catalog_json = serde_json::to_string_pretty(&catalog).expect("catalog is serializable");
71    let catalog_path = plan.output_dir.join("catalog.json");
72    fs::write(&catalog_path, catalog_json).map_err(|source| io_error(catalog_path, source))?;
73    Ok(plan)
74}
75
76fn build_mdbook(item: &BuildItem) -> Result<()> {
77    let status = Command::new("mdbook")
78        .arg("build")
79        .arg(&item.source_dir)
80        .arg("--dest-dir")
81        .arg(&item.dest_dir)
82        .status()
83        .map_err(|source| io_error("mdbook", source))?;
84    if status.success() {
85        Ok(())
86    } else {
87        Err(BookyardError::MdBookBuildFailed {
88            id: item.id.clone(),
89            status: status.to_string(),
90        })
91    }
92}