unreact/files/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use std::{fs, path::Path};
5
6use crate::{Config, Error, FileMap};
7
8/// Returns `Err` if source folders are not found in the working directory
9pub fn check_source_folders(config: &Config) -> Result<(), Error> {
10    let src_folders = [&config.templates, &config.public, &config.styles];
11    for folder in src_folders {
12        if !Path::new(&folder).is_dir() {
13            return Err(Error::SourceDirectoryNotExist(folder.to_string()));
14        }
15    }
16    Ok(())
17}
18
19/// Read a folder recursively, and read every file contents
20///
21/// Returns a hashmap of filepath strings (relative to the given directory), and file contents
22///
23/// Returns `Err` if cannot read a file or folder children
24pub fn read_folder_recurse(folder: &str) -> Result<FileMap, Error> {
25    let mut filemap = FileMap::new();
26    load_filemap(&mut filemap, folder, "")?;
27    Ok(filemap)
28}
29
30/// Load all files of a folder recursively into an existing hashmap
31///
32/// - For every *file* in the given directory, read and insert to hashmap
33/// - For every *folder* in the given directory, recurse this function, with the 'parent' folder as this folder
34///
35/// Returns `Err` if cannot read a file or folder children
36fn load_filemap(map: &mut FileMap, root: &str, parent: &str) -> Result<(), Error> {
37    // Full path relative to working directory
38    let full_path = format!("{root}/{parent}/");
39
40    // Children of current directory
41    let children = try_unwrap!(
42        fs::read_dir(&full_path),
43        else Err(err) => return io_fail!(ReadDir, full_path, err),
44    );
45
46    // Loop child files and folders
47    for file in children.flatten() {
48        // Get file path and file name
49        let (path, full_name) = (file.path(), file.file_name());
50        let Some((path, name)) = path.to_str().zip(full_name.to_str()) else {
51            continue;
52        };
53        // Normalize slashes in path
54        let path = path.replace('\\', "/");
55
56        // If child is a folder, recurse this function
57        if Path::new(&path).is_dir() {
58            load_filemap(map, root, &format!("{parent}{name}/"))?;
59            continue;
60        }
61
62        // Get name (not file extension) of child file
63        let name = get_filename(name);
64
65        // Read file contents
66        let content = try_unwrap!(
67            fs::read_to_string(&path),
68            else Err(err) => return io_fail!(ReadFile, path, err),
69        );
70
71        // Insert file and contents to hashmap
72        map.insert(format!("{parent}{name}"), content);
73    }
74
75    Ok(())
76}
77
78/// Remove files recursively from build directory, and create empty folders to be filled
79///
80/// All paths are treated relative to working directory
81///
82/// 1. Removes build folder (`./build/` or otherwise specified), if exists
83/// 2. Creates build folder
84/// 3. Creates `styles/` and `public/` inside build folder
85/// 4. Copies all files from public source folder into `public/` inside build folder
86pub fn clean_build_dir(config: &Config, is_dev: bool) -> Result<(), Error> {
87    // Remove build folder (if exists)
88    if Path::new(&config.build).exists() {
89        try_unwrap!(
90            fs::remove_dir_all(&config.build),
91            else Err(err) => return io_fail!(RemoveDir, config.build.clone(), err),
92        );
93    }
94
95    // Create output folders (build and subfolders)
96    let out_folders = ["", "styles", "public"];
97    for folder in out_folders {
98        let path = format!("{}/{}/", config.build, folder);
99
100        try_unwrap!(
101            fs::create_dir_all(&path),
102            else Err(err) => return io_fail!(CreateDir, path, err),
103        );
104    }
105
106    // Public directory
107    // Only *copy* directory in production
108    if !is_dev {
109        // Recursively copy public directory
110        try_unwrap!(
111            dircpy::copy_dir(&config.public, format!("{}/public", config.build)),
112            else Err(err) => return io_fail!(CopyDir, config.public.clone(), err),
113        );
114    } else {
115        // Create dummy note file
116        try_unwrap!(
117            fs::write(format!("{}/public/EMPTY", config.build), "'public' folder should always be empty in dev mode"),
118            else Err(err) => return io_fail!(CopyDir, config.public.clone(), err),
119        )
120    }
121
122    Ok(())
123}
124
125/// Get file 'name' from full file
126///
127/// Returns everything before the first `.` period
128///
129/// Returns empty string if nothing found
130pub fn get_filename(full_name: &str) -> &str {
131    full_name.split('.').next().unwrap_or("")
132}