summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
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)
 }