From 95ffb400c41c0c17a8bd3d0eb16200c5fc594440 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Fri, 18 Oct 2024 11:43:55 -0700 Subject: [PATCH] save --- docs/docs/controllers/sessions.md | 4 +- docs/docs/views/index.md | 6 +- docs/docs/views/templates/.pages | 7 + docs/docs/views/templates/caching.md | 19 +++ docs/docs/views/templates/for-loops.md | 28 ++++ docs/docs/views/templates/functions.md | 1 + docs/docs/views/templates/if-statements.md | 45 ++++++ docs/docs/views/templates/index.md | 48 +++++- docs/docs/views/templates/nomenclature.md | 1 + docs/docs/views/templates/variables.md | 162 +++++++++++++++++++++ examples/dynamic-templates/src/main.rs | 30 ++++ 11 files changed, 346 insertions(+), 5 deletions(-) create mode 100644 docs/docs/views/templates/.pages create mode 100644 docs/docs/views/templates/caching.md create mode 100644 docs/docs/views/templates/for-loops.md create mode 100644 docs/docs/views/templates/functions.md create mode 100644 docs/docs/views/templates/if-statements.md create mode 100644 docs/docs/views/templates/nomenclature.md create mode 100644 docs/docs/views/templates/variables.md diff --git a/docs/docs/controllers/sessions.md b/docs/docs/controllers/sessions.md index 7432cddf..395f0496 100644 --- a/docs/docs/controllers/sessions.md +++ b/docs/docs/controllers/sessions.md @@ -42,4 +42,6 @@ let response = Response::new() ## Renew sessions -Sessions are automatically renewed on each request. Expired sessions are renewed as well, unless session [authentication](../authentication) is enabled. +Sessions are automatically renewed on each request. This allows your active users to remain "logged in", while inactive ones would be redirected to a login page if session [authentication](../authentication) is enabled. + +Expired sessions are not renewed, so a user holding an expired session will need to use an authentication controller to get a new valid session. diff --git a/docs/docs/views/index.md b/docs/docs/views/index.md index 33ba771d..4d5a9229 100644 --- a/docs/docs/views/index.md +++ b/docs/docs/views/index.md @@ -1,15 +1,15 @@ # Views basics -Rwf comes with a [templating](templates/) library that can generate dynamic pages. Dynamic templates allow you to generate unique HTML pages on the fly, and libraries like [Turbo](turbo) can use it in a way that feels like a native frontend application. +Rwf comes with a [templating](templates/) library that can generate dynamic pages. Dynamic templates allow you to create unique HTML pages on the fly, and libraries like [Turbo](turbo) can use it in a way that feels like a native frontend application. ## What are views? -Views are the **V** in MVC: they control what your users see and how they experience your web app. Separating views from [controllers](../controllers/) allows them to reuse similar parts of your web app on different pages. +Views are the **V** in MVC: they control what your users see and how they experience your web app. Separating views from [controllers](../controllers/) allows controllers to reuse similar parts of your web app on different pages without code duplication. ## Using JavaScript frontends -If you prefer to build your frontend with JavaScript libraries like React or Vue, take a look at Rwf's [REST](../controllers/REST/) API documentation. While useful, Rwf templates are not required to build web applications. +If you prefer to build your frontend with JavaScript libraries like React or Vue, take a look at Rwf's [REST](../controllers/REST/) API documentation. Rwf templates are not required to build web applications. ## Learn more diff --git a/docs/docs/views/templates/.pages b/docs/docs/views/templates/.pages new file mode 100644 index 00000000..f65aafaa --- /dev/null +++ b/docs/docs/views/templates/.pages @@ -0,0 +1,7 @@ +nav: + - 'index.md' + - 'variables.md' + - 'if-statements.md' + - 'for-loops.md' + - 'functions.md' + - '...' diff --git a/docs/docs/views/templates/caching.md b/docs/docs/views/templates/caching.md new file mode 100644 index 00000000..aa6fa409 --- /dev/null +++ b/docs/docs/views/templates/caching.md @@ -0,0 +1,19 @@ +# Template cache + +Templates are compiled and evaluated on the fly. This is handy for local development, allowing you to modify the template without recompiling the Rust app or restarting the web server, but in production could be an unnecessary performance hindrance. + +The template cache makes sure a template is compiled only once. All subsequent executions of the template will use an internal representation and are much faster to run. + +## Using the cache + +To use the template cache, templates must be stored on disk, for example in a `templates` directory. Loading a template should use the [`Template::load`](https://docs.rs/rwf/latest/rwf/view/template/struct.Template.html#method.load) function: + +```rust +let template = Template::load("templates/index.html")?; +``` + +The first time the template is loaded, it will be fetched from disk and compiled. Once compiled, it will be stored in the cache to be reused by all subsequent calls to [`Template::load`](https://docs.rs/rwf/latest/rwf/view/template/struct.Template.html#method.load). + +## Enable the cache + +The template cache is disabled by default. To enable it, toggle the `cache_templates` setting in [configuration](../../../configuration). diff --git a/docs/docs/views/templates/for-loops.md b/docs/docs/views/templates/for-loops.md new file mode 100644 index 00000000..50f9083e --- /dev/null +++ b/docs/docs/views/templates/for-loops.md @@ -0,0 +1,28 @@ +# For loops + +Rwf templates have only one kind of for loop: for each. This allows writing more reliable templates, and to avoid common bugs like infinite loops, which will stall a web app in production. + +A for loop can iterate over a list of values, for example: + +```erb + +``` + +Template lists, unlike Rust's `Vec`, can hold [variables](../variables) of different data types, and are dynamically evaluted at runtime: + +=== "Template" + ```erb + <% for value in ["one", 2 * 5, 3/1.5] %> + <%= value %> + <% end %> + ``` +=== "Output" + ``` + one + 10 + 2.0 + ``` diff --git a/docs/docs/views/templates/functions.md b/docs/docs/views/templates/functions.md new file mode 100644 index 00000000..e8e881bf --- /dev/null +++ b/docs/docs/views/templates/functions.md @@ -0,0 +1 @@ +# Functions diff --git a/docs/docs/views/templates/if-statements.md b/docs/docs/views/templates/if-statements.md new file mode 100644 index 00000000..14484214 --- /dev/null +++ b/docs/docs/views/templates/if-statements.md @@ -0,0 +1,45 @@ +# If statements + +If statements allow you to control the flow of templates, conditionally displaying some elements while hiding others. For example, if a [variable](../variables) is "falsy", you can hide entire sections of your website: + +```erb +<% if logged_in %> + +<% else %> + +<% end %> +``` + +If statements start with `if` and must always finish with `end`. + +## Expressions + +If statements support evaluating large expressions for truthiness, for example: + +```erb +<% if var.lower + "_" + bar.upper == "lo_HI" %> + +<% end %> +``` + +While it's advisable to write simple if statements and delegate complex logic to views where the Rust compiler can be more helpful, Rwf template language is almost [Turing-complete](https://en.wikipedia.org/wiki/Turing_completeness) and can be used to write arbitrarily complex templates. + +### Operator precendence + +Templates respect operator precedence, e.g., multiplication is performed before addition, unless parentheses are specified (which are also supported). + +## Else If + +If statements support else if blocks (written as `elsif`), evaluating multiple expressions and executing the first one which evaluates to true: + +```erb +<% if one %> + +<% elsif two %> + +<% elsif three %> + +<% else %> + +<% end %> +``` diff --git a/docs/docs/views/templates/index.md b/docs/docs/views/templates/index.md index 95d30a04..41d2eb3c 100644 --- a/docs/docs/views/templates/index.md +++ b/docs/docs/views/templates/index.md @@ -1 +1,47 @@ -# Templates overview +# Templates overview + +Dynamic templates are a mix of HTML and a programming language which directs how the HTML is displayed. For example, if you have a profile page for your web app users, you would want each of your users to have a page unique to them. To achieve this, you would write only one template and substitute unique aspects of each using template variables, for example: + +```erb +
+

<%= username %>

+

<%= bio %>

+
+``` + +The variables `username` and `bio` can be substituded for values unique to each of your users, for example: + +=== "Rust" + ```rust + use rwf::prelude::*; + + let template = Template::from_str(r#" +
+

<%= username %>

+

<%= bio %>

+
+ "#)?; + + let html = template.render([ + ("username", "Alice"), + ("bio", "I like turtles") + ])?; + + println!("{}", html); + ``` +=== "Output" + ```html +
+

Alice

+

I like turtles

+
+ ``` + +Templates help reuse HTML (and CSS, JavaScript) just like regular functions and structs help +reuse code. + +## Learn more + +- [Variables](variables) +- [For loops](for-loops) +- [If statements](if-statements) diff --git a/docs/docs/views/templates/nomenclature.md b/docs/docs/views/templates/nomenclature.md new file mode 100644 index 00000000..94a49100 --- /dev/null +++ b/docs/docs/views/templates/nomenclature.md @@ -0,0 +1 @@ +# Nomenclature diff --git a/docs/docs/views/templates/variables.md b/docs/docs/views/templates/variables.md new file mode 100644 index 00000000..d98eaa5b --- /dev/null +++ b/docs/docs/views/templates/variables.md @@ -0,0 +1,162 @@ +# Variables + +Template variables are used to substitute unique information into a reusable template. Rwf supports variables of different kinds, like strings, numbers, lists, and hashes. Complex variables like hashes can be iterated through using [for loops](../for-loops). + +## Using variables + +Using variables in your templates is typically done by "printing" them, or outputting them, into the template text, by placing them between `<%=` and `%>` tags: + +```erb +<%= variable %> +``` + +The `<%=` tag indicates what follows is an [expression](../nomenclature), which should be evaluated and converted to text for displaying purposes. + +The `%>` tag is not specific to printing variables, and indicates the end of a code block inside a template. What follows that tag is just regular text which has no special meaning. + +## Defining variables + +A variable is defined when a template is rendered. Using one of many possible ways to define a [context](../context), the variable is given a value at runtime: + +=== "Rust" + ```rust + let template = Template::from_str("<%= variable %>")?; + let string = template.render([ + ("variable", "I love pancakes for dinner."), + ])?; + println!("{}", string); + ``` +=== "Output" + ``` + I love pancakes for dinner. + ``` + +### Missing variables + +It's not uncommon to forget to define variables, epecially if a template is large, or used in multiple places in the app where some variables don't have a known value. + +If an undefined variable is used in a template, Rwf will throw a runtime error. This is good for debugging issues when variables are unintentionally forgotten by the developer. However, if the variable is not always available, you can check if it's defined first: + +```erb +<% if variable %> +

<%= variable %>

+<% end %> +``` + +Due to the nature of [if statements](../if-statements), if the variable is defined and evaluates to a "falsy" value, e.g. `0`, `""` (empty string), `null`, etc., the if statement will not be executed either. This is helpful for handling many similar cases without having to write complex statements. + +## Supported data types + +Rwf variables support most Rust data types. The conversion between Rust and the template language happens automatically. + +### Number + +Rwf supports two kinds of numbers: integers and floating points. + +An integer is any whole number, negative or positive (including zero). Rust has many integer types, e.g. `i8`, `i32`, `u64`, etc., but the template language converts all of them to an 64-bit singed integer: + +=== "Template" + ```erb + <%= 500 %> + ``` +=== "Output" + ``` + 500 + ``` + +Rust's `f32` and `f64` are converted to 64-bit double precision floating point. Operations between integers and floating points are supported, the final result being a float: + +=== "Template" + ```erb + <%= 500 + 1.5 %> + ``` +=== "Output" + ``` + 501.5 + ``` + +Numbers can be [converted](../functions) to strings, floored, ceiled and rounded, for example: + +=== "Template" + ```erb + <%= 123.45.round.to_s %> + ``` +=== "Output" + ``` + 123 + ``` + +### Strings + +Using strings in templates can be used in two ways: with the `<%=` (print) operator, which outputs the string, escaping any dangerous HTML characters, e.g. `<` becomes `<`, or using `<%-` which performs no conversions and prints the string as-is. + +#### String security + +Escaping HTML characters is a good idea in cases where your users are the ones supplying the value of the string. This prevents script injection attacks, e.g. users placing malicious code on your website. + +Unless you're sure about the provinance of a string, use `<%=` to output it in templates. + +### Boolean + +Boolean variables can either be `true` or `false`. They map directly to Rust's `bool` data type. + +### Lists + +Lists are arrays of other template variables, including other lists, strings, numbers, and hashes. In templates, lists can be defined by using square brackets, and iterated on using [for loops](../for-loops), for example: + +=== "Template" + ```erb + <% for item in [1, 2, "three"] %> + <%= item %> + <% end %> + ``` +=== "Output" + ``` + 1 + 2 + three + ``` + +Rwf lists are flexible and can contain multiple data types. This separates them from Rust's `Vec` and slices which can only hold one kind of data. + +You can also access a specific element in a list by indexing into it with the dot(`.`) notation: + +```erb +<%= list.1 %> +``` + +Lists are 0-indexed, so the above example accesses the second element in the list. + +### Hashes + +Hashes, also known as dicts or hash tables, are a key/value storage data type. Unlike a list, it contains a mapping between a value (the key), and a value. Values can be accessed by knowing a key, or by iterating through the entire hash with a [for loop](../for-loops): + +```erb +

<%= user.name %>

+

<%= user.email %>

+``` + +Rwf hashes use the dot (`.`) notation to access values in a hash. In this example, the `user` is a hash, and `name` and `email` are keys. + +## Truthy vs. falsy + +Variables are often used in [if statements](../if-statements) to decide whether to execute some code or not. To make the template language less verbose, variables can be evaluted for truthiness without calling explicit functions depending on their data type. + +The following variables and data types evaluate to false: + +| Data type | Value | +|-----------|----------| +| Integer | `0` | +| Float | `0.0` | +| Boolean | `false` | +| String | `""` (empty) | +| List | `[]` (emtpy) | +| Hash | `{}` (empty) | + +All other variables evaluate to true. + +## Learn more + +- [If statements](../if-statements) +- [For loops](../for-loops) +- [Functions](../functions) diff --git a/examples/dynamic-templates/src/main.rs b/examples/dynamic-templates/src/main.rs index 7ae2f99c..05d93751 100644 --- a/examples/dynamic-templates/src/main.rs +++ b/examples/dynamic-templates/src/main.rs @@ -50,3 +50,33 @@ async fn main() { .await .expect("error shutting down server"); } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_turtles() -> Result<(), Error> { + let template = Template::from_str( + r#" +
+

<%= username %>

+

<%= bio %>

+
+ "#, + )?; + + let html = template.render([("username", "Alice"), ("bio", "I like turtles")])?; + + assert_eq!( + html, + r#" +
+

Alice

+

I like turtles

+
+ "# + ); + Ok(()) + } +}