Skip to content

Commit

Permalink
Rework custom syntax
Browse files Browse the repository at this point in the history
- Implement a dedicated optimized searcher for the default syntax.
- Use the `aho-corasick` crate for custom syntax.
- Put custom syntax behind a feature.
  • Loading branch information
rossmacarthur committed Jan 17, 2025
1 parent 69cf095 commit 5054b80
Show file tree
Hide file tree
Showing 26 changed files with 507 additions and 785 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
name: cargo-hack

- name: Test
run: cargo test --workspace
run: cargo test --workspace --all-features

- name: Test (feature powerset)
run: cargo hack test --package upon --feature-powerset --depth 2 --lib --tests
Expand All @@ -71,12 +71,16 @@ jobs:
- uses: actions/checkout@v4

- uses: dtolnay/[email protected]
- name: Check (no filters)
- name: Check (no filters, no syntax)
run: cargo check --no-default-features --features serde,unicode

- uses: dtolnay/[email protected]
- name: Check (no filters)
run: cargo check --no-default-features --features serde,syntax,unicode

- uses: dtolnay/[email protected]
- name: Test
run: cargo test
run: cargo test --all-features
env:
RUSTFLAGS: --deny warnings

Expand Down
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"rust-analyzer.check.features": "all",
"rust-analyzer.cargo.features": "all",
"rust-analyzer.server.extraEnv": {
"RUSTFLAGS": "--cfg internal_debug"
},
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ include = ["src/**/*", "LICENSE-*", "README.md"]
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
aho-corasick = { version = "1.1.2", optional = true }
serde = { version = "1.0.137", optional = true }
unicode-ident = { version = "1.0.5", optional = true }
unicode-width = { version = "0.1.9", optional = true }
Expand All @@ -35,6 +36,10 @@ filters = []
# the context using `Value`'s '`From` impls.
serde = ["dep:serde"]

# Enables support for configuring custom delimiters in templates and pulls in
# the `aho-corasick` crate.
syntax = ["dep:aho-corasick"]

# Allows unicode identifiers in templates and enables improved error
# formatting.
unicode = ["dep:unicode-ident", "dep:unicode-width"]
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ The following crate features are available.
[`render_from(..)`][render_from] to render templates and
construct the context using [`Value`][value]’s `From` impls.

- **`syntax`** *(disabled by default)* — Enables support for configuring
custom delimiters in templates (see `Engine::with_syntax`) and pulls in
the `aho-corasick` crate.

- **`unicode`** *(enabled by default)* — Enables unicode support and pulls
in the [`unicode-ident`][unicode-ident] and
[`unicode-width`][unicode-width] crates. If disabled then unicode
Expand Down
4 changes: 2 additions & 2 deletions SYNTAX.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ the binary or provided at runtime (e.g. read from a file). A template
contains [**expressions**](#expressions) for rendering values and
[**blocks**](#blocks) for controlling logic. These require you to use
specific syntax delimiters in the template. Because `upon` allows you to
configure these delimiters, this document will only refer to the
**default** configuration.
configure these delimiters, this document will only refer to the **default**
configuration.

## Expressions

Expand Down
4 changes: 2 additions & 2 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ publish = false
[dependencies]
handlebars = "4.3.7"
liquid = "0.26.4"
minijinja = "1.0.5"
minijinja = { version = "1.0.5", features = ["custom_syntax"] }
rand = "0.8.5"
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.103"
tera = "1.19.0"
tinytemplate = "1.2.1"
upon = { path = ".." }
upon = { path = "..", features = ["syntax"] }

[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
Expand Down
23 changes: 23 additions & 0 deletions benches/benchdata/syntax/minijinja.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>{ title }</title>
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<%- for user in users -%>
<% if not user.is_disabled %>
<# This is a comment #>
<tr>
<td>{ user.name }</td>
<td>{ user.age }</td>
</tr>
<% endif %>
<%- endfor -%>
</table>
</body>
</html>
23 changes: 23 additions & 0 deletions benches/benchdata/syntax/upon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>{ title }</title>
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<%- for user in users -%>
<% if not user.is_disabled %>
<# This is a comment #>
<tr>
<td>{ user.name }</td>
<td>{ user.age }</td>
</tr>
<% endif %>
<%- endfor -%>
</table>
</body>
</html>
20 changes: 20 additions & 0 deletions benches/benches/engines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ criterion_group! {
benches,
bench_init,
bench_compile,
bench_syntax,
bench_render,
bench_filters,
}
Expand Down Expand Up @@ -57,6 +58,25 @@ pub fn bench_compile(c: &mut Criterion) {
bench!(Upon, "../benchdata/basic/upon.html");
}

/// Benchmarks the time taken to compile a template with custom syntax.
pub fn bench_syntax(c: &mut Criterion) {
let mut g = c.benchmark_group("syntax");

macro_rules! bench {
($E:ty, $source:literal) => {{
g.bench_function(<$E as Engine>::name(), |b| {
let source = repeat(include_str!($source), 50);
let mut engine =
<$E as Engine>::with_syntax(("{", "}"), ("<%", "%>"), ("<#", "#>"));
b.iter(|| engine.add_template("bench", &source));
});
}};
}

bench!(Minijinja, "../benchdata/syntax/minijinja.html");
bench!(Upon, "../benchdata/syntax/upon.html");
}

/// Benchmarks the time taken to render a template as a string.
pub fn bench_render(c: &mut Criterion) {
let mut g = c.benchmark_group("render");
Expand Down
53 changes: 44 additions & 9 deletions benches/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@ mod tests;
use std::collections::HashMap;

/// Abstraction for a template engine.
pub trait Engine<'a> {
pub trait Engine<'a>: Sized {
fn name() -> &'static str;
fn new() -> Self;
fn add_filters(&mut self);
fn with_syntax(
_expr: (&'static str, &'static str),
_block: (&'static str, &'static str),
_comment: (&'static str, &'static str),
) -> Self {
unimplemented!()
}
fn add_filters(&mut self) {
unimplemented!()
}
fn add_template(&mut self, name: &'static str, source: &'a str);
fn render<S>(&self, name: &'static str, ctx: &S) -> String
where
Expand Down Expand Up @@ -83,9 +92,6 @@ impl<'engine> Engine<'engine> for Liquid {
}
}

#[inline]
fn add_filters(&mut self) {}

#[inline]
fn add_template(&mut self, name: &'static str, source: &'engine str) {
let template = self.parser.parse(source).unwrap();
Expand Down Expand Up @@ -123,6 +129,24 @@ impl<'engine> Engine<'engine> for Minijinja<'engine> {
}

#[inline]
fn with_syntax(
(variable_start, variable_end): (&'static str, &'static str),
(block_start, block_end): (&'static str, &'static str),
(comment_start, comment_end): (&'static str, &'static str),
) -> Self {
let mut env = minijinja::Environment::new();
env.set_syntax(minijinja::Syntax {
block_start: block_start.into(),
block_end: block_end.into(),
variable_start: variable_start.into(),
variable_end: variable_end.into(),
comment_start: comment_start.into(),
comment_end: comment_end.into(),
})
.unwrap();
env
}

fn add_filters(&mut self) {}

#[inline]
Expand Down Expand Up @@ -156,7 +180,6 @@ impl<'engine> Engine<'engine> for Tera {
tera::Tera::default()
}

#[inline]
fn add_filters(&mut self) {}

#[inline]
Expand Down Expand Up @@ -195,9 +218,6 @@ impl<'engine> Engine<'engine> for TinyTemplate<'engine> {
tt
}

#[inline]
fn add_filters(&mut self) {}

#[inline]
fn add_template(&mut self, name: &'static str, source: &'engine str) {
self.add_template(name, source).unwrap();
Expand Down Expand Up @@ -229,6 +249,21 @@ impl<'engine> Engine<'engine> for upon::Engine<'engine> {
upon::Engine::new()
}

#[inline]
fn with_syntax(
(begin_expr, end_expr): (&'static str, &'static str),
(begin_block, end_block): (&'static str, &'static str),
(begin_comment, end_comment): (&'static str, &'static str),
) -> Self {
upon::Engine::with_syntax(
upon::Syntax::builder()
.expr(begin_expr, end_expr)
.block(begin_block, end_block)
.comment(begin_comment, end_comment)
.build(),
)
}

#[inline]
fn add_filters(&mut self) {
self.add_filter("lower", str::to_lowercase);
Expand Down
25 changes: 25 additions & 0 deletions benches/src/testdata/syntax_minijinja.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>My awesome webpage!</title>
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>

<tr>
<td>Nancy Wheeler</td>
<td>17</td>
</tr>


<tr>
<td>Steve Harrington</td>
<td>18</td>
</tr>
</table>
</body>
</html>
25 changes: 25 additions & 0 deletions benches/src/testdata/syntax_upon.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>My awesome webpage!</title>
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>

<tr>
<td>Nancy Wheeler</td>
<td>17</td>
</tr>


<tr>
<td>Steve Harrington</td>
<td>18</td>
</tr>
</table>
</body>
</html>
Loading

0 comments on commit 5054b80

Please sign in to comment.