use std::path::{Path, PathBuf};
use super::{Config, BEST_PRACTICE_TEMPLATE, TARGET_CHAPTERS_DIR, TARGET_TEMPLATE_DIR};
impl Config {
pub fn get_chapter_full_file_name(
&self,
chapter: &mdbook_renderer::book::Chapter,
ctx: &mdbook_renderer::RenderContext,
) -> Option<PathBuf> {
let chapter_dir = self.get_chapters_dir(ctx);
match &chapter.source_path {
None => None,
Some(source_path) => {
let chapter_file = chapter_dir.join(source_path.with_extension("typ"));
Some(chapter_file)
}
}
}
pub fn get_chapter_relative_chapter_file_name(
&self,
chapter: &mdbook_renderer::book::Chapter,
_ctx: &mdbook_renderer::RenderContext,
) -> Option<PathBuf> {
let chapter_dir = PathBuf::from(TARGET_CHAPTERS_DIR);
match &chapter.source_path {
None => None,
Some(source_path) => {
let chapter_file = chapter_dir.join(source_path.with_extension("typ")); Some(chapter_file)
}
}
}
pub fn append_chapter_to_typst_output(
&self,
ctx: &mdbook_renderer::RenderContext,
typst_output: &mut String,
) -> anyhow::Result<()> {
let book = &ctx.book;
let mut typ_content = Vec::new();
for item in book.items.iter() {
self.process_book_item(item, &mut typ_content, ctx)?;
}
for line in typ_content {
typst_output.push_str(&format!("{}\n", line));
}
Ok(())
}
fn process_book_item(
&self,
item: &mdbook_renderer::book::BookItem,
typ_content: &mut Vec<String>,
ctx: &mdbook_renderer::RenderContext,
) -> anyhow::Result<()> {
match item {
mdbook_renderer::book::BookItem::Chapter(chapter) => {
if let Some(chapter_path) =
self.get_chapter_relative_chapter_file_name(chapter, ctx)
{
log::debug!(
"Including chapter: {} with path: {}",
chapter.name,
chapter_path.display()
);
typ_content.push(format!("#include \"{}\"", chapter_path.display()));
}
for sub_item in &chapter.sub_items {
self.process_book_item(sub_item, typ_content, ctx)?;
}
}
mdbook_renderer::book::BookItem::Separator => {
log::info!("Skipping separator in book structure");
}
mdbook_renderer::book::BookItem::PartTitle(title) => {
log::info!("Adding part title: {}", title);
typ_content.push(format!("// Part: {}", title));
}
}
Ok(())
}
pub fn convert_book(
&self,
_chapter_file_list: &mut [PathBuf],
ctx: &mdbook_renderer::RenderContext,
) -> anyhow::Result<()> {
let target_template_dir = self.get_typst_templates_dir(ctx);
if !target_template_dir.exists() {
return Err(anyhow::anyhow!(
"template directory {} not found",
target_template_dir.display()
));
}
if self.templates.is_empty() {
let mut typst_output = String::new();
self.append_chapter_to_typst_output(ctx, &mut typst_output)?;
self.write_typst_file(ctx, &typst_output, None)?;
} else {
for (name, file_name) in &self.templates {
let mut typst_output = String::new();
let file_name = Path::new(file_name).file_name().unwrap().to_str().unwrap();
let dst = target_template_dir.join(file_name);
if !dst.exists() || !dst.is_file() {
return Err(anyhow::anyhow!("template file {} not found", dst.display()));
}
typst_output.push_str(&format!(
"#import \"{}/{}\": {}\n",
TARGET_TEMPLATE_DIR, file_name, BEST_PRACTICE_TEMPLATE
));
typst_output.push_str("\n\n//Document Metadata\n");
typst_output.push_str("#let metadata = (\n");
for (key, value) in &self.template_parameters {
let value = value.replace("\"", "\\\"");
let value = value.replace("\n", "\\n");
typst_output.push_str(&format!(" {}: \"{}\",\n", key, value));
}
typst_output.push_str(")\n");
typst_output.push_str(&format!("#show: doc => {}(\n", BEST_PRACTICE_TEMPLATE));
for key in self.template_parameters.keys() {
typst_output.push_str(&format!(" {}: metadata.{},\n", key, key));
}
typst_output.push_str(" doc\n");
typst_output.push_str(")\n\n");
self.append_chapter_to_typst_output(ctx, &mut typst_output)?;
self.write_typst_file(ctx, &typst_output, Some(name))?;
}
}
Ok(())
}
pub fn write_typst_file(
&self,
ctx: &mdbook_renderer::RenderContext,
typst_output: &str,
template_name: Option<&str>,
) -> anyhow::Result<()> {
let book_name = self.get_book_name(template_name, ctx);
let typst_dir = self.get_typst_dir(ctx);
let output_file = typst_dir.join(format!("{}.typ", book_name));
std::fs::write(output_file, typst_output)
.map_err(|e| anyhow::anyhow!("failed to write typst file:{}", e))
}
pub fn convert_book_to_pdf(&self, ctx: &mdbook_renderer::RenderContext) -> anyhow::Result<()> {
if self.templates.is_empty() {
self.invoke_typst_command(ctx, None)?;
} else {
for name in self.templates.keys() {
self.invoke_typst_command(ctx, Some(name))?;
}
}
Ok(())
}
pub fn invoke_typst_command(
&self,
ctx: &mdbook_renderer::RenderContext,
template_name: Option<&str>,
) -> anyhow::Result<()> {
let book_name = self.get_book_name(template_name, ctx);
let typst_dir = self.get_typst_dir(ctx);
let typst_file = typst_dir.join(format!("{}.typ", book_name));
if !typst_file.exists() || !typst_file.is_file() {
return Err(anyhow::anyhow!(
"typst file {} not found",
typst_file.display()
));
}
let pdf_dir = self.get_pdf_dir(ctx);
if !pdf_dir.exists() {
std::fs::create_dir_all(&pdf_dir)?;
}
let output_file = pdf_dir.join(format!("{}.pdf", book_name));
let status = std::process::Command::new("typst")
.arg("compile")
.arg(&typst_file)
.arg(&output_file)
.status()?;
if !status.success() {
return Err(anyhow::anyhow!(
"failed to convert typst file:{} to pdf",
typst_file.display()
));
}
log::info!(
"converted typst file:{} to pdf:{}",
typst_file.display(),
output_file.display()
);
Ok(())
}
}