1#[macro_export]
127macro_rules! tree {
128 ($($all:tt)*) => {{
129 #[allow(unused_mut)]
130 let mut trie = $crate::TrieMap::new();
131 $crate::tree_internal!(trie $($all)*);
133 $crate::FsTree::Directory(trie)
134 }};
135}
136
137#[doc(hidden)]
138#[macro_export]
139macro_rules! tree_internal {
140 ($parent:ident) => {};
142 ($parent:ident $path:ident : $($rest:tt)*) => {
143 $crate::tree_internal_dir!($parent { ::std::stringify!($path) } $($rest)*)
144 };
145 ($parent:ident $path:literal : $($rest:tt)*) => {
146 $crate::tree_internal_dir!($parent { $path } $($rest)*)
147 };
148 ($parent:ident { $path:expr } : $($rest:tt)*) => {
149 $crate::tree_internal_dir!($parent { $path } $($rest)*)
150 };
151
152 ($parent:ident $path:ident -> $($rest:tt)*) => {
159 $crate::tree_internal_symlink!($parent { ::std::stringify!($path) } $($rest)*)
160 };
161 ($parent:ident $path:literal -> $($rest:tt)*) => {
162 $crate::tree_internal_symlink!($parent { $path } $($rest)*)
163 };
164 ($parent:ident { $path:expr } -> $($rest:tt)*) => {
165 $crate::tree_internal_symlink!($parent { $path } $($rest)*)
166 };
167
168 ($parent:ident $path:ident $($rest:tt)*) => {
169 $crate::tree_internal_regular!($parent { ::std::stringify!($path) } $($rest)*);
170 };
171 ($parent:ident $path:literal $($rest:tt)*) => {
172 $crate::tree_internal_regular!($parent { $path } $($rest)*);
173 };
174 ($parent:ident { $path:expr } $($rest:tt)*) => {
175 $crate::tree_internal_regular!($parent { $path } $($rest)*);
176 };
177}
178
179#[doc(hidden)]
180#[macro_export]
181macro_rules! tree_internal_dir {
182 ($parent:ident { $path:expr } [ $($inner:tt)* ] $($rest:tt)*) => {
183 #[allow(unused_mut)]
184 let mut node = $crate::TrieMap::new();
185 $crate::tree_internal!(node $($inner)*);
186 $parent.insert(
187 ::std::path::PathBuf::from($path),
188 $crate::FsTree::Directory(node)
189 );
190 $crate::tree_internal!($parent $($rest)*)
191 };
192}
193
194#[doc(hidden)]
195#[macro_export]
196macro_rules! tree_internal_regular {
197 ($parent:ident { $path:expr } $($rest:tt)*) => {
198 $parent.insert(
199 ::std::path::PathBuf::from($path),
200 $crate::FsTree::Regular
201 );
202 $crate::tree_internal!($parent $($rest)*);
203 };
204}
205
206#[doc(hidden)]
207#[macro_export]
208macro_rules! tree_internal_symlink {
209 ($parent:ident { $path:expr } $target:ident $($rest:tt)*) => {
211 $crate::tree_internal_symlink!(@done $parent { $path } { ::std::stringify!($target) } $($rest)*)
212 };
213 ($parent:ident { $path:expr } $target:literal $($rest:tt)*) => {
214 $crate::tree_internal_symlink!(@done $parent { $path } { $target } $($rest)*)
215 };
216 ($parent:ident { $path:expr } { $target:expr } $($rest:tt)*) => {
217 $crate::tree_internal_symlink!(@done $parent { $path } { $target } $($rest)*)
218 };
219
220 (@done $parent:ident { $path:expr } { $target:expr } $($rest:tt)*) => {
222 $parent.insert(
223 ::std::path::PathBuf::from($path),
224 $crate::FsTree::Symlink(::std::path::PathBuf::from($target))
225 );
226 $crate::tree_internal!($parent $($rest)*)
227 };
228}
229
230#[cfg(test)]
231mod tests {
232 use std::path::PathBuf;
233
234 use pretty_assertions::assert_eq;
235
236 use crate::{FsTree, TrieMap};
237
238 #[test]
239 fn test_tree_macro_empty() {
240 assert_eq!(tree! {}, FsTree::Directory(TrieMap::new()));
241 }
242
243 #[test]
244 fn test_tree_macro_compiles_with_literals_and_idents() {
245 tree! {
246 "folder": [
247 folder: [
248 file
249 "file"
250 link -> target
251 link -> "target"
252 "link" -> target
253 "link" -> "target"
254 ]
255 ]
256 };
257 }
258
259 #[test]
260 fn test_tree_macro_single_regular_file() {
261 let result = tree! {
262 file
263 };
264 let expected = FsTree::Directory(TrieMap::from([("file".into(), FsTree::Regular)]));
265 assert_eq!(result, expected);
266 }
267
268 #[test]
269 fn test_tree_macro_empty_directory() {
270 let result = tree! { dir: [] };
271 let expected = FsTree::Directory(TrieMap::from([("dir".into(), FsTree::new_dir())]));
272 assert_eq!(result, expected);
273 }
274
275 #[test]
276 fn test_tree_macro_single_symlink() {
277 let result = tree! {
278 link -> target
279 };
280
281 let expected = FsTree::Directory(TrieMap::from([(
282 "link".into(),
283 FsTree::Symlink("target".into()),
284 )]));
285
286 assert_eq!(result, expected);
287 }
288
289 #[test]
290 fn test_tree_macro_nested_directories() {
291 let result = tree! {
292 outer_dir: [
293 inner_dir: []
294 ]
295 };
296
297 let expected = {
298 let mut tree = FsTree::new_dir();
299 tree.insert("outer_dir", FsTree::Directory(TrieMap::new()));
300 tree.insert("outer_dir/inner_dir", FsTree::Directory(TrieMap::new()));
301 tree
302 };
303
304 assert_eq!(result, expected);
305 }
306
307 #[test]
308 fn test_tree_macro_mixed_types() {
309 let result = tree! {
310 config
311 outer_dir: [
312 file1
313 file2
314 ]
315 link -> target
316 };
317
318 let expected = {
319 let mut tree = FsTree::new_dir();
320 tree.insert("config", FsTree::Regular);
321 tree.insert("outer_dir", FsTree::Directory(TrieMap::new()));
322 tree.insert("outer_dir/file1", FsTree::Regular);
323 tree.insert("outer_dir/file2", FsTree::Regular);
324 tree.insert("link", FsTree::Symlink("target".into()));
325 tree
326 };
327
328 assert_eq!(result, expected);
329 }
330
331 #[rustfmt::skip]
332 #[test]
333 fn test_tree_macro_big_example() {
334 let result = tree! {
335 config1
336 config2
337 outer_dir: [
338 file1
339 file2
340 inner_dir: [
341 inner1
342 inner2
343 inner3
344 inner_link -> inner_target
345 ]
346 ]
347 link -> target
348 config3
349 };
350
351 let expected = FsTree::Directory(TrieMap::from([
352 ("config1".into(), FsTree::Regular),
353 ("config2".into(), FsTree::Regular),
354 ("outer_dir".into(), FsTree::Directory(TrieMap::from([
355 ("file1".into(), FsTree::Regular),
356 ("file2".into(), FsTree::Regular),
357 ("inner_dir".into(), FsTree::Directory(TrieMap::from([
358 ("inner1".into(), FsTree::Regular),
359 ("inner2".into(), FsTree::Regular),
360 ("inner3".into(), FsTree::Regular),
361 ("inner_link".into(), FsTree::Symlink("inner_target".into())),
362 ]))),
363 ]))),
364 ("link".into(), FsTree::Symlink("target".into())),
365 ("config3".into(), FsTree::Regular),
366 ]));
367
368 assert_eq!(result, expected);
369 }
370
371 #[rustfmt::skip]
372 #[test]
373 fn test_tree_macro_with_expressions() {
374 let config = |index: i32| format!("config{index}");
375
376 let result = tree! {
377 {config(1)}
378 {"config2".to_string()}
379 "outer_dir": [
380 {{
381 let mut string = String::new();
382 string.push_str("file");
383 string.push('1');
384 string
385 }}
386 file2
387 {"inner".to_string() + "_" + "dir"}: [
388 inner1
389 {{"inner2"}}
390 inner3
391 { format!("inner_link") } -> { ["inner_target"][0] }
392 ]
393 ]
394 link -> { PathBuf::from("target") }
395 config3
396 };
397
398 let expected = FsTree::Directory(TrieMap::from([
399 ("config1".into(), FsTree::Regular),
400 ("config2".into(), FsTree::Regular),
401 ("outer_dir".into(), FsTree::Directory(TrieMap::from([
402 ("file1".into(), FsTree::Regular),
403 ("file2".into(), FsTree::Regular),
404 ("inner_dir".into(), FsTree::Directory(TrieMap::from([
405 ("inner1".into(), FsTree::Regular),
406 ("inner2".into(), FsTree::Regular),
407 ("inner3".into(), FsTree::Regular),
408 ("inner_link".into(), FsTree::Symlink("inner_target".into())),
409 ]))),
410 ]))),
411 ("link".into(), FsTree::Symlink("target".into())),
412 ("config3".into(), FsTree::Regular),
413 ]));
414
415 assert_eq!(result, expected);
416 }
417
418 #[rustfmt::skip]
419 #[test]
420 fn test_tree_macro_with_symlinks_all_possibilities() {
421
422 let result = tree! {
424 a1 -> b1
425 a2 -> "b2"
426 a3 -> {"b3"}
427 "a4" -> b4
428 "a5" -> "b5"
429 "a6" -> {"b6"}
430 {"a7"} -> b7
431 {"a8"} -> "b8"
432 {"a9"} -> {"b9"}
433 };
434
435 let expected = FsTree::Directory(TrieMap::from([
436 ("a1".into(), FsTree::Symlink("b1".into())),
437 ("a2".into(), FsTree::Symlink("b2".into())),
438 ("a3".into(), FsTree::Symlink("b3".into())),
439 ("a4".into(), FsTree::Symlink("b4".into())),
440 ("a5".into(), FsTree::Symlink("b5".into())),
441 ("a6".into(), FsTree::Symlink("b6".into())),
442 ("a7".into(), FsTree::Symlink("b7".into())),
443 ("a8".into(), FsTree::Symlink("b8".into())),
444 ("a9".into(), FsTree::Symlink("b9".into())),
445 ]));
446
447 assert_eq!(result, expected);
448 }
449}