1use std::env;
2use std::path::PathBuf;
3use std::process::Command;
4
5use clap::Args;
6use color_eyre::eyre::{self, Result};
7use fluent_templates::Loader;
8use fs_extra::dir::CopyOptions;
9use mdbook_driver::MDBook;
10use walkdir::WalkDir;
11
12use crate::utils::{self, CurrentDir};
13use crate::{LANG_ID, LOCALES};
14
15#[must_use]
16#[derive(Args)]
17#[command(arg_required_else_help = true,
18 about = LOCALES.lookup(&LANG_ID, "build_command"))]
19pub struct Build {
20 #[arg(help = LOCALES.lookup(&LANG_ID, "build_path"))]
21 pub build_path: PathBuf,
22
23 #[arg(short, long, default_value_t = false,
24 help = LOCALES.lookup(&LANG_ID, "delete"))]
25 pub delete: bool,
26
27 #[arg(short, long, default_value_t = false,
28 help = LOCALES.lookup(&LANG_ID, "open"))]
29 pub open: bool,
30}
31
32pub fn execute(config: Build) -> Result<()> {
33 if utils::is_mdbook_dir(&config.build_path)? {
34 execute_mdbook(config)?;
35 } else {
36 execute_pandoc(config)?;
37 }
38 println!("{}", utils::locales("build_complete_msg", "👌"));
39
40 Ok(())
41}
42
43fn execute_mdbook(config: Build) -> Result<()> {
44 println!("{}", utils::locales_with_arg("build_msg", "📚", "mdBook"));
45
46 let input_mdbook_dir_path = dunce::canonicalize(&config.build_path)?;
47 tracing::info!(
48 "Input mdBook directory path: `{}`",
49 input_mdbook_dir_path.display()
50 );
51
52 let book_path = input_mdbook_dir_path.join("book");
53
54 if book_path.try_exists()? {
55 tracing::warn!("The mdBook output directory already exists and will be deleted");
56 utils::remove_file_or_dir(&book_path)?;
57 }
58
59 match MDBook::load(&input_mdbook_dir_path) {
60 Ok(mdbook) => {
61 if let Err(error) = mdbook.build() {
62 eyre::bail!("mdBook failed to build: {error}");
63 }
64 }
65 Err(error) => {
66 eyre::bail!("mdBook failed to load: {error}");
67 }
68 }
69
70 if config.delete {
71 for entry in WalkDir::new(&input_mdbook_dir_path).max_depth(1) {
72 let path = entry?.path().to_path_buf();
73
74 if path != input_mdbook_dir_path && path != book_path {
75 utils::remove_file_or_dir(path)?;
76 }
77 }
78
79 let mut options = CopyOptions::new();
80 options.copy_inside = true;
81 options.content_only = true;
82 fs_extra::dir::move_dir(&book_path, &input_mdbook_dir_path, &options)?;
83 }
84
85 if config.open {
86 let index_html_path = if config.delete {
87 input_mdbook_dir_path.join("index.html")
88 } else {
89 book_path.join("index.html")
90 };
91
92 open::that(index_html_path)?;
93 }
94
95 Ok(())
96}
97
98fn execute_pandoc(config: Build) -> Result<()> {
99 utils::ensure_executable_exists("pandoc")?;
100
101 let input_file_path;
102 let input_file_parent_path;
103 let mut in_directory = false;
104
105 if utils::is_markdown_or_txt_file(&config.build_path)? {
106 input_file_path = dunce::canonicalize(&config.build_path)?;
107 input_file_parent_path = input_file_path.parent().unwrap().to_path_buf();
108 } else if let Ok(Some(path)) =
109 utils::try_get_markdown_or_txt_file_name_in_dir(&config.build_path)
110 {
111 in_directory = true;
112
113 input_file_path = path;
114 input_file_parent_path = dunce::canonicalize(&config.build_path)?;
115 } else {
116 eyre::bail!("Invalid input path: `{}`", config.build_path.display());
117 }
118 tracing::info!("Input file path: `{}`", input_file_path.display());
119 println!("{}", utils::locales_with_arg("build_msg", "📚", "Pandoc"));
120
121 let output_epub_file_path =
122 env::current_dir()?.join(utils::read_markdown_to_epub_file_name(&input_file_path)?);
123 tracing::info!(
124 "Output epub file path: `{}`",
125 output_epub_file_path.display()
126 );
127
128 if output_epub_file_path.try_exists()? {
129 tracing::warn!("The epub output file already exists and will be deleted");
130 utils::remove_file_or_dir(&output_epub_file_path)?;
131 }
132
133 let current_dir = CurrentDir::new(&input_file_parent_path)?;
134 let output = Command::new("pandoc")
135 .arg("--from=commonmark+yaml_metadata_block")
136 .arg("--to=epub3")
137 .arg("--split-level=2")
138 .arg("--epub-title-page=false")
139 .args(["-o", output_epub_file_path.to_str().unwrap()])
140 .arg(&input_file_path)
141 .output()?;
142
143 tracing::info!("{}", simdutf8::basic::from_utf8(&output.stdout)?);
144
145 if !output.status.success() {
146 tracing::error!("{}", simdutf8::basic::from_utf8(&output.stderr)?);
147 eyre::bail!("`pandoc` failed to execute");
148 }
149
150 if config.delete {
151 if in_directory {
152 current_dir.restore()?;
154 utils::remove_file_or_dir(input_file_parent_path)?;
155 } else {
156 let images = utils::read_markdown_to_images(&input_file_path)?;
157 utils::remove_file_or_dir_all(&images)?;
158
159 utils::remove_file_or_dir(input_file_path)?;
160
161 current_dir.restore()?;
162 }
163 }
164
165 if config.open {
166 open::that(output_epub_file_path)?;
167 }
168
169 Ok(())
170}