1use std::{
2 fs::File,
3 io::Write,
4 path::{Path, PathBuf},
5};
6
7#[cfg(feature = "yaml")]
8use serde::Deserialize;
9
10#[derive(Debug)]
53#[cfg_attr(feature = "yaml", derive(Deserialize))]
54pub struct TreeBuilder {
55 #[cfg_attr(feature = "yaml", serde(default = "crate::tree::temp_dir"))]
57 pub root: PathBuf,
58 #[cfg_attr(feature = "yaml", serde(default))]
60 override_file: bool,
61 entries: Vec<crate::Entry>,
63 #[cfg_attr(feature = "yaml", serde(default = "crate::yaml::default_drop"))]
65 drop: bool,
66}
67
68impl TreeBuilder {
69 #[must_use]
71 pub fn root_folder<P: AsRef<Path>>(mut self, dir: P) -> Self {
72 self.root = dir.as_ref().to_path_buf();
73 self
74 }
75
76 #[must_use]
78 pub const fn drop(mut self, yes: bool) -> Self {
79 self.drop = yes;
80 self
81 }
82
83 #[must_use]
85 pub const fn override_file(mut self, yes: bool) -> Self {
86 self.override_file = yes;
87 self
88 }
89
90 #[must_use]
92 pub fn add<P: AsRef<Path>>(mut self, path: P, content: &str) -> Self {
93 self.entries.push(crate::Entry {
94 path: path.as_ref().to_path_buf(),
95 kind: crate::Kind::TextFile {
96 content: content.to_string(),
97 },
98 settings: None,
99 });
100 self
101 }
102
103 #[must_use]
107 pub fn add_file<P: AsRef<Path>>(self, path: P, content: &str) -> Self {
108 self.add(path, content)
109 }
110
111 #[must_use]
113 pub fn add_empty<P: AsRef<Path>>(self, path: P) -> Self {
114 self.add_empty_file(path)
115 }
116
117 #[must_use]
119 pub fn add_empty_file<P: AsRef<Path>>(mut self, path: P) -> Self {
120 self.entries.push(crate::Entry {
121 path: path.as_ref().to_path_buf(),
122 kind: crate::Kind::EmptyFile,
123 settings: None,
124 });
125 self
126 }
127
128 #[must_use]
130 pub fn add_directory<P: AsRef<Path>>(mut self, path: P) -> Self {
131 self.entries.push(crate::Entry {
132 path: path.as_ref().to_path_buf(),
133 kind: crate::Kind::Directory,
134 settings: None,
135 });
136 self
137 }
138
139 #[must_use]
141 pub fn add_file_with_settings<P: AsRef<Path>>(
142 mut self,
143 path: P,
144 content: &str,
145 settings: crate::tree::Settings,
146 ) -> Self {
147 self.entries.push(crate::Entry {
148 path: path.as_ref().to_path_buf(),
149 kind: crate::Kind::TextFile {
150 content: content.to_string(),
151 },
152 settings: Some(settings),
153 });
154 self
155 }
156
157 #[must_use]
159 pub fn add_empty_file_with_settings<P: AsRef<Path>>(
160 mut self,
161 path: P,
162 settings: crate::tree::Settings,
163 ) -> Self {
164 self.entries.push(crate::Entry {
165 path: path.as_ref().to_path_buf(),
166 kind: crate::Kind::EmptyFile,
167 settings: Some(settings),
168 });
169 self
170 }
171
172 #[must_use]
174 pub fn add_directory_with_settings<P: AsRef<Path>>(
175 mut self,
176 path: P,
177 settings: crate::tree::Settings,
178 ) -> Self {
179 self.entries.push(crate::Entry {
180 path: path.as_ref().to_path_buf(),
181 kind: crate::Kind::Directory,
182 settings: Some(settings),
183 });
184 self
185 }
186
187 #[must_use]
189 pub fn add_readonly_file<P: AsRef<Path>>(self, path: P, content: &str) -> Self {
190 self.add_file_with_settings(path, content, crate::tree::Settings::new().readonly(true))
191 }
192
193 #[must_use]
195 pub fn add_readonly_empty_file<P: AsRef<Path>>(self, path: P) -> Self {
196 self.add_empty_file_with_settings(path, crate::tree::Settings::new().readonly(true))
197 }
198
199 pub fn create(&self) -> std::io::Result<crate::Tree> {
205 if !self.root.exists() {
206 std::fs::create_dir_all(&self.root)?;
207 }
208
209 for entry in &self.entries {
211 let dest_path = self.root.join(&entry.path);
212 if !self.override_file && dest_path.exists() {
213 continue;
214 }
215
216 match &entry.kind {
217 crate::Kind::Directory => {
218 std::fs::create_dir_all(&dest_path)?;
219 }
220 crate::Kind::EmptyFile => {
221 if let Some(parent_dir) = Path::new(&dest_path).parent() {
222 std::fs::create_dir_all(parent_dir)?;
223 }
224 File::create(&dest_path)?;
225 }
226 crate::Kind::TextFile { content } => {
227 if let Some(parent_dir) = Path::new(&dest_path).parent() {
228 std::fs::create_dir_all(parent_dir)?;
229 }
230 let mut file = File::create(&dest_path)?;
231 file.write_all(content.as_bytes())?;
232 }
233 }
234
235 if let Some(settings) = &entry.settings {
236 if matches!(entry.kind, crate::Kind::Directory) {
237 continue;
238 }
239
240 let dest_path_for_perms = self.root.join(&entry.path);
241 if settings.readonly {
242 let mut permissions = std::fs::metadata(&dest_path_for_perms)?.permissions();
243 permissions.set_readonly(true);
244 std::fs::set_permissions(&dest_path_for_perms, permissions)?;
245 }
246 }
247 }
248
249 Ok(crate::Tree {
250 root: self.root.clone(),
251 drop: self.drop,
252 })
253 }
254}
255
256impl Default for TreeBuilder {
257 fn default() -> Self {
259 Self {
260 entries: vec![],
261 override_file: false,
262 root: crate::tree::temp_dir(),
263 drop: true,
264 }
265 }
266}