tree-type 0.4.5

Rust macros for creating type-safe filesystem tree structures
Documentation
use assert2::let_assert;
use tempfile::TempDir;
use tree_type::tree_type;

// ============================================================================
// DIRECTORY PATTERNS - Root Level
// ============================================================================

tree_type! { EmptyDir { simple/ } }
tree_type! { DirWithAttrs { #[required] required/ } }
tree_type! { DirWithChildren { subdir/ { child("file.txt") } } }
tree_type! { DirWithAttrsChildren { #[required] subdir/ { child("file.txt") } } }
tree_type! { DirCustomType { dir/ as CustomDir } }

#[test]
fn test_empty_dir() {
    let temp = TempDir::new().unwrap();
    let root = EmptyDir::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.simple().exists());
}

#[test]
fn test_dir_with_attrs() {
    let temp = TempDir::new().unwrap();
    let root = DirWithAttrs::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.required().exists());
}

#[test]
fn test_dir_with_children() {
    let temp = TempDir::new().unwrap();
    let root = DirWithChildren::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.subdir().exists());
}

#[test]
fn test_dir_with_attrs_children() {
    let temp = TempDir::new().unwrap();
    let root = DirWithAttrsChildren::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.subdir().exists());
}

#[test]
fn test_dir_custom_type() {
    let temp = TempDir::new().unwrap();
    let root = DirCustomType::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.dir().exists());
}

// ============================================================================
// FILE PATTERNS - Root Level
// ============================================================================

tree_type! { SimpleFile { file("file.txt") } }
tree_type! { RequiredFile { #[required] file("file.txt") } }
tree_type! { OptionalFile { #[optional] file("file.txt") } }
tree_type! { FileCustomType { file("file.txt") as CustomFile } }

#[test]
fn test_simple_file() {
    let temp = TempDir::new().unwrap();
    let root = SimpleFile::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
}

#[test]
fn test_required_file() {
    let temp = TempDir::new().unwrap();
    let root = RequiredFile::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
}

#[test]
fn test_optional_file() {
    let temp = TempDir::new().unwrap();
    let root = OptionalFile::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
}

#[test]
fn test_file_custom_type() {
    let temp = TempDir::new().unwrap();
    let root = FileCustomType::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
}

// ============================================================================
// NESTING LEVELS
// ============================================================================

tree_type! {
    Nested1 {
        level1/ {
            #[required]
            file("file.txt")
        }
    }
}

tree_type! {
    Nested2 {
        level1/ {
            level2/ {
                #[required]
                file("file.txt")
            }
        }
    }
}

tree_type! {
    Nested3 {
        level1/ {
            level2/ {
                level3/ {
                    #[required]
                    file("file.txt")
                }
            }
        }
    }
}

#[test]
fn test_nested_1_level() {
    let temp = TempDir::new().unwrap();
    let root = Nested1::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.level1().exists());
}

#[test]
fn test_nested_2_levels() {
    let temp = TempDir::new().unwrap();
    let root = Nested2::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.level1().exists());
    assert!(root.level1().level2().exists());
}

#[test]
fn test_nested_3_levels() {
    let temp = TempDir::new().unwrap();
    let root = Nested3::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.level1().level2().level3().exists());
}

// ============================================================================
// DYNAMIC IDS
// ============================================================================

tree_type! {
    DynamicId1 {
        items/ {
            [id: String]/ as Item1 {
                #[required]
                file("data.txt")
            }
        }
    }
}

tree_type! {
    DynamicId2 {
        orgs/ {
            [org: String]/ as Org {
                [project: String]/ as Project {
                    #[required]
                    file("data.txt")
                }
            }
        }
    }
}

tree_type! {
    DynamicIdAttrs {
        items/ {
            [id: String]/ as Item2 {
                #[required]
                content/ {
                    #[required]
                    file("config.txt")
                }
            }
        }
    }
}

#[test]
fn test_dynamic_id_single() {
    let temp = TempDir::new().unwrap();
    let root = DynamicId1::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    let item = root.items().id("test");
    assert!(item.sync().is_ok());
}

#[test]
fn test_dynamic_id_nested() {
    let temp = TempDir::new().unwrap();
    let root = DynamicId2::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    let org = root.orgs().org("myorg");
    assert!(org.sync().is_ok());
    let project = org.project("myproject");
    assert!(project.sync().is_ok());
}

#[test]
fn test_dynamic_id_with_attrs() {
    let temp = TempDir::new().unwrap();
    let root = DynamicIdAttrs::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.items().exists());
    let item = root.items().id("test");
    assert!(item.sync().is_ok());
    assert!(item.content().exists());
}

// ============================================================================
// COMPLEX COMBINATIONS
// ============================================================================

tree_type! {
    MultipleDirs {
        config/,
        docs/,
        cache/
    }
}

tree_type! {
    ComplexMixed {
        root/ {
            [category: String]/ as Category {
                [item: String]/ as Item3 {
                    #[required]
                    metadata("meta.json"),
                    #[required]
                    settings("settings.toml")
                }
            }
        }
    }
}

#[test]
fn test_multiple_dirs() {
    let temp = TempDir::new().unwrap();
    let root = MultipleDirs::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.config().exists());
    assert!(root.docs().exists());
    assert!(root.cache().exists());
}

#[test]
fn test_complex_mixed() {
    let temp = TempDir::new().unwrap();
    let root = ComplexMixed::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());

    let category = root.root().category("books");
    assert!(category.sync().is_ok());

    let item_obj = category.item("book1");
    assert!(item_obj.sync().is_ok());
}

// ============================================================================
// CUSTOM TYPE WITH CHILDREN
// ============================================================================

tree_type! {
    DirCustomTypeChildren {
        files/ as DataDir {
            #[required]
            file("config.txt")
        }
    }
}

#[test]
fn test_dir_custom_type_with_children() {
    let temp = TempDir::new().unwrap();
    let root = DirCustomTypeChildren::new(temp.path()).unwrap();
    let_assert!(Ok(_) = root.sync());
    assert!(root.files().exists());
}