assemble_core/
file.rs

1use crate::project::buildable::{Buildable, BuiltByContainer, IntoBuildable};
2use std::fmt::{Debug, Display, Formatter};
3use std::fs::{File, Metadata, OpenOptions};
4use std::io;
5use std::io::{Read, Write};
6
7use std::path::{Path, PathBuf};
8
9/// A wrapper type that derefs to a File, while also providing access to it's path
10pub struct RegularFile {
11    path: PathBuf,
12    file: File,
13    open_options: OpenOptions,
14    built_by: BuiltByContainer,
15}
16
17assert_impl_all!(RegularFile: Send, Sync);
18
19impl RegularFile {
20    /// Create a regular file using an options object and a path
21    pub fn with_options<P: AsRef<Path>>(path: P, options: &OpenOptions) -> io::Result<Self> {
22        Ok(Self {
23            path: path.as_ref().to_path_buf(),
24            file: options.open(path)?,
25            open_options: options.clone(),
26            built_by: BuiltByContainer::default(),
27        })
28    }
29
30    /// Opens a file in write-only mode.
31    ///
32    /// Will create a file if it does not exist, and will truncate if it does.
33    pub fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
34        Self::with_options(
35            path,
36            File::options().create(true).write(true).truncate(true),
37        )
38    }
39
40    /// Attempts to open a file in read-only mode.
41    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
42        Self::with_options(path, File::options().read(true))
43    }
44
45    /// Gets the path of the file
46    pub fn path(&self) -> &Path {
47        &self.path
48    }
49
50    /// Add a built by
51    pub fn built_by<T: Buildable + 'static>(&mut self, task: T) {
52        self.built_by.add(task)
53    }
54
55    /// Get the underlying file of this regular file
56    pub fn file(&self) -> &File {
57        &self.file
58    }
59
60    pub fn metadata(&self) -> io::Result<Metadata> {
61        self.file().metadata()
62    }
63}
64
65impl Debug for RegularFile {
66    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("RegularFile")
68            .field("path", &self.path)
69            .field("open_options", &self.open_options)
70            .field("built_buy", &"...")
71            .finish()
72    }
73}
74
75impl From<RegularFile> for PathBuf {
76    fn from(rf: RegularFile) -> Self {
77        rf.path
78    }
79}
80
81impl Display for RegularFile {
82    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83        write!(f, "{:?}", self.path)
84    }
85}
86
87impl Read for RegularFile {
88    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
89        self.file.read(buf)
90    }
91}
92
93impl Read for &RegularFile {
94    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
95        (&self.file).read(buf)
96    }
97}
98
99impl Write for RegularFile {
100    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
101        self.file.write(buf)
102    }
103
104    fn flush(&mut self) -> io::Result<()> {
105        self.file.flush()
106    }
107}
108
109impl AsRef<Path> for RegularFile {
110    fn as_ref(&self) -> &Path {
111        &self.path
112    }
113}
114
115impl IntoBuildable for &RegularFile {
116    type Buildable = BuiltByContainer;
117
118    fn into_buildable(self) -> Self::Buildable {
119        self.built_by.clone()
120    }
121}
122
123/// Trait to get this value as a file location
124pub trait AsFileLocation {
125    /// Some type that can be interpreted as a path
126    type FilePath: AsRef<Path>;
127
128    /// Get the file location of this value
129    fn file_location(&self) -> Self::FilePath;
130}
131
132impl<P: AsRef<Path>> AsFileLocation for P {
133    type FilePath = PathBuf;
134
135    fn file_location(&self) -> Self::FilePath {
136        self.as_ref().to_path_buf()
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use std::io::Read;
144    use std::io::Write;
145    use tempfile::TempDir;
146
147    #[test]
148    fn create_file() {
149        let tempdir = TempDir::new().unwrap();
150        let file = RegularFile::with_options(
151            tempdir.path().join("file"),
152            OpenOptions::new().create(true).write(true),
153        )
154        .unwrap();
155
156        assert_eq!(file.path(), tempdir.path().join("file"));
157    }
158
159    #[test]
160    fn can_write() {
161        let tempdir = TempDir::new().unwrap();
162        let file = RegularFile::with_options(
163            tempdir.path().join("file"),
164            OpenOptions::new().create(true).write(true),
165        )
166        .unwrap();
167
168        writeln!(file.file(), "Hello, World!").expect("Couldn't write to file");
169    }
170
171    #[test]
172    fn can_read() {
173        let tempdir = TempDir::new().unwrap();
174        let reg_file = RegularFile::with_options(
175            tempdir.path().join("file"),
176            OpenOptions::new().create(true).write(true),
177        )
178        .unwrap();
179
180        let mut file = reg_file.file();
181        writeln!(file, "Hello, World!").expect("Couldn't write to file");
182
183        let mut file =
184            RegularFile::with_options(tempdir.path().join("file"), OpenOptions::new().read(true))
185                .unwrap();
186
187        let mut buffer = String::new();
188        file.read_to_string(&mut buffer)
189            .expect("Couldn't read from file");
190        assert_eq!(buffer.trim(), "Hello, World!");
191    }
192}