grace_cli/core/file/
file.rs

1use std::fs;
2use std::path::Path;
3use std::vec::Vec;
4use thiserror::Error;
5
6/// Enum to represent the different errors that can occur when working with files.
7#[derive(Error, Debug, PartialEq)]
8pub enum FileErr {
9    #[error("Provided path is not a directory")]
10    NotADirectory,
11    #[error("Provided path is not a file")]
12    NotAFile,
13    #[error("File already exists")]
14    FileAlreadyExist,
15    #[error("File does not exist")]
16    FileDoesNotExist,
17    #[error("Directory does not exist")]
18    DirectoryDoesNotExist,
19    #[error("Failed to read directory: {0}")]
20    ReadDirError(String),
21    #[error("Failed to rename file: {0}")]
22    RenameError(String),
23    #[error("Failed to create file: {0}")]
24    CreateFileError(String),
25    #[error("I/O error: {0}")]
26    IoError(String),
27}
28
29/// Struct to represent a file or directory.
30/// This struct is used to abstract away the underlying file system.
31pub struct File<'a> {
32    path: &'a Path,
33}
34
35impl File<'_> {
36    /// Create a new file.
37    pub fn new(path_str: &str) -> File<'_> {
38        File {
39            path: Path::new(path_str),
40        }
41    }
42
43    /// Return the file extension.
44    pub fn extension(&self) -> Option<String> {
45        self.path.extension().map(|os_str| {
46            os_str
47                .to_str()
48                .map(|str| str.to_string())
49                .unwrap_or("".to_string())
50        })
51    }
52
53    /// Return the file name.
54    pub fn file_name(&self) -> Option<String> {
55        self.path.file_name().map(|os_str| {
56            os_str
57                .to_str()
58                .map(|str| str.to_string())
59                .unwrap_or("".to_string())
60        })
61    }
62
63    /// Return the file stem.
64    pub fn file_stem(&self) -> Option<String> {
65        self.path.file_stem().map(|os_str| {
66            os_str
67                .to_str()
68                .map(|str| str.to_string())
69                .unwrap_or("".to_string())
70        })
71    }
72
73    /// Check if the file is a directory.
74    pub fn is_dir(&self) -> bool {
75        self.path.is_dir()
76    }
77
78    /// Check if the file is a file.
79    pub fn is_file(&self) -> bool {
80        self.path.is_file()
81    }
82
83    /// Check if the file exist.
84    pub fn exist(&self) -> bool {
85        self.path.exists()
86    }
87
88    /// If `File` is a directory, return a vector of the files in the directory.
89    /// If `File` is a file, return an error `FileErr::NotADirectory`.
90    pub fn read_dir(&self) -> Result<Vec<String>, FileErr> {
91        if !self.is_dir() {
92            return Err(FileErr::NotADirectory);
93        }
94
95        let paths = fs::read_dir(self.path).map_err(|e| FileErr::ReadDirError(e.to_string()))?;
96
97        let files_in_dir: Result<Vec<String>, FileErr> = paths
98            .map(|entry| {
99                entry
100                    .map_err(|e| FileErr::ReadDirError(e.to_string()))
101                    .and_then(|e| {
102                        e.file_name()
103                            .into_string()
104                            .map_err(|_| FileErr::IoError("Invalid UTF-8 in filename".to_string()))
105                    })
106            })
107            .collect();
108
109        files_in_dir
110    }
111
112    /// Create a file. If the file already exist, an error `FileErr::FileAlreadyExist` will be returned.
113    pub fn create_file(&self) -> Result<(), FileErr> {
114        if self.exist() {
115            return Err(FileErr::FileAlreadyExist);
116        }
117
118        fs::File::create(self.path)
119            .map(|_| ())
120            .map_err(|e| FileErr::CreateFileError(e.to_string()))
121    }
122
123    /// Rename file or directory.
124    pub fn rename(&self, new_name: &str) -> Result<(), FileErr> {
125        let new_path = self.path.with_file_name(new_name);
126        fs::rename(self.path, new_path).map_err(|e| FileErr::RenameError(e.to_string()))
127    }
128
129    pub fn path(&self) -> String {
130        self.path.to_str().unwrap().to_string()
131    }
132}