1use std::{
64 io,
65 path::{Path, PathBuf},
66};
67
68const DEFAULT_CONTENT: &str = "0123456789";
69
70pub trait FileWriter {
71 fn write<P, C>(&mut self, path: P, content: C) -> io::Result<()>
72 where
73 P: AsRef<Path>,
74 C: AsRef<[u8]>;
75}
76
77pub trait FilenameGenerator {
78 fn generate(&mut self) -> PathBuf;
79}
80
81pub trait PathFilenameGenerator: FilenameGenerator {
82 type Name: ToString;
83 fn next_name(&mut self) -> Self::Name;
84
85 fn base_path(&self) -> &Path;
86}
87
88impl<T, M: ToString> FilenameGenerator for T
89where
90 T: PathFilenameGenerator<Name = M>,
91{
92 fn generate(&mut self) -> PathBuf {
93 let name = self.next_name().to_string();
94 self.base_path().join(name)
95 }
96}
97
98pub struct OrdinalPathFilenameGenerator {
99 root: PathBuf,
100 current: usize,
101}
102
103impl<T> From<T> for OrdinalPathFilenameGenerator
104where
105 T: AsRef<Path>,
106{
107 fn from(value: T) -> Self {
108 Self {
109 root: value.as_ref().into(),
110 current: 0,
111 }
112 }
113}
114
115impl PathFilenameGenerator for OrdinalPathFilenameGenerator {
116 type Name = usize;
117
118 fn next_name(&mut self) -> usize {
119 let current = self.current;
120 self.current += 1;
121 current
122 }
123
124 fn base_path(&self) -> &Path {
125 &self.root
126 }
127}
128
129pub struct DefaultFileWriter {}
130
131impl FileWriter for DefaultFileWriter {
132 fn write<P, C>(&mut self, path: P, content: C) -> io::Result<()>
133 where
134 P: AsRef<Path>,
135 C: AsRef<[u8]>,
136 {
137 std::fs::write(path, content)
138 }
139}
140
141pub struct FileGeneratorBuilder {}
142
143impl FileGeneratorBuilder {
144 pub fn with_size(size: usize) -> SizedFileGenerator {
145 SizedFileGenerator { size }
146 }
147}
148
149pub trait FileGenerator: Sized {
150 fn write<PG, F>(&self, path_generator: &mut PG, file_writer: &mut F) -> io::Result<()>
151 where
152 PG: FilenameGenerator,
153 F: FileWriter;
154
155 fn generate_to<P>(&self, path: P) -> io::Result<()>
156 where
157 P: AsRef<Path>,
158 {
159 self.write(
160 &mut OrdinalPathFilenameGenerator::from(path),
161 &mut DefaultFileWriter {},
162 )
163 }
164
165 fn repeat(self, count: usize) -> RepeatFileGenerator<Self> {
166 RepeatFileGenerator {
167 count,
168 generator: self,
169 }
170 }
171}
172
173pub struct SizedFileGenerator {
174 size: usize,
175}
176
177impl FileGenerator for SizedFileGenerator {
178 fn write<PG, F>(&self, path_generator: &mut PG, file_writer: &mut F) -> io::Result<()>
179 where
180 PG: FilenameGenerator,
181 F: FileWriter,
182 {
183 let buffer: Vec<u8> = DEFAULT_CONTENT.bytes().cycle().take(self.size).collect();
184
185 file_writer.write(path_generator.generate(), buffer)
186 }
187}
188
189pub struct RepeatFileGenerator<G: FileGenerator> {
190 count: usize,
191 generator: G,
192}
193
194impl<G: FileGenerator> FileGenerator for RepeatFileGenerator<G> {
195 fn write<PG, F>(&self, path_generator: &mut PG, file_writer: &mut F) -> io::Result<()>
196 where
197 PG: FilenameGenerator,
198 F: FileWriter,
199 {
200 for _ in 0..self.count {
201 self.generator.write(path_generator, file_writer)?;
202 }
203
204 Ok(())
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::path::PathBuf;
211
212 use crate::{FileGenerator, FileGeneratorBuilder, FileWriter, OrdinalPathFilenameGenerator};
213
214 struct TestFileGenerator {
215 files: Vec<(PathBuf, Vec<u8>)>,
216 }
217
218 impl FileWriter for TestFileGenerator {
219 fn write<P, C>(&mut self, path: P, content: C) -> std::io::Result<()>
220 where
221 P: AsRef<std::path::Path>,
222 C: AsRef<[u8]>,
223 {
224 self.files
225 .push((path.as_ref().into(), content.as_ref().into()));
226
227 Ok(())
228 }
229 }
230
231 #[test]
232 fn repeated_file_generator() {
233 let mut test_file_generator = TestFileGenerator { files: vec![] };
234
235 let _ = FileGeneratorBuilder::with_size(1)
236 .repeat(3)
237 .write(
238 &mut OrdinalPathFilenameGenerator::from("/test/path"),
239 &mut test_file_generator,
240 )
241 .unwrap();
242
243 assert_eq!(
244 test_file_generator.files,
245 vec![
246 (PathBuf::from("/test/path/0"), b"0".to_vec()),
247 (PathBuf::from("/test/path/1"), b"0".to_vec()),
248 (PathBuf::from("/test/path/2"), b"0".to_vec())
249 ]
250 );
251 }
252}