summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-06-13 06:40:48 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-06-13 06:40:48 +0200
commit07c2924e7abc9641df6f6bc6181c912b70904972 (patch)
treec006b1f66588c1b4e6f1a96c7d6be24c1a2d87a4
parentfeat(cli): Switch to stderrlog, configured by cli options (diff)
downloadlpm-07c2924e7abc9641df6f6bc6181c912b70904972.tar.gz
lpm-07c2924e7abc9641df6f6bc6181c912b70904972.zip
feat(new): Ensure that file names are ASCII only
This allows us to just query the last chapter instead of storing it.
Diffstat (limited to '')
-rw-r--r--src/config_file.rs7
-rw-r--r--src/new/chapter.rs172
-rw-r--r--src/new/mod.rs35
-rw-r--r--src/new/section.rs11
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,17 +2,10 @@ 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,
     pub chapter: 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<FileTree> {
     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<Option<ChapterName>> {
+    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<Result<String>> {
+            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::<Result<Vec<String>>>()?;
+
+    // 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<GeneratedFile> {
-    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);