1#[macro_export]
45macro_rules! tree {
46 ($($all:tt)+) => {{
47 let mut trie = $crate::TrieMap::new();
48 $crate::trees_internal!(trie $($all)*);
49 $crate::FsTree::Directory(trie)
50 }};
51}
52
53#[doc(hidden)]
54#[macro_export]
55macro_rules! trees_internal {
56 ($parent_trie:ident) => {};
58 ($parent_trie:ident $path:ident : { $($inner:tt)* } $($rest:tt)*) => {
60 #[allow(unused_mut)]
61 let mut trie = $crate::TrieMap::new();
62 $crate::trees_internal!(trie $($inner)*);
63 $parent_trie.insert(
64 ::std::path::PathBuf::from(stringify!($path)),
65 $crate::FsTree::Directory(trie)
66 );
67 $crate::trees_internal!($parent_trie $($rest)*)
68 };
69 ($parent_trie:ident $path:literal : { $($inner:tt)* } $($rest:tt)*) => {
71 #[allow(unused_mut)]
72 let mut trie = $crate::TrieMap::new();
73 $crate::trees_internal!(trie $($inner)*);
74 $parent_trie.insert(
75 ::std::path::PathBuf::from($path),
76 $crate::FsTree::Directory(trie)
77 );
78 $crate::trees_internal!($parent_trie $($rest)*)
79 };
80 ($parent_trie:ident $path:ident -> $target:ident $($rest:tt)*) => {
82 $parent_trie.insert(
83 ::std::path::PathBuf::from(stringify!($path)),
84 $crate::FsTree::Symlink(::std::path::PathBuf::from(stringify!($target)))
85 );
86 $crate::trees_internal!($parent_trie $($rest)*)
87 };
88 ($parent_trie:ident $path:literal -> $target:ident $($rest:tt)*) => {
90 $parent_trie.insert(
91 ::std::path::PathBuf::from($path),
92 $crate::FsTree::Symlink(::std::path::PathBuf::from(stringify!($target)))
93 );
94 $crate::trees_internal!($parent_trie $($rest)*)
95 };
96 ($parent_trie:ident $path:ident -> $target:literal $($rest:tt)*) => {
98 $parent_trie.insert(
99 ::std::path::PathBuf::from(stringify!($path)),
100 $crate::FsTree::Symlink(::std::path::PathBuf::from($target))
101 );
102 $crate::trees_internal!($parent_trie $($rest)*)
103 };
104 ($parent_trie:ident $path:literal -> $target:literal $($rest:tt)*) => {
106 $parent_trie.insert(
107 ::std::path::PathBuf::from($path),
108 $crate::FsTree::Symlink(::std::path::PathBuf::from($target))
109 );
110 $crate::trees_internal!($parent_trie $($rest)*)
111 };
112 ($parent_trie:ident $path:ident $($rest:tt)*) => {
114 $parent_trie.insert(
115 ::std::path::PathBuf::from(stringify!($path)),
116 $crate::FsTree::Regular
117 );
118 $crate::trees_internal!($parent_trie $($rest)*);
119 };
120 ($parent_trie:ident $path:literal $($rest:tt)*) => {
122 $parent_trie.insert(
123 ::std::path::PathBuf::from($path),
124 $crate::FsTree::Regular
125 );
126 $crate::trees_internal!($parent_trie $($rest)*);
127 };
128}
129
130#[cfg(test)]
131mod tests {
132 use pretty_assertions::assert_eq;
133
134 use crate::{FsTree, TrieMap};
135
136 #[test]
137 fn test_macro_compiles_with_literals_and_idents() {
138 tree! {
139 "folder": {
140 folder: {
141 file
142 "file"
143 link -> target
144 link -> "target"
145 "link" -> target
146 "link" -> "target"
147 }
148 }
149 };
150 }
151
152 #[test]
153 fn test_tree_macro_single_regular_file() {
154 let result = tree! {
155 file
156 };
157
158 let expected = FsTree::Directory(TrieMap::from([("file".into(), FsTree::Regular)]));
159
160 assert_eq!(result, expected);
161 }
162
163 #[test]
164 fn test_tree_macro_empty_directory() {
165 let result = tree! {
166 dir: {}
167 };
168
169 let expected = FsTree::Directory(TrieMap::from([("dir".into(), FsTree::new_dir())]));
170
171 assert_eq!(result, expected);
172 }
173
174 #[test]
175 fn test_tree_macro_single_symlink() {
176 let result = tree! {
177 link -> target
178 };
179
180 let expected = FsTree::Directory(TrieMap::from([(
181 "link".into(),
182 FsTree::Symlink("target".into()),
183 )]));
184
185 assert_eq!(result, expected);
186 }
187
188 #[test]
189 fn test_tree_macro_nested_directories() {
190 let result = tree! {
191 outer_dir: {
192 inner_dir: {
193 }
194 }
195 };
196
197 let expected = {
198 let mut tree = FsTree::new_dir();
199 tree.insert("outer_dir", FsTree::Directory(TrieMap::new()));
200 tree.insert("outer_dir/inner_dir", FsTree::Directory(TrieMap::new()));
201 tree
202 };
203
204 assert_eq!(result, expected);
205 }
206
207 #[test]
208 fn test_tree_macro_mixed_types() {
209 let result = tree! {
210 config
211 outer_dir: {
212 file1
213 file2
214 }
215 link -> target
216 };
217
218 let expected = {
219 let mut tree = FsTree::new_dir();
220 tree.insert("config", FsTree::Regular);
221 tree.insert("outer_dir", FsTree::Directory(TrieMap::new()));
222 tree.insert("outer_dir/file1", FsTree::Regular);
223 tree.insert("outer_dir/file2", FsTree::Regular);
224 tree.insert("link", FsTree::Symlink("target".into()));
225 tree
226 };
227
228 assert_eq!(result, expected);
229 }
230
231 #[rustfmt::skip]
232 #[test]
233 fn test_tree_macro_big_example() {
234 let result = tree! {
235 config1
236 config2
237 outer_dir: {
238 file1
239 file2
240 inner_dir: {
241 inner1
242 inner2
243 inner3
244 inner_link -> inner_target
245 }
246 }
247 link -> target
248 config3
249 };
250
251 let expected = FsTree::Directory(TrieMap::from([
252 ("config1".into(), FsTree::Regular),
253 ("config2".into(), FsTree::Regular),
254 ("outer_dir".into(), FsTree::Directory(TrieMap::from([
255 ("file1".into(), FsTree::Regular),
256 ("file2".into(), FsTree::Regular),
257 ("inner_dir".into(), FsTree::Directory(TrieMap::from([
258 ("inner1".into(), FsTree::Regular),
259 ("inner2".into(), FsTree::Regular),
260 ("inner3".into(), FsTree::Regular),
261 ("inner_link".into(), FsTree::Symlink("inner_target".into())),
262 ]))),
263 ]))),
264 ("link".into(), FsTree::Symlink("target".into())),
265 ("config3".into(), FsTree::Regular),
266 ]));
267
268 assert_eq!(result, expected);
269 }
270}