tree_fs/lib.rs
1#![doc = include_str!("../README.md")]
2
3use rand::{distributions::Alphanumeric, thread_rng, Rng};
4use serde::Deserialize;
5use std::env;
6use std::fs::File;
7use std::io::Write;
8use std::path::Path;
9use std::path::PathBuf;
10
11use thiserror::Error;
12
13#[cfg(feature = "yaml")]
14#[derive(Error, Debug)]
15pub enum Error {
16 #[error(transparent)]
17 YAML(#[from] serde_yaml::Error),
18 #[error(transparent)]
19 IO(#[from] std::io::Error),
20}
21#[cfg(feature = "yaml")]
22pub type Result<T> = std::result::Result<T, Error>;
23
24/// Represents a file tree structure
25#[derive(Debug, Deserialize)]
26pub struct Tree {
27 /// Root folder where the tree will be created.
28 #[serde(default = "temp_dir")]
29 pub root: PathBuf,
30 #[serde(default)]
31 drop: bool,
32}
33
34/// Represents a file tree structure
35///
36/// # Examples
37///
38/// ```rust
39// <snip id="example-builder">
40/// use tree_fs::TreeBuilder;
41/// let tree_fs = TreeBuilder::default()
42/// .add("test/foo.txt", "bar")
43/// .add_empty("test/folder-a/folder-b/bar.txt")
44/// .create()
45/// .expect("create tree fs");
46/// println!("created successfully in {}", tree_fs.root.display());
47// </snip>
48/// ```
49///
50/// ```rust
51// <snip id="example-drop">
52/// use tree_fs::TreeBuilder;
53/// let tree_fs = TreeBuilder::default()
54/// .add("test/foo.txt", "bar")
55/// .add_empty("test/folder-a/folder-b/bar.txt")
56/// .drop(true)
57/// .create()
58/// .expect("create tree fs");
59///
60/// println!("created successfully in {}", tree_fs.root.display());
61///
62/// let path = tree_fs.root.clone();
63/// assert!(path.exists());
64///
65/// drop(tree_fs);
66/// assert!(!path.exists());
67// </snip>
68/// ```
69#[derive(Debug, Deserialize)]
70pub struct TreeBuilder {
71 /// Root folder where the tree will be created.
72 #[serde(default = "temp_dir")]
73 pub root: PathBuf,
74 /// Flag indicating whether existing files should be overridden.
75 #[serde(default)]
76 override_file: bool,
77 /// List of file metadata entries in the tree.
78 files: Vec<FileMetadata>,
79 #[serde(default)]
80 drop: bool,
81}
82
83/// Represents metadata for a file in the tree.
84#[derive(Debug, Deserialize)]
85pub struct FileMetadata {
86 /// Path of the file relative to the root folder.
87 pub path: PathBuf,
88 /// Optional content to be written to the file.
89 pub content: Option<String>,
90}
91
92impl Default for TreeBuilder {
93 /// Creates a default `Tree` instance with an empty file list,
94 fn default() -> Self {
95 Self {
96 files: vec![],
97 override_file: false,
98 root: temp_dir(),
99 drop: false,
100 }
101 }
102}
103
104impl Drop for Tree {
105 fn drop(&mut self) {
106 if self.drop {
107 let _ = std::fs::remove_dir_all(&self.root);
108 }
109 }
110}
111
112impl TreeBuilder {
113 /// Sets the root folder where the tree will be created.
114 #[must_use]
115 pub fn root_folder<P: AsRef<Path>>(mut self, dir: P) -> Self {
116 self.root = dir.as_ref().to_path_buf();
117 self
118 }
119
120 /// Sets the `override_file` flag, indicating whether existing files should be overridden.
121 #[must_use]
122 pub const fn drop(mut self, yes: bool) -> Self {
123 self.drop = yes;
124 self
125 }
126
127 /// Sets the `override_file` flag, indicating whether existing files should be overridden.
128 #[must_use]
129 pub const fn override_file(mut self, yes: bool) -> Self {
130 self.override_file = yes;
131 self
132 }
133
134 /// Adds a file with content to the tree.
135 #[must_use]
136 pub fn add<P: AsRef<Path>>(mut self, path: P, content: &str) -> Self {
137 self.files.push(FileMetadata {
138 path: path.as_ref().to_path_buf(),
139 content: Some(content.to_string()),
140 });
141 self
142 }
143
144 /// Adds a file with a empty content.
145 #[must_use]
146 pub fn add_empty<P: AsRef<Path>>(mut self, path: P) -> Self {
147 self.files.push(FileMetadata {
148 path: path.as_ref().to_path_buf(),
149 content: None,
150 });
151 self
152 }
153
154 /// Creates the file tree by generating files and directories based on the specified metadata.
155 ///
156 /// # Errors
157 ///
158 /// Returns an `std::io::Result` indicating success or failure in creating the file tree.
159 pub fn create(&self) -> std::io::Result<Tree> {
160 if !self.root.exists() {
161 std::fs::create_dir_all(&self.root)?;
162 }
163 for file in &self.files {
164 let dest_file = self.root.join(&file.path);
165 if !self.override_file && dest_file.exists() {
166 continue;
167 }
168
169 if let Some(parent_dir) = Path::new(&dest_file).parent() {
170 std::fs::create_dir_all(parent_dir)?;
171 }
172
173 let mut new_file = File::create(&dest_file)?;
174 if let Some(content) = &file.content {
175 new_file.write_all(content.as_bytes())?;
176 }
177 }
178
179 Ok(Tree {
180 root: self.root.clone(),
181 drop: self.drop,
182 })
183 }
184}
185
186#[cfg(feature = "yaml")]
187/// Creates a file tree based on the content of a YAML file.
188///
189/// # Examples
190///
191/// ```rust
192// <snip id="example-from-yaml-file">
193/// use std::path::PathBuf;
194/// let yaml_path = PathBuf::from("tests/fixtures/tree.yaml");
195/// let tree_fs = tree_fs::from_yaml_file(&yaml_path).expect("create tree fs");
196/// assert!(tree_fs.root.exists())
197// </snip>
198/// ```
199///
200/// # Errors
201///
202/// Returns a `Result` containing the path to the root folder of the generated file tree on success,
203/// or an error if the operation fails.
204pub fn from_yaml_file(path: &PathBuf) -> Result<Tree> {
205 let f = std::fs::File::open(path)?;
206 let tree_builder: TreeBuilder = serde_yaml::from_reader(f)?;
207 Ok(tree_builder.create()?)
208}
209
210#[cfg(feature = "yaml")]
211/// Creates a file tree based on a YAML-formatted string.
212///
213/// # Examples
214///
215/// ```rust
216// <snip id="example-from-yaml-str">
217/// let content = r#"
218/// override_file: false
219/// files:
220/// - path: foo.json
221/// content: |
222/// { "foo;": "bar" }
223/// - path: folder/bar.yaml
224/// content: |
225/// foo: bar
226/// "#;
227///
228/// let tree_fs = tree_fs::from_yaml_str(content).expect("create tree fs");
229/// assert!(tree_fs.root.exists())
230// </snip>
231///
232/// ```
233///
234/// # Errors
235/// Returns a `Result` containing the path to the root folder of the generated file tree on success,
236/// or an error if the operation fails.
237pub fn from_yaml_str(content: &str) -> Result<Tree> {
238 let tree_builder: TreeBuilder = serde_yaml::from_str(content)?;
239 Ok(tree_builder.create()?)
240}
241
242fn temp_dir() -> PathBuf {
243 let random_string: String = thread_rng()
244 .sample_iter(&Alphanumeric)
245 .take(5)
246 .map(char::from)
247 .collect();
248
249 env::temp_dir().join(random_string)
250}