utkarshkukreti / markup.rs

A blazing fast, type-safe template engine for Rust.
Apache License 2.0
350 stars 14 forks source link

How to deduplicate reused elements #39

Closed rben01 closed 7 months ago

rben01 commented 7 months ago

I have code structured as follows:

markup::define! {
    Body(use_div: bool, n: usize) {
        @let x = "x";
        @if *use_div {
            div { @for _ in 0..*n { @x } }
            "it's a div"
        } else {
            span { @for _ in 0..*n { @x } }
            "it's a span"
        }
    }
}

Naturally I want to deduplicate the two identical loops, but I can't figure out what the most natural way to do this is.

Attempts that don't compile:

@let for_loop = @for _ in 0..*n { @x };  // and various combinations with/without @
@if *use_div {
    div { @for_loop }
...
@let for_loop = || {
    @for _ in 0..*n { @x }  // and various combinations with/without @
};
@if *use_div {
    div { @for_loop() }
...
@let for_loop = || {
    for _ in 0..*n { x }  // the trait bound `(): markup::Render` is not satisfied (makes sense)
};
@if *use_div {
    div { @for_loop() }
...

I suppose I could define a new struct in the define! and then render it as follows, but I'm hoping for something a bit simpler if possible (I don't want to have to do this every time I have a bit of repeated work).

markup::define! {
    Loop<'a>(x: &'a str, n: usize) {
        @for _ in 0..*n {
            @x
        }
    }
    Body(use_div: bool, n: usize) {
        @let x = "x";
        @let for_loop = Loop { x, n: *n };
        @if *use_div {
            div { @for_loop }
            "it's a div"
        } else {
            span { @for_loop }
            "it's a span"
        }
    }
}
utkarshkukreti commented 7 months ago

Not sure if this would apply to your real use case, but for this exact example, you can use a dynamic element name ${expr}:

markup::define! {
    Body(use_div: bool, n: usize) {
        @let name = if *use_div { "div" } else { "span" };
        @let x = "x";
        ${name} {
            @for _ in 0..*n { @x }
        }
        "it's a " @name
    }
}

Does this help?

utkarshkukreti commented 7 months ago

Or you can use markup::new! to define an anonymous template:

markup::define! {
    Body(use_div: bool, n: usize) {
        @let x = "x";
        @let for_loop = markup::new! {
            @for _ in 0..*n { @x }
        };
        @if *use_div {
            div { @for_loop }
            "it's a div"
        } else {
            span { @for_loop }
            "it's a span"
        }
    }
}
rben01 commented 7 months ago

Thanks! I hadn't considered the inline use of markup::new! but that's exactly what I'm looking for.