#template #html #pug #haml #generate

markup

一个针对 Rust 的闪电般快速、类型安全的模板引擎

35 个版本

0.15.0 2023 年 11 月 23 日
0.13.1 2022 年 6 月 27 日
0.12.5 2021 年 9 月 11 日
0.12.2 2021 年 5 月 24 日
0.1.1 2018 年 12 月 3 日

#28模板引擎

Download history 503/week @ 2024-04-28 707/week @ 2024-05-05 938/week @ 2024-05-12 717/week @ 2024-05-19 745/week @ 2024-05-26 1158/week @ 2024-06-02 1325/week @ 2024-06-09 1223/week @ 2024-06-16 1266/week @ 2024-06-23 1075/week @ 2024-06-30 936/week @ 2024-07-07 520/week @ 2024-07-14 850/week @ 2024-07-21 887/week @ 2024-07-28 661/week @ 2024-08-04 802/week @ 2024-08-11

3,278 每月下载量
用于 5 包 (4 个直接使用)

MIT/Apache

28KB
377

markup.rs

一个针对 Rust 的闪电般快速、类型安全的模板引擎。

Build Version Documentation Downloads License

markup.rs 是一个由过程宏驱动的 Rust 模板引擎,它会在编译时解析模板,并在运行时生成最优的 Rust 代码以渲染模板。模板可以嵌入 Rust 代码,并由 Rust 编译器进行类型检查,从而实现完全的类型安全。

特性

  • 完全类型安全,当使用像 rust-analyzer 这样的编辑器扩展时,会在行内高亮显示错误。
  • 语法简洁,受 HamlSlimPug 启发。
  • 零不安全代码。
  • 零运行时依赖。
  • ⚡ 闪电般快速。在不使用不安全代码的 benchmark 中,这是最快的,总体排名第二。

安装

[dependencies]
markup = "0.15.0"

框架集成

我们为以下 Web 框架提供了集成示例

  1. Axum
  2. Rocket

快速示例

markup::define! {
    Home<'a>(title: &'a str) {
        @markup::doctype()
        html {
            head {
                title { @title }
                style {
                    "body { background: #fafbfc; }"
                    "#main { padding: 2rem; }"
                }
            }
            body {
                @Header { title }
                #main {
                    p {
                        "This domain is for use in illustrative examples in documents. You may \
                        use this domain in literature without prior coordination or asking for \
                        permission."
                    }
                    p {
                        a[href = "https://www.iana.org/domains/example"] {
                            "More information..."
                        }
                    }
                }
                @Footer { year: 2020 }
            }
        }
    }

    Header<'a>(title: &'a str) {
        header {
            h1 { @title }
        }
    }

    Footer(year: u32) {
        footer {
            "(c) " @year
        }
    }
}

fn main() {
    println!(
        "{}",
        Home {
            title: "Example Domain"
        }
    )
}

输出

<!DOCTYPE html><html><head><title>Example Domain</title><style>body { background: #fafbfc; }#main { padding: 2rem; }</style></head><body><header><h1>Example Domain</h1></header><div id="main"><p>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</p><p><a href="https://www.iana.org/domains/example">More information...</a></p></div><footer>(c) 2020</footer></body></html>

输出(手动美化)

<!doctype html>
<html>
  <head>
    <title>Example Domain</title>
    <style>
      body {
        background: #fafbfc;
      }
      #main {
        padding: 2rem;
      }
    </style>
  </head>
  <body>
    <header><h1>Example Domain</h1></header>
    <div id="main">
      <p>
        This domain is for use in illustrative examples in documents. You may
        use this domain in literature without prior coordination or asking for
        permission.
      </p>
      <p>
        <a href="https://www.iana.org/domains/example">More information...</a>
      </p>
    </div>
    <footer>(c) 2020</footer>
  </body>
</html>

语法参考(WIP)

markup::define! 和 markup::new!

定义模板有两种方式:markup::define!markup::new!

markup::define! 定义了一个带有命名参数的模板。这些模板无法访问外部作用域中的变量。模板可以有泛型参数。在底层,markup::define! 编译为实现了 markup::Renderstd::fmt::Display 特性的 Rust 结构。

代码
markup::define! {
    Hello<'a>(name: &'a str) {
        "Hello, " @name "!"
    }
    HelloGeneric<T: std::fmt::Display>(name: T) {
        "Hello, " @name.to_string() "!"
    }
}

// The template can now be printed directly or written to a stream:
println!("{}", Hello { name: "World" });
writeln!(&mut std::io::stdout(), "{}", HelloGeneric { name: "World 2" }).unwrap();

// The template can also be rendered to a String:
let string = Hello { name: "World 3" }.to_string();
println!("{}", string);
输出
Hello, World!
Hello, World 2!
Hello, World 3!

markup::new! 定义了一个不带任何参数的模板。这些模板可以访问外部作用域中的变量。

代码
let name = "World";
let template = markup::new! {
    "Hello, " @name "!"
};

// The template can now be printed directly or written to a stream:
println!("{}", template);
writeln!(&mut std::io::stdout(), "{}", template).unwrap();

// The template can also be rendered to a String:
let string = template.to_string();
println!("{}", string);
输出
Hello, World!
Hello, World!
Hello, World!

表达式

模板可以包含裸文本值,它们将按原样渲染。也可以包含以 @ 符号开头的表达式(包括函数和宏调用)。除非它们被 markup::raw() 包裹,否则所有字符串都将进行 HTML 转义。

代码
markup::define! {
    Expressions(a: i32, b: i32) {
        1 " + " 2 " = " @{1 + 2} '\n'
        @a " - " @b " = " @{a - b} '\n'
        @format!("{} * {} = {}", a, b, a * b) '\n'
        @a " ^ 4 = " @a.pow(4) '\n'

        // All output is escaped by default.
        "<>\n"
        // Escaping can be disabled using `markup::raw()`.
        @markup::raw("<div></div>")
    }
}

println!("{}", Expressions { a: 5, b: 3 });
输出
1 + 2 = 3
5 - 3 = 2
5 * 3 = 15
5 ^ 4 = 625
&lt;&gt;
<div></div>

元素

元素使用类似于 CSS 选择器的语法定义。元素可以包含在大括号中的嵌套元素,或者用分号自闭合。

代码
markup::define! {
    Elements(name: &'static str) {
        // Just a div.
        div {}
        '\n'
        // Three nested elements.
        main {
            aside {
                h3 { "Sidebar" }
            }
        }
        '\n'
        // Self-closing input element.
        input;
        '\n'
        // Element with a name containing dashes.
        $"my-custom-element" {}
        '\n'
        // Element with a dynamic name.
        ${name} {}
    }
}

println!("{}", Elements { name: "span" });
输出
<div></div>
<main><aside><h3>Sidebar</h3></aside></main>
<input>
<my-custom-element></my-custom-element>
<span></span>

属性

属性定义在元素名称之后。可以使用类似于 CSS 选择器的语法使用 #. 定义 idclass 属性。类可以使用这种简写语法指定多次。其他属性用方括号指定。

代码
markup::define! {
    Attributes(id: u32, category: String, data: std::collections::BTreeMap<String, String>) {
        // A div with an id and two classes.
        // Note: Starting with Rust 2021, there must be a space between the
        // element name and `#` due to reserved syntax (http://doc.rust-lang.net.cn/edition-guide/rust-2021/reserving-syntax.html).
        div #foo.bar.baz {}
        '\n'
        // A div with a dynamically computed id and one static and one dynamic class.
        div #{format!("post-{}", id)}.post.{format!("category-{}", category)} {}
        '\n'

        // Boolean attributes are only rendered if true. Specifying no value is the same as `true`.
        input[checked = true];
        '\n'
        input[checked = false];
        '\n'
        input[checked];
        '\n'

        // `Option` attributes are rendered only if they're `Some`.
        input[type = Some("text"), minlength = None::<String>];
        '\n'

        // Attribute names can also be expressions wrapped in braces.
        div[{format!("{}{}", "data-", "post-id")} = id] {}
        '\n'

        // Multiple attributes can be added dynamically using the `..` syntax.
        div[..data.iter().map(|(k, v)| (("data-", k), v))] {}
    }
}

println!("{}", Attributes {
    id: 123,
    category: String::from("tutorial"),
    data: [
        (String::from("foo"), String::from("bar")),
        (String::from("baz"), String::from("quux"))
    ].iter().cloned().collect(),
});
输出
<div id="foo" class="bar baz"></div>
<div id="post-123" class="post category-tutorial"></div>
<input checked>
<input>
<input checked>
<input type="text">
<div data-post-id="123"></div>
<div data-baz="quux" data-foo="bar"></div>

@if 和 @if let

@if@if let 的工作方式与 Rust 类似。

代码
markup::define! {
    If(x: u32, y: Option<u32>) {
        @if *x == 1 {
            "x = 1\n"
        } else if *x == 2 {
            "x = 2\n"
        } else {
            "x is neither 1 nor 2\n"
        }

        @if let Some(y) = y {
            "y = " @y "\n"
        } else {
            "y is None\n"
        }
    }
}

println!("{}", If { x: 2, y: Some(2) });
println!("{}", If { x: 3, y: None });
输出
x = 2
y = 2

x is neither 1 nor 2
y is None

@match

@match 的工作方式与 Rust 类似,但分支必须用大括号包裹。

代码
markup::define! {
    Match(x: Option<u32>) {
        @match x {
            Some(1) | Some(2) => {
                "x is 1 or 2"
            }
            Some(x) if *x == 3 => {
                "x is 3"
            }
            None => {
                "x is None"
            }
            _ => {
                "x is something else"
            }
        }
    }
}

println!("{}", Match { x: None });
println!("{}", Match { x: Some(2) });
println!("{}", Match { x: Some(4) });
输出
x is None
x is 1 or 2
x is something else

@for

@for 的工作方式与 Rust 类似。

代码
markup::define! {
    For<'a>(xs: &'a [u32]) {
        @for (i, x) in xs.iter().enumerate() {
            @i ": " @x "\n"
        }
    }
}

println!("{}", For { xs: &[1, 2, 4, 8] });
输出
0: 1
1: 2
2: 4
3: 8

语句

模板可以包含以 @ 符号开头的语句。最有用的此类语句是 @let,用于计算一个值以供以后重用。@fn 可以用来定义一个函数。还支持 @struct@mod@impl@const@static 等。

代码
markup::define! {
    Statement(x: i32) {
        @let double_x = x * 2;
        @double_x '\n'

        @fn triple(x: i32) -> i32 { x * 3 }
        @triple(*x)
    }
}

println!("{}", Statement { x: 2 });
输出
4
6

依赖

~270–720KB
~17K SLoC