summary refs log tree commit diff stats
path: root/src/new/chapter.rs
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 /src/new/chapter.rs
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 'src/new/chapter.rs')
-rw-r--r--src/new/chapter.rs172
1 files changed, 125 insertions, 47 deletions
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))
 }