rustlavel_view/lib.rs
1//! rustlavel-view: templating in the spirit of Blade, written from scratch.
2//!
3//! A template is parsed once into an AST and then interpreted. That choice is
4//! what makes [`Engine::with_reload`] possible: with reload on, a designer
5//! edits `resources/views/pages/home.rl.html`, refreshes the browser, and sees
6//! the result without a recompile — the single ergonomic advantage Blade has
7//! over compiled Rust templates, and the one worth keeping.
8//!
9//! ```ignore
10//! use rustlavel_view::{Context, Engine};
11//!
12//! let engine = Engine::default().with_reload(!config.is_production());
13//! let html = engine.render("pages.home", &Context::new().with("title", "Home"))?;
14//! ```
15//!
16//! # The syntax
17//!
18//! ```html
19//! {{ user.name }} escaped output — always, no opt-out
20//! {!! post.body !!} raw output, for HTML you already trust
21//! {{-- a note --}} stripped before rendering
22//!
23//! @if(user.admin) ... @elseif(user.editor) ... @else ... @endif
24//! @foreach(posts as post) {{ loop.index }} {{ post.title }} @endforeach
25//!
26//! @extends("layouts.app")
27//! @section("title", page.name)
28//! @section("body") ... @endsection
29//! @yield("body")
30//! @include("parts.nav")
31//! ```
32//!
33//! Inside `@foreach`, `loop` carries `index`, `iteration`, `first`, `last`, and
34//! `count`. A structural directive alone on a line takes the line with it, so
35//! the rendered HTML looks like HTML.
36//!
37//! `@include` renders a partial in place, with the scope it was included from —
38//! a partial used inside a loop can read the loop's item and its `loop`
39//! variable, which is what makes `@include` worth having at all.
40//!
41//! # What is missing on purpose
42//!
43//! `@php` does not exist and will not. A template that can run code stops being
44//! reviewable as markup, hides logic from the compiler and from tests, and
45//! turns "just a view change" into a deployment risk. Compute the value in
46//! Rust, put it in the [`Context`], and render it. Templates using `@php` are
47//! rejected with an error that says exactly this.
48//!
49//! Expressions are equally limited — paths, literals, comparisons, `&&`, `||`,
50//! `!` — for the same reason. There is no arithmetic and there are no calls.
51//!
52//! # Escaping
53//!
54//! `{{ }}` escapes `&`, `<`, `>`, `"` and `'`, every time, with no way to turn
55//! it off. Markup that is genuinely trusted asks for [`escape`]'s absence out
56//! loud with `{!! !!}`, which makes the dangerous case the one that stands out
57//! in a diff.
58
59pub mod ast;
60pub mod context;
61pub mod engine;
62pub mod escape;
63pub mod expr;
64pub mod lexer;
65pub mod parser;
66pub mod source;
67pub mod value;
68
69mod render;
70
71pub use ast::{Branch, Node, Template, TemplateRef};
72pub use context::{Context, Scope};
73pub use engine::{DEFAULT_ROOT, EXTENSION, Engine};
74pub use escape::escape;
75pub use expr::{BinaryOp, Expr};
76pub use value::{to_text, truthy};