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