kindling/lib.rs
1//! kindling: Kindle MOBI/AZW3 builder library.
2//!
3//! This crate provides EPUB-to-MOBI/AZW3 conversion, a comic pipeline,
4//! KDP pre-flight validation, and post-build MOBI readback checks.
5//! The same functionality is exposed through the `kindling-cli` binary,
6//! which is a thin wrapper around these modules.
7
8// Public API
9pub mod comic;
10pub mod epub;
11pub mod extracted;
12pub mod kdp_rules;
13pub mod mobi;
14pub mod mobi_check;
15pub mod mobi_dump;
16pub mod mobi_rewrite;
17pub mod opf;
18pub mod profile;
19pub mod repair;
20pub mod validate;
21
22// Internal implementation modules, visible inside the crate only.
23pub(crate) mod cbr;
24pub(crate) mod checks;
25pub(crate) mod cncx;
26pub(crate) mod exth;
27pub(crate) mod html_check;
28pub(crate) mod indx;
29pub(crate) mod kf8;
30pub(crate) mod moire;
31pub(crate) mod palmdoc;
32pub(crate) mod vwi;
33
34#[cfg(test)]
35mod tests;
36
37use std::path::Path;
38
39use crate::extracted::ExtractedEpub;
40
41/// Fallback author written to EXTH 100 when the input OPF or ComicInfo.xml
42/// does not supply a creator. Used anywhere kindling needs a non-empty author
43/// string: dictionary EXTH, book EXTH, and the comic pipeline. Kept as a
44/// single crate-wide constant so the displayed author on a Kindle home screen
45/// is consistent regardless of which entry point produced the file.
46pub const DEFAULT_AUTHOR: &str = "kindling";
47
48/// Pre-flight KDP validation used by `do_build`. Returns `Err(error_count)`.
49///
50/// Path-based entry point: parses the OPF into an [`ExtractedEpub`] and
51/// then delegates to [`run_preflight_validation_on_extracted`]. Callers
52/// that already have an `ExtractedEpub` (e.g. the CLI build flow that
53/// then hands the same instance to `mobi::build_mobi_from_extracted`)
54/// should use the `_on_extracted` variant directly so the OPF is not
55/// parsed twice.
56pub fn run_preflight_validation(opf_path: &Path, no_validate: bool) -> Result<(), usize> {
57 if no_validate {
58 println!("Skipping KDP validation (--no-validate)");
59 return Ok(());
60 }
61
62 let epub = match ExtractedEpub::from_opf_path(opf_path) {
63 Ok(e) => e,
64 Err(e) => {
65 // Match the original "couldn't parse" warning path exactly:
66 // print the path-aware warning and treat as a soft pass so the
67 // build can proceed and surface a more useful error itself.
68 println!(
69 "Validating {} against Kindle Publishing Guidelines v{}",
70 opf_path.display(),
71 kdp_rules::KPG_VERSION
72 );
73 eprintln!(
74 "Warning: could not parse OPF for pre-flight validation ({}): {}",
75 opf_path.display(),
76 e
77 );
78 return Ok(());
79 }
80 };
81
82 run_preflight_validation_on_extracted(&epub, no_validate)
83}
84
85/// Pre-flight KDP validation against an already-parsed [`ExtractedEpub`].
86///
87/// Identical behavior to [`run_preflight_validation`] (including the
88/// header line, per-finding output, summary line, error/warning return
89/// semantics, and `--no-validate` short-circuit), but does not re-parse
90/// the OPF. Use this whenever the caller has already constructed an
91/// `ExtractedEpub` it intends to reuse for the subsequent build.
92pub fn run_preflight_validation_on_extracted(
93 epub: &ExtractedEpub,
94 no_validate: bool,
95) -> Result<(), usize> {
96 if no_validate {
97 println!("Skipping KDP validation (--no-validate)");
98 return Ok(());
99 }
100
101 println!(
102 "Validating {} against Kindle Publishing Guidelines v{}",
103 epub.opf_path.display(),
104 kdp_rules::KPG_VERSION
105 );
106
107 let report = validate::validate(epub);
108
109 for finding in &report.findings {
110 println!("{}", finding);
111 }
112
113 let errors = report.error_count();
114 let warnings = report.warning_count();
115 let infos = report.info_count();
116 println!("{} errors, {} warnings, {} info", errors, warnings, infos);
117
118 if errors > 0 {
119 return Err(errors);
120 }
121 if warnings > 0 {
122 println!("Validation passed with {} warnings", warnings);
123 }
124 Ok(())
125}