summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-03-31 21:57:01 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-03-31 21:57:01 +0200
commit2e5e4b5736c446198e36760e254b7c17dd987166 (patch)
treeb74915864a2c80dbc0a0ebe26a52140a934f45c5 /src
parentdocs(example): Add an example directory (diff)
downloadlpm-2e5e4b5736c446198e36760e254b7c17dd987166.tar.gz
lpm-2e5e4b5736c446198e36760e254b7c17dd987166.zip
refactor(treewide): Improve code quality by working with a FileTree
The FileTree has been taken from the implementation written by my for the
Trinitrix project. It alleviates the problem, where functions had to do
many things themselves.
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs (renamed from src/command_line_interface.rs)19
-rw-r--r--src/config_file.rs19
-rw-r--r--src/data.rs27
-rw-r--r--src/file_tree/mod.rs88
-rw-r--r--src/main.rs127
-rw-r--r--src/new/chapter.rs143
-rw-r--r--src/new/mod.rs95
-rw-r--r--src/new/project.rs111
-rw-r--r--src/new/section.rs102
9 files changed, 357 insertions, 374 deletions
diff --git a/src/command_line_interface.rs b/src/cli.rs
index 5d24ae5..fe1b194 100644
--- a/src/command_line_interface.rs
+++ b/src/cli.rs
@@ -2,7 +2,7 @@ use clap::{Parser, Subcommand};
 
 /// A project manager for LaTeX
 #[derive(Parser, Debug)]
-#[clap(author, version, about, long_about = None)]
+#[command(author, version, about, long_about = None)]
 pub struct Args {
     #[command(subcommand)]
     pub cli: Command,
@@ -12,13 +12,17 @@ pub struct Args {
 pub enum Command {
     /// Generates a new part
     #[command(subcommand)]
-    New(SubCommand),
+    New(What),
 }
 
 #[derive(Subcommand, Debug)]
-pub enum SubCommand {
+pub enum What {
     /// Adds a section
     Section {
+        /// The name of the chapter to extend, can be empty when the current_dir is inside a
+        /// chapter already.
+        #[arg(long, short)]
+        chapter: Option<String>,
         /// Name of the new Section
         name: String,
     },
@@ -28,13 +32,4 @@ pub enum SubCommand {
         /// Name of the new Chapter
         name: String,
     },
-    //    /// generates a new project
-    //    Project {
-    //        /// Name of the new Project
-    //        name: String,
-    //        /// Name of the first chapter
-    //        first_chapter: String,
-    //        // /// Name of the first section
-    //        // first_section: String,
-    //    },
 }
diff --git a/src/config_file.rs b/src/config_file.rs
new file mode 100644
index 0000000..838a78d
--- /dev/null
+++ b/src/config_file.rs
@@ -0,0 +1,19 @@
+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/data.rs b/src/data.rs
deleted file mode 100644
index 72609b8..0000000
--- a/src/data.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use serde_derive::{Deserialize, Serialize};
-
-#[derive(Deserialize, Serialize)]
-pub struct Data {
-    pub last_chapter: LastChapter,
-}
-
-#[derive(Deserialize, Serialize)]
-pub struct LastChapter {
-    pub user_name: String,
-    pub number: u32,
-}
-
-//fn main() {
-//    let config: Config = toml::from_str(r#"
-//        ip = '127.0.0.1'
-//
-//        [keys]
-//        github = 'xxxxxxxxxxxxxxxxx'
-//        travis = 'yyyyyyyyyyyyyyyyy'
-//    "#).unwrap();
-//
-//    assert_eq!(config.ip, "127.0.0.1");
-//    assert_eq!(config.port, None);
-//    assert_eq!(config.keys.github, "xxxxxxxxxxxxxxxxx");
-//    assert_eq!(config.keys.travis.as_ref().unwrap(), "yyyyyyyyyyyyyyyyy");
-//}
diff --git a/src/file_tree/mod.rs b/src/file_tree/mod.rs
new file mode 100644
index 0000000..d6f0c3c
--- /dev/null
+++ b/src/file_tree/mod.rs
@@ -0,0 +1,88 @@
+/*
+* Copyright (C) 2023 - 2024:
+* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
+* SPDX-License-Identifier: LGPL-3.0-or-later
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see <https://www.gnu.org/licenses/>.
+*/
+
+//! [`FileTree`]s are the fundamental data structure used by trixy to present generated data to
+//! you.
+
+use std::{
+    fs, io,
+    path::{Path, PathBuf},
+};
+
+/// A file tree containing all files that were generated. These are separated into host and
+/// auxiliary files. See their respective descriptions about what differentiates them.
+#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FileTree {
+    /// Files, that are supposed to be included in the compiled crate.
+    pub files: Vec<GeneratedFile>,
+}
+
+/// A generated files
+#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct GeneratedFile {
+    /// The path this generated file would like to be placed at.
+    /// This path is relative to the crate root.
+    pub path: PathBuf,
+
+    /// The content of this file.
+    ///
+    /// This should already be formatted and ready to be used.
+    pub value: String,
+}
+
+impl GeneratedFile {
+    pub fn new(path: PathBuf, value: String) -> Self {
+        Self { path, value }
+    }
+    pub fn new_in_out_dir(name: String, value: String, out_dir: &Path) -> Self {
+        let path = out_dir.join(name);
+        Self { path, value }
+    }
+
+    pub fn materialize(self) -> io::Result<()> {
+        fs::create_dir_all(self.path.parent().expect("This path should have a parent"))?;
+        fs::write(self.path, self.value.as_bytes())?;
+        Ok(())
+    }
+}
+
+impl FileTree {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn add_file(&mut self, file: GeneratedFile) {
+        self.files.push(file)
+    }
+
+    pub fn extend(&mut self, files: Vec<GeneratedFile>) {
+        files.into_iter().for_each(|file| self.add_file(file));
+    }
+
+    pub fn materialize(self) -> io::Result<()> {
+        self.files
+            .into_iter()
+            .map(|file| -> io::Result<()> { file.materialize() })
+            .collect::<io::Result<()>>()?;
+        Ok(())
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index b19e7bf..8c2ea62 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,38 +1,111 @@
+use std::{env, ffi::OsString, fs, path::PathBuf};
+
+use anyhow::{bail, Context};
 use clap::Parser;
-use command_line_interface::{
-    Args,
-    Command::New,
-    SubCommand::{Chapter, Section},
+use log::debug;
+
+use crate::{
+    cli::{
+        Args,
+        Command::New,
+        What::{Chapter, Section},
+    },
+    config_file::Config,
+    new::{chapter::generate_new_chapter, section::generate_new_section},
 };
-use new::{chapter::generate_new_chapter, section::generate_new_section};
 
-pub mod command_line_interface;
-pub mod data;
+pub mod cli;
+pub mod config_file;
 pub mod new;
 
-fn main() {
+// The copyright header tells you, where this file is from.
+pub mod file_tree;
+
+fn main() -> anyhow::Result<()> {
+    env_logger::init();
     let args = Args::parse();
 
-    match args.cli {
+    let project_root = get_project_root_by_lmp_toml().context("Looking for the project root")?;
+
+    let config_file = fs::read_to_string(project_root.join("lpm.toml"))?;
+    let config: Config = toml::from_str(&config_file).context("Reading toml from string")?;
+
+    let file_tree = match args.cli {
         New(new_command) => match new_command {
-            Section { name } => generate_new_section(name).unwrap(),
-            Chapter { name } => generate_new_chapter(name).unwrap(),
-            //            Project {
-            //                name,
-            //                first_chapter,
-            //                //first_section,
-            //            } => {
-            //                let preamble_path = PathBuf::from("");
-            //                let resource_path = PathBuf::from("");
-            //                generate_new_project(
-            //                    name,
-            //                    first_chapter,
-            //                    //first_section,
-            //                    preamble_path,
-            //                    resource_path,
-            //                )
-            //                .unwrap()
-            //            }
+            Section { name, chapter } => {
+                let chapter = if let Some(val) = chapter {
+                    // The user probably has not added the preceeding chapter number to the chapter
+                    // string
+                    if val.starts_with(|c: char| c.is_numeric()) {
+                        eprintln!(
+                            "Your chapter name starts with a number, assuming \
+                            that you have already added the chapter number"
+                        );
+                        val
+                    } else {
+                        bail!(
+                            "Calculating the chapter number is not yet \
+                            implemented, please add it yourself"
+                        );
+                    }
+                } else {
+                    // The user thinks that they are already inside a chapter
+                    get_upwards_chapter()?
+                };
+
+                generate_new_section(&config, name, &project_root, &chapter)?
+            }
+            Chapter { name } => generate_new_chapter(config, &project_root, name)?,
         },
+    };
+
+    file_tree.materialize()?;
+
+    Ok(())
+}
+
+pub fn get_project_root_by_lmp_toml() -> anyhow::Result<PathBuf> {
+    let path = env::current_dir()?;
+    let mut path_ancestors = path.as_path().ancestors();
+
+    while let Some(path_segment) = path_ancestors.next() {
+        if fs::read_dir(path_segment)?.into_iter().any(|path_segment| {
+            path_segment
+                .expect("The read_dir shouldn't error out here")
+                .file_name()
+                == OsString::from("lpm.toml")
+        }) {
+            return Ok(PathBuf::from(path_segment));
+        }
     }
+    bail!("Ran out of places to find lpm.toml")
+}
+
+fn get_upwards_chapter() -> anyhow::Result<String> {
+    let current_path = env::current_dir()?;
+
+    for anc in current_path.as_path().ancestors() {
+        debug!("Reading directory {}", anc.display());
+
+        for dir in fs::read_dir(anc)? {
+            let dir = dir?;
+            debug!("Checking path: {}", dir.file_name().to_string_lossy());
+
+            if dir.file_name() == OsString::from("chapter.tex") {
+                match anc
+                    .file_name()
+                    .expect("This should always be a file")
+                    .to_str()
+                {
+                    Some(str) => return Ok(str.to_owned()),
+                    None => bail!(
+                        "Failed to convert your path ('{}') to a string!",
+                        dir.file_name().to_string_lossy()
+                    ),
+                }
+            }
+        }
+    }
+
+    bail!("Failed to get a chapter name, please specify one with the `--chapter` flag!")
 }
diff --git a/src/new/chapter.rs b/src/new/chapter.rs
index 88f2a85..749202f 100644
--- a/src/new/chapter.rs
+++ b/src/new/chapter.rs
@@ -1,67 +1,108 @@
-use std::{
-    fs::{self, File},
-    io::{self, Write},
-};
+use std::{fs, path::Path};
 
 use convert_case::{Case, Casing};
 
-use crate::data::Data;
+use crate::{
+    config_file::Config,
+    file_tree::{FileTree, GeneratedFile},
+};
 
-use super::{get_project_root, CHAPTER};
+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));
 
-pub fn generate_new_chapter(name: String) -> io::Result<()> {
-    let project_root = get_project_root().unwrap();
+    Ok(file_tree)
+}
 
-    let raw_data_file = fs::read_to_string(project_root.join("lpm.toml")).unwrap();
-    let mut data_file: Data = toml::from_str(&raw_data_file).unwrap();
-    let mut main_file = fs::read_to_string(project_root.join("main.tex")).unwrap();
+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;
 
-    fs::create_dir(project_root.join(format!("content/{}", name.to_case(Case::Snake),))).unwrap();
-    let mut new_chapter = File::create(project_root.join(format!(
-        "content/{}/chapter_{:02}.tex",
-        name.to_case(Case::Snake),
-        data_file.last_chapter.number + 1
-    )))
-    .unwrap();
-    new_chapter
-        .write_all(CHAPTER.replace("REPLACEMENT_CHAPTER", &name).as_bytes())
-        .unwrap();
+    GeneratedFile::new(
+        project_root.join("lpm.toml"),
+        toml::to_string(&config).expect("We changed it ourselfes, the conversion should work"),
+    )
+}
 
-    fs::create_dir(project_root.join(format!("content/{}/sections", name.to_case(Case::Snake),)))
-        .unwrap();
+fn new_chapter_file(config: &Config, name: &str, project_root: &Path) -> GeneratedFile {
+    let chapter_text = config
+        .templates
+        .chapter
+        .replace("REPLACEMENT_CHAPTER", &name);
 
-    main_file = main_file.replace(
-        &format!(
-            "\\includeonly{{content/{}/{}}}",
-            &data_file.last_chapter.user_name,
-            &format!("chapter_{:02}", data_file.last_chapter.number)
-        ),
-        &format!(
-            "\\includeonly{{content/{}/{}}}",
-            name.to_case(Case::Snake),
-            &format!("chapter_{:02}", data_file.last_chapter.number + 1)
-        ),
-    );
-    let find_index = main_file.find("% NEXT_CHAPTER").unwrap();
-    main_file.insert_str(
+    GeneratedFile::new(
+        project_root
+            .join("content")
+            .join(format! {"{:02}_{}", config.last_chapter.number + 1, name.to_case(Case::Snake)})
+            .join("chapter.tex"),
+        chapter_text,
+    )
+}
+
+fn new_main_file(
+    project_root: &Path,
+    config: &Config,
+    name: &str,
+) -> anyhow::Result<GeneratedFile> {
+    let mut main_text = fs::read_to_string(project_root.join("main.tex"))?;
+
+    if &config.last_chapter.user_name == "static" && config.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",
+            ),
+        )
+    } 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)
+                ),
+                "chapter.tex",
+            ),
+        )
+    };
+
+    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    ",
-            name.to_case(Case::Snake),
-            &format!("chapter_{:02}", data_file.last_chapter.number + 1)
+            &format!(
+                "{:02}_{}",
+                config.last_chapter.number + 1,
+                &name.to_case(Case::Snake)
+            ),
+            "chapter.tex",
         ),
     );
 
-    data_file.last_chapter.user_name = name.to_case(Case::Snake);
-    data_file.last_chapter.number += 1;
-
-    fs::write(
-        project_root.join("lpm.toml"),
-        toml::to_string(&data_file).expect("We changed it ourselfes, the conversion should work"),
-    )
-    .unwrap();
-
-    fs::write(project_root.join("main.tex"), main_file).unwrap();
-
-    Ok(())
+    Ok(GeneratedFile::new(project_root.join("main.tex"), main_text))
 }
diff --git a/src/new/mod.rs b/src/new/mod.rs
index 33783c4..a85187c 100644
--- a/src/new/mod.rs
+++ b/src/new/mod.rs
@@ -1,97 +1,2 @@
 pub mod chapter;
-pub mod project;
 pub mod section;
-
-use std::{
-    env,
-    ffi::OsString,
-    fs::read_dir,
-    io::{self, ErrorKind},
-    path::PathBuf,
-};
-
-const SECTION: &'static str = r#"%! TEX root = ../../../main.tex
-% LTeX: language=de-DE
-
-\lesson{REPLACMENT_SECTION_TITLE}{DATE}{}
-Dies ist etwas Text
-"#;
-
-const CHAPTER: &'static str = r#"%! TEX root = ../main.tex
-% LTeX: language=de-DE
-
-\chapter{REPLACEMENT_CHAPTER}
-"#;
-
-const TITLE_FILE: &'static str = r#"% LTeX: language=de-DE
-
-\maketitle
-\tableofcontents
-\vspace*{\fill}
-\makeatletter
-Copyright \textcopyright{} \@authors{} \@years{}\\
-\ \\
-Dieses Werk ist lizenziert unter den Bedingungen der CC BY-SA 4.0.
-Der Lizenztext ist online unter \url{http://creativecommons.org/licenses/by-sa/4.0/legalcode} abrufbar.
-\makeatother
-\clearpage
-"#;
-
-const MAIN_FILE: &'static str = r#"%\documentclass[a4paper, 12pt, nosolutions]{report}
-\documentclass[a4paper, 12pt]{report}
-% LTeX: language=de-DE
-\input{headers/preamble.tex}
-\input{headers/preamble_local.tex}
-
-
-\title{\textbf{Titel}}
-\author{Name\thanks{Beispiel}}
-\authors{Name}
-\years{2022 - 2023}
-\date{\DTMDate{2002-12-4}}
-
-\includeonly{content/REPLACEMENT_CHAPTER/chapter_01}
-
-\begin{document}
-    \input{content/static/title}
-
-    \include{content/REPLACEMENT_CHAPTER/chapter_01}
-    % NEXT_CHAPTER
-
-    \printbibliography\relax
-\end{document}
-"#;
-
-pub fn get_project_root() -> io::Result<PathBuf> {
-    let path = env::current_dir()?;
-    let mut path_ancestors = path.as_path().ancestors();
-
-    while let Some(path_segment) = path_ancestors.next() {
-        if read_dir(path_segment)?.into_iter().any(|path_segment| {
-            path_segment
-                .expect("The read_dir shouldn't error out here")
-                .file_name()
-                == OsString::from("lpm.toml")
-        }) {
-            return Ok(PathBuf::from(path_segment));
-        }
-    }
-    Err(io::Error::new(
-        ErrorKind::NotFound,
-        "Ran out of places to find lpm.toml",
-    ))
-}
-
-pub fn get_all_chapters() -> io::Result<Vec<String>> {
-    let path = get_project_root()?;
-    let output = read_dir(path.join("content"))?
-        .map(|path| {
-            path.expect("The values sholud be fine")
-                .file_name()
-                .to_str()
-                .expect("all names should be valid utf-8")
-                .to_owned()
-        })
-        .collect();
-    Ok(output)
-}
diff --git a/src/new/project.rs b/src/new/project.rs
deleted file mode 100644
index 56edead..0000000
--- a/src/new/project.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-use std::{
-    env,
-    fs::{self, File},
-    io::{self, Write},
-    path::PathBuf,
-    process::Command,
-};
-
-use convert_case::{Case, Casing};
-
-use crate::data::Data;
-
-use super::{CHAPTER, MAIN_FILE, TITLE_FILE};
-
-const NEEDED_DIRECTORYS: &'static [&'static str] =
-    &["build", "content", "headers", "references", "resources"];
-
-pub fn generate_new_project(
-    name: String,
-    first_chapter: String,
-    //first_section: String,
-    preamble_path: PathBuf,
-    resource_path: PathBuf,
-) -> io::Result<()> {
-    fs::create_dir(&name).unwrap();
-    env::set_current_dir(&name).unwrap();
-    let project_root = env::current_dir().unwrap();
-
-    for directory in NEEDED_DIRECTORYS {
-        fs::create_dir(directory).unwrap();
-    }
-
-    // content
-    env::set_current_dir(project_root.join("content")).unwrap();
-    fs::create_dir(&first_chapter.to_case(Case::Snake)).unwrap();
-    fs::create_dir("static").unwrap();
-
-    env::set_current_dir("static").unwrap();
-    let mut title_file = File::create("title.tex").unwrap();
-    title_file.write_all(TITLE_FILE.as_bytes()).unwrap();
-
-    env::set_current_dir(
-        project_root
-            .join("content")
-            .join(&first_chapter.to_case(Case::Snake)),
-    )
-    .unwrap();
-    fs::create_dir("sections").unwrap();
-    let mut chapter_file = File::create("chapter_01.tex").unwrap();
-    chapter_file
-        .write_all(
-            CHAPTER
-                .replace("REPLACEMENT_CHAPTER", &first_chapter)
-                .as_bytes(),
-        )
-        .unwrap();
-
-    //env::set_current_dir("sections").unwrap();
-    //let mut section_file = File::create(format!("{}.tex", &first_section)).unwrap();
-    //section_file
-    //    .write_all(SECTION.as_bytes())
-    //    .unwrap();
-
-    // headers
-    env::set_current_dir(project_root.join("headers")).unwrap();
-    File::create("preamble_local.tex").unwrap();
-    fs::copy(fs::canonicalize(preamble_path).unwrap(), "preamble.tex").unwrap();
-
-    // resources
-    env::set_current_dir(project_root.join("resources")).unwrap();
-    fs::canonicalize(resource_path)
-        .unwrap()
-        .read_dir()
-        .unwrap()
-        .map(|path| path.expect("The provided path should work"))
-        .for_each(|path| {
-            fs::copy(path.path(), path.file_name()).unwrap();
-        });
-
-    // root files
-    env::set_current_dir(project_root).unwrap();
-    let mut main_file = File::create("main.tex").unwrap();
-    main_file
-        .write_all(
-            MAIN_FILE
-                .replace("REPLACEMENT_CHAPTER", &first_chapter.to_case(Case::Snake))
-                .as_bytes(),
-        )
-        .unwrap();
-
-    let data_file = Data {
-        last_chapter: crate::data::LastChapter {
-            user_name: first_chapter.to_case(Case::Snake),
-            number: 1,
-        },
-    };
-    let mut lpm_file = File::create("lpm.toml").unwrap();
-    lpm_file
-        .write_all(toml::to_string(&data_file).unwrap().as_bytes())
-        .unwrap();
-
-    Command::new("git")
-        .arg("init")
-        .output()
-        .expect("failed to execute process");
-
-    let mut lpm_file = File::create(".gitignore").unwrap();
-    lpm_file.write_all(b"/build").unwrap();
-
-    Ok(())
-}
diff --git a/src/new/section.rs b/src/new/section.rs
index 31742c2..a359fb0 100644
--- a/src/new/section.rs
+++ b/src/new/section.rs
@@ -1,63 +1,63 @@
-use std::{
-    env,
-    fs::{self, read_dir},
-    io::{self, ErrorKind},
-    path::PathBuf,
-    time::SystemTime,
-};
+use std::{fs, path::Path, time::SystemTime};
 
+use anyhow::Context;
 use chrono::{DateTime, Local};
 use convert_case::{Case, Casing};
+use log::debug;
 
-use super::SECTION;
+use crate::{
+    config_file::Config,
+    file_tree::{FileTree, GeneratedFile},
+};
 
-pub fn generate_new_section(name: String) -> io::Result<()> {
-    let chapter_root = get_section_root()?;
-    let chapter_main_file_path = read_dir(&chapter_root)?
-        .into_iter()
-        .map(|path| path.unwrap())
-        .filter(|path| path.file_name().to_str().unwrap().contains("chapter_"))
-        .last()
-        .unwrap()
-        .path();
-    let mut main_file = fs::read_to_string(&chapter_main_file_path).unwrap();
+pub fn generate_new_section(
+    config: &Config,
+    name: String,
+    project_root: &Path,
+    chapter_name: &str,
+) -> anyhow::Result<FileTree> {
+    let chapter_root = project_root.join("content").join(chapter_name);
+    debug!("Chapter root is: {}", chapter_root.display());
 
-    main_file.push_str(&format!(
-        "\\input{{content/{}/sections/{}}}\n",
-        chapter_root.file_name().unwrap().to_str().unwrap(),
-        &name.to_case(Case::Snake)
-    ));
-    fs::write(chapter_main_file_path, main_file)?;
-    fs::write(
-        chapter_root.join(format!("sections/{}.tex", name.to_case(Case::Snake))),
-        SECTION.replace("REPLACMENT_SECTION_TITLE", &name).replace(
+    let mut file_tree = FileTree::new();
+
+    let new_section_text = config
+        .templates
+        .section
+        .replace("REPLACMENT_SECTION_TITLE", &name)
+        .replace(
             "DATE",
             &format!(
                 "{}",
-                DateTime::<Local>::from(SystemTime::now()).format("%Y-%m-%d")
+                // FIXME: The time is not really precise enough to display the time.  <2024-03-31>
+                DateTime::<Local>::from(SystemTime::now()).format("%Y-%m-%d (%_H:%_S)")
             ),
-        ),
-    )?;
-    Ok(())
-}
+        );
+
+    let new_section_file = GeneratedFile::new(
+        chapter_root
+            .join("sections")
+            .join(format!("{}.tex", name.to_case(Case::Snake))),
+        new_section_text,
+    );
+    file_tree.add_file(new_section_file);
+
+    let chapter_file_path = chapter_root.join("chapter.tex");
+    let mut chapter_file_text = fs::read_to_string(&chapter_file_path).with_context(|| {
+        format!(
+            "Failed to read the chapter main file ('{}') to string",
+            &chapter_file_path.display(),
+        )
+    })?;
+
+    chapter_file_text.push_str(&format!(
+        "\\input{{content/{}/sections/{}}}\n",
+        chapter_name,
+        &name.to_case(Case::Snake)
+    ));
+
+    let chapter_file = GeneratedFile::new(chapter_file_path, chapter_file_text);
+    file_tree.add_file(chapter_file);
 
-pub fn get_section_root() -> io::Result<PathBuf> {
-    let path = env::current_dir()?;
-    let mut path_ancestors = path.as_path().ancestors();
-
-    while let Some(path_segment) = path_ancestors.next() {
-        if read_dir(path_segment)?.into_iter().any(|path| {
-            path.expect("The read_dir shouldn't error out here")
-                .file_name()
-                .to_str()
-                .unwrap()
-                .contains("chapter_")
-        }) {
-            return Ok(PathBuf::from(path_segment));
-        }
-    }
-    Err(io::Error::new(
-        ErrorKind::NotFound,
-        "Ran out of places to find chapter_root",
-    ))
+    Ok(file_tree)
 }