From 07c2924e7abc9641df6f6bc6181c912b70904972 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Thu, 13 Jun 2024 06:40:48 +0200 Subject: feat(new): Ensure that file names are ASCII only This allows us to just query the last chapter instead of storing it. --- src/config_file.rs | 7 --- src/new/chapter.rs | 172 ++++++++++++++++++++++++++++++++++++++--------------- src/new/mod.rs | 35 +++++++++++ src/new/section.rs | 11 +++- 4 files changed, 168 insertions(+), 57 deletions(-) diff --git a/src/config_file.rs b/src/config_file.rs index 2233b36..2244893 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -2,16 +2,9 @@ use serde_derive::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] pub struct Config { - pub last_chapter: LastChapter, pub templates: Template, } -#[derive(Deserialize, Serialize)] -pub struct LastChapter { - pub user_name: String, - pub number: u32, -} - #[derive(Deserialize, Serialize)] pub struct Template { pub section: String, diff --git a/src/new/chapter.rs b/src/new/chapter.rs index 749202f..887855b 100644 --- a/src/new/chapter.rs +++ b/src/new/chapter.rs @@ -1,36 +1,130 @@ -use std::{fs, path::Path}; +use std::{fmt::Display, fs, path::Path}; -use convert_case::{Case, Casing}; +use anyhow::{Context, Result}; use crate::{ config_file::Config, file_tree::{FileTree, GeneratedFile}, }; +use super::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(); - file_tree.add_file(new_main_file(project_root, &config, &name)?); - file_tree.add_file(new_chapter_file(&config, &name, project_root)); - file_tree.add_file(new_lpm_toml_file(config, name, project_root)); + + 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 new_lpm_toml_file(mut config: Config, name: String, project_root: &Path) -> GeneratedFile { - config.last_chapter.user_name = name.to_case(Case::Snake); - config.last_chapter.number += 1; +fn get_last_chapter_name(project_root: &Path) -> anyhow::Result> { + let chapter_dirs = project_root.join("content"); - GeneratedFile::new( - project_root.join("lpm.toml"), - toml::to_string(&config).expect("We changed it ourselfes, the conversion should work"), - ) + 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) -> GeneratedFile { +fn new_chapter_file( + config: &Config, + name: &str, + project_root: &Path, + last_chapter_number: u32, +) -> GeneratedFile { let chapter_text = config .templates .chapter @@ -39,7 +133,7 @@ fn new_chapter_file(config: &Config, name: &str, project_root: &Path) -> Generat GeneratedFile::new( project_root .join("content") - .join(format! {"{:02}_{}", config.last_chapter.number + 1, name.to_case(Case::Snake)}) + .join(ChapterName::from_str(name, last_chapter_number).to_string()) .join("chapter.tex"), chapter_text, ) @@ -49,60 +143,44 @@ fn new_main_file( project_root: &Path, config: &Config, name: &str, + last_chapter_number: u32, + last_chapter_name: MangledName, ) -> anyhow::Result { - let mut main_text = fs::read_to_string(project_root.join("main.tex"))?; + 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 &config.last_chapter.user_name == "static" && config.last_chapter.number == 0 { + 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{}", - &format!( - "\\includeonly{{content/{}/{}}}", - &format!( - "{:02}_{}", - config.last_chapter.number + 1, - &name.to_case(Case::Snake) - ), - "chapter.tex", - ), - ) + main_text = main_text.replace("\\includeonly{}", &chapter_includeonly) } else { main_text = main_text.replace( &format!( "\\includeonly{{content/{}/{}}}", - &format!( - "{:02}_{}", - config.last_chapter.number, &config.last_chapter.user_name - ), - "chapter.tex", - ), - &format!( - "\\includeonly{{content/{}/{}}}", - &format!( - "{:02}_{}", - config.last_chapter.number + 1, - &name.to_case(Case::Snake) - ), + 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 ", - &format!( - "{:02}_{}", - config.last_chapter.number + 1, - &name.to_case(Case::Snake) - ), + ChapterName::from_str(name, last_chapter_number).to_string(), "chapter.tex", ), ); - Ok(GeneratedFile::new(project_root.join("main.tex"), main_text)) + Ok(GeneratedFile::new(main_path, main_text)) } diff --git a/src/new/mod.rs b/src/new/mod.rs index a85187c..8d8193c 100644 --- a/src/new/mod.rs +++ b/src/new/mod.rs @@ -1,2 +1,37 @@ +use std::fmt::Display; + +use convert_case::{Case, Casing}; +use deunicode::deunicode; + pub mod chapter; pub mod section; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct MangledName(String); + +impl MangledName { + pub fn new(name: &str) -> Self { + let ascii_name = deunicode(&name); + Self(ascii_name.to_case(Case::Snake)) + } + + pub fn from_str_unsafe(name: &str) -> Self { + Self(name.to_owned()) + } + + pub fn check_mangled(name: &str) -> bool { + let mangled = Self::new(name); + let normal = Self::from_str_unsafe(name); + + mangled == normal + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} +impl Display for MangledName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} diff --git a/src/new/section.rs b/src/new/section.rs index a359fb0..86d347c 100644 --- a/src/new/section.rs +++ b/src/new/section.rs @@ -1,4 +1,8 @@ -use std::{fs, path::Path, time::SystemTime}; +use std::{ + fs, + path::Path, + time::{SystemTime, UNIX_EPOCH}, +}; use anyhow::Context; use chrono::{DateTime, Local}; @@ -8,6 +12,7 @@ use log::debug; use crate::{ config_file::Config, file_tree::{FileTree, GeneratedFile}, + new::MangledName, }; pub fn generate_new_section( @@ -37,7 +42,7 @@ pub fn generate_new_section( let new_section_file = GeneratedFile::new( chapter_root .join("sections") - .join(format!("{}.tex", name.to_case(Case::Snake))), + .join(format!("{}.tex", MangledName::new(&name))), new_section_text, ); file_tree.add_file(new_section_file); @@ -53,7 +58,7 @@ pub fn generate_new_section( chapter_file_text.push_str(&format!( "\\input{{content/{}/sections/{}}}\n", chapter_name, - &name.to_case(Case::Snake) + &MangledName::new(&name) )); let chapter_file = GeneratedFile::new(chapter_file_path, chapter_file_text); -- cgit 1.4.1