use std::{fmt::Display, fs, path::Path}; use anyhow::{Context, Result}; use crate::{ config_file::Config, file_tree::{FileTree, GeneratedFile}, }; use super::{replacement::untemplatize_chapter, MangledName}; pub struct ChapterName { name: MangledName, number: u32, } impl Display for ChapterName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{:02}_{}", self.number, self.name)) } } impl ChapterName { pub fn from_str(name: &str, last_chapter_number: u32) -> Self { let name = MangledName::new(name); Self { name, number: last_chapter_number + 1, } } pub fn new(name: MangledName, last_chapter_number: u32) -> Self { Self { name, number: last_chapter_number + 1, } } pub fn to_components(self) -> (MangledName, u32) { (self.name, self.number) } fn from_components(name: MangledName, number: u32) -> Self { Self { name, number } } } pub fn generate_new_chapter( config: Config, project_root: &Path, name: String, ) -> anyhow::Result { let mut file_tree = FileTree::new(); let (last_chapter_name, last_chapter_number) = get_last_chapter_name(project_root) .context("Failed to get information on last chapter")? .unwrap_or(ChapterName { name: MangledName::from_str_unsafe("static"), number: 0, }) .to_components(); file_tree.add_file(new_main_file( project_root, &config, &name, last_chapter_number, last_chapter_name, )?); file_tree.add_file(new_chapter_file( &config, &name, project_root, last_chapter_number, )); Ok(file_tree) } fn get_last_chapter_name(project_root: &Path) -> anyhow::Result> { let chapter_dirs = project_root.join("content"); let mut chapter_names = fs::read_dir(chapter_dirs)? .filter_map(|path| -> Option> { let path = match path.context("Failed to read a path") { Ok(ok) => ok, Err(err) => return Some(Err(err)), }; let os_file_name = path.file_name(); let file_name = os_file_name .to_str() .expect("All chapter should be converted to ascii"); if file_name == "static" { None } else { Some(Ok(file_name.to_owned())) } }) .collect::>>()?; // There are no chapters, besides the default `static` one, which was sorted out if chapter_names.is_empty() { return Ok(None); } // The names are prefixed with a number chapter_names.sort(); let raw_components = chapter_names[chapter_names.len() - 1] .split_once('_') .expect("Exits"); let number: u32 = raw_components.0.parse().expect("Will be a number"); // The name is already mangled assert!(MangledName::check_mangled(raw_components.1)); let name: MangledName = MangledName::from_str_unsafe(raw_components.1); Ok(Some(ChapterName::from_components(name, number))) } fn new_chapter_file( config: &Config, name: &str, project_root: &Path, last_chapter_number: u32, ) -> GeneratedFile { let chapter_text = untemplatize_chapter(&config.templates.chapter, &name); GeneratedFile::new( project_root .join("content") .join(ChapterName::from_str(name, last_chapter_number).to_string()) .join("chapter.tex"), chapter_text, ) } fn new_main_file( project_root: &Path, config: &Config, name: &str, last_chapter_number: u32, last_chapter_name: MangledName, ) -> anyhow::Result { let main_path = project_root.join(&config.main_file); let mut main_text = fs::read_to_string(&main_path)?; let chapter_includeonly: String = format!( "\\includeonly{{content/{}/{}}}", ChapterName::from_str(name, last_chapter_number).to_string(), "chapter.tex", ); if &last_chapter_name.as_str() == &"static" && last_chapter_number == 0 { // This is the first added chapter; The `\includeonly` will be empty. main_text = main_text.replace("\\includeonly{}", &chapter_includeonly) } else { main_text = main_text.replace( &format!( "\\includeonly{{content/{}/{}}}", ChapterName::new(last_chapter_name, last_chapter_number - 1).to_string(), "chapter.tex", ), &chapter_includeonly, ) }; let find_index = main_text .find("% NEXT_CHAPTER") .expect("The % NEXT_CHAPTER maker must exist"); main_text.insert_str( find_index, &format!( "\\include{{content/{}/{}}}\n ", ChapterName::from_str(name, last_chapter_number).to_string(), "chapter.tex", ), ); Ok(GeneratedFile::new(main_path, main_text)) }