From a87be75b0dd0f1e68737badcd4b27868ec5d0094 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Tue, 19 Nov 2024 10:30:11 -0800 Subject: [PATCH] More docs --- rwf/src/job/clock.rs | 2 +- rwf/src/view/cache.rs | 19 ++++++++++- rwf/src/view/mod.rs | 20 ++++++++++++ rwf/src/view/prelude.rs | 1 + rwf/src/view/template/context.rs | 16 +++++++++ rwf/src/view/template/error.rs | 4 +++ rwf/src/view/template/language/expression.rs | 5 +++ rwf/src/view/template/language/mod.rs | 3 ++ rwf/src/view/template/language/op.rs | 9 ++++++ rwf/src/view/template/language/program.rs | 7 ++++ rwf/src/view/template/language/statement.rs | 5 +++ rwf/src/view/template/language/term.rs | 8 +++++ rwf/src/view/template/lexer/mod.rs | 10 ++++-- rwf/src/view/template/lexer/token.rs | 1 + rwf/src/view/template/mod.rs | 34 ++++++++++++++++++-- 15 files changed, 138 insertions(+), 6 deletions(-) diff --git a/rwf/src/job/clock.rs b/rwf/src/job/clock.rs index de9f9d33..6274fba0 100644 --- a/rwf/src/job/clock.rs +++ b/rwf/src/job/clock.rs @@ -13,7 +13,7 @@ use std::time::Instant; use tokio::time::{sleep, Duration}; use tracing::{error, info}; -static LOCK: i64 = 4334345490663; +static LOCK: i64 = 4_334_345_490_663; /// A job that runs on a schedule. pub struct ScheduledJob { diff --git a/rwf/src/view/cache.rs b/rwf/src/view/cache.rs index ca2aac61..b550c1fc 100644 --- a/rwf/src/view/cache.rs +++ b/rwf/src/view/cache.rs @@ -1,3 +1,11 @@ +//! Global template cache. +//! +//! Using the cache ensures that templates are only compiled once, increasing their +//! execution speed considerably. +//! The template cache is enabled by default in production (`release`), and disabled +//! in development (`debug`). +//! +//! [`Template::load`] uses the template cache automatically. use super::{template::Error, Template}; use crate::config::get_config; use std::collections::HashMap; @@ -5,22 +13,30 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use once_cell::sync::Lazy; -// use tokio::sync::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard}; static TEMPLATES: Lazy> = Lazy::new(|| Mutex::new(Templates::new())); +/// Templates cache. pub struct Templates { templates: HashMap>, } impl Templates { + /// Create new empty template cache. pub fn new() -> Self { Self { templates: HashMap::new(), } } + /// Retrieve a template from the cache. If the template doesn't exist, it will be fetched + /// from disk and compiled. + /// + /// While this has to be done while holding the global template lock, this operation will be + /// fast once most templates are cached. + /// Holding the global lock while reading the template from disk + /// prevents the thundering herd problem. pub fn get(&mut self, path: impl AsRef + Copy) -> Result, Error> { let cache_templates = get_config().general.cache_templates; @@ -38,6 +54,7 @@ impl Templates { } } + /// Obtain a lock to the global template cache. pub fn cache() -> MutexGuard<'static, Templates> { TEMPLATES.lock() } diff --git a/rwf/src/view/mod.rs b/rwf/src/view/mod.rs index fb4f5f34..393c807d 100644 --- a/rwf/src/view/mod.rs +++ b/rwf/src/view/mod.rs @@ -1,5 +1,25 @@ //! Dynamic templates and views, the **V** in MVC. //! +//! Rwf templates are inspired from multiple other languages like ERB and Jinja2. The templates are dynamic, +//! meaning they are interpreted at runtime. This allows for smoother local development experience, while the +//! template cache in production makes this performant as well. +//! +//! # Example +//! +//! ``` +//! # use rwf::view::*; +//! let template = Template::from_str("

<%= title %>

").unwrap(); +//! let mut context = Context::new(); +//! +//! context.set("title", "Hello from Rwf!").unwrap(); +//! +//! let rendered = template.render(&context).unwrap(); +//! +//! assert_eq!(rendered, "

Hello from Rwf!

"); +//! ``` +//! +//! # User guides +//! //! See [documentation](https://levkk.github.io/rwf/views/) on how to use templates. pub mod cache; pub mod prelude; diff --git a/rwf/src/view/prelude.rs b/rwf/src/view/prelude.rs index 8e890065..b98a6ecd 100644 --- a/rwf/src/view/prelude.rs +++ b/rwf/src/view/prelude.rs @@ -1,3 +1,4 @@ +//! Types useful when working with templates. pub use super::template::{ToTemplateValue, Value}; pub use super::Template; pub use super::Templates; diff --git a/rwf/src/view/template/context.rs b/rwf/src/view/template/context.rs index 0372da8a..ef055c5c 100644 --- a/rwf/src/view/template/context.rs +++ b/rwf/src/view/template/context.rs @@ -1,3 +1,14 @@ +//! Template context. Contains variable assignments. +//! +//! Most Rust data types can be converted to template data types +//! automatically. +//! +//! Contexts can be created easily using the `context!` macro, for example: +//! +//! ```ignore +//! let ctx = context!("var" => 1, "title" => "hello world!"); +//! ``` +//! use crate::view::template::{Error, ToTemplateValue, Value}; use parking_lot::RwLock; use std::collections::HashMap; @@ -9,26 +20,31 @@ use once_cell::sync::Lazy; static DEFAULTS: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(Context::default()))); +/// Template context. #[derive(Debug, Default, Clone)] pub struct Context { values: HashMap, } impl Context { + /// Create new empty context. pub fn new() -> Self { DEFAULTS.read().clone() } + /// Get a variable value. pub fn get(&self, key: &str) -> Option { self.values.get(key).cloned() } + /// Set a variable value. Converts from Rust types to template types automatically. pub fn set(&mut self, key: &str, value: impl ToTemplateValue) -> Result<&mut Self, Error> { self.values .insert(key.to_string(), value.to_template_value()?); Ok(self) } + /// Set global variable defaults. pub fn defaults(context: Self) { (*DEFAULTS.write()) = context; } diff --git a/rwf/src/view/template/error.rs b/rwf/src/view/template/error.rs index aed4081c..3821bc94 100644 --- a/rwf/src/view/template/error.rs +++ b/rwf/src/view/template/error.rs @@ -1,8 +1,11 @@ +//! Errors returned by the template engine. + use super::{Token, TokenWithContext}; use thiserror::Error; use std::path::{Path, PathBuf}; +/// Template error. #[derive(Error, Debug)] pub enum Error { #[error("syntax error")] @@ -40,6 +43,7 @@ pub enum Error { } impl Error { + /// Render the template error in a human-readable way. // TODO: this function is iffy. Needs more work. pub fn pretty(self, source: &str, path: Option + Copy>) -> Self { let token = match self { diff --git a/rwf/src/view/template/language/expression.rs b/rwf/src/view/template/language/expression.rs index 0e4114d9..2602cc50 100644 --- a/rwf/src/view/template/language/expression.rs +++ b/rwf/src/view/template/language/expression.rs @@ -1,3 +1,4 @@ +//! Language expression. When evaluated, produces a single value. use super::{ super::lexer::{Token, TokenWithContext, Tokenize, Value}, super::Context, @@ -430,8 +431,12 @@ impl Expression { } } +/// Evalute an expression given a context. pub trait Evaluate { + /// Evaluate expression given the context. fn evaluate(&self, context: &Context) -> Result; + + /// Evaluate expression with an empty context. fn evaluate_default(&self) -> Result { self.evaluate(&Context::default()) } diff --git a/rwf/src/view/template/language/mod.rs b/rwf/src/view/template/language/mod.rs index 0e1e4e8b..f097d089 100644 --- a/rwf/src/view/template/language/mod.rs +++ b/rwf/src/view/template/language/mod.rs @@ -1,3 +1,6 @@ +//! Implementation of the template language. +//! +//! Includes the parser and runtime. pub mod expression; pub mod op; pub mod program; diff --git a/rwf/src/view/template/language/op.rs b/rwf/src/view/template/language/op.rs index 3a8a1f85..be0eb667 100644 --- a/rwf/src/view/template/language/op.rs +++ b/rwf/src/view/template/language/op.rs @@ -1,8 +1,10 @@ +//! Mathematical operation between data types. use super::super::lexer::{Token, Value}; use super::super::Error; use std::cmp::Ordering; +/// List of supported operations, e.g. addition, equality, etc. #[derive(Clone, Copy, PartialEq, Debug)] pub enum Op { Not, @@ -30,10 +32,13 @@ impl PartialOrd for Op { } impl Op { + /// Convert a language token to an op. If the token + /// isn't an op, `None` is returned. pub fn from_token(token: Token) -> Option { Option::::from(token) } + /// Is this a binary operator, i.e. an operation between two terms? pub fn binary(&self) -> bool { match self { Op::Not => false, @@ -41,6 +46,7 @@ impl Op { } } + /// Evaluate the operation on a value. pub fn evaluate_unary(&self, value: &Value) -> Result { match self { Op::Not => Ok(Value::Boolean(!value.truthy())), @@ -54,6 +60,7 @@ impl Op { } } + /// Combinate two terms into one using the operation. pub fn evaluate_binary(&self, left: &Value, right: &Value) -> Result { match self { Op::Equals => Ok(Value::Boolean(left == right)), @@ -72,6 +79,8 @@ impl Op { } } + /// Calculate operator precendence, i.e. in an expression with multiple + /// operations, determine their order of execution. // Source: pub fn precendence(&self) -> u8 { match self { diff --git a/rwf/src/view/template/language/program.rs b/rwf/src/view/template/language/program.rs index 0d426d94..3f0732f4 100644 --- a/rwf/src/view/template/language/program.rs +++ b/rwf/src/view/template/language/program.rs @@ -1,12 +1,17 @@ +//! Executable template. +//! +//! A program is a list of statements. use super::super::{Context, Error, TokenWithContext, Tokenize}; use super::Statement; +/// Executable program. #[derive(Debug, Clone)] pub struct Program { statements: Vec, } impl Program { + /// Evaluate the program given the context. The context contains variable definitions. pub fn evaluate(&self, context: &Context) -> Result { let mut result = String::new(); for statement in &self.statements { @@ -16,6 +21,7 @@ impl Program { Ok(result) } + /// Parse the program from a list of tokens. pub fn parse(tokens: Vec) -> Result { let mut iter = tokens.into_iter().peekable(); let mut statements = vec![]; @@ -28,6 +34,7 @@ impl Program { Ok(Program { statements }) } + /// Compile the program from source. pub fn from_str(source: &str) -> Result { let tokens = source.tokenize()?; Program::parse(tokens) diff --git a/rwf/src/view/template/language/statement.rs b/rwf/src/view/template/language/statement.rs index c969b42a..9ed72127 100644 --- a/rwf/src/view/template/language/statement.rs +++ b/rwf/src/view/template/language/statement.rs @@ -1,3 +1,4 @@ +//! Language statement, code which executes arbitrary instructions, like for loops or print to screen. use super::{ super::Template, super::{Context, Error, Token, TokenWithContext, Tokenize, Value}, @@ -25,6 +26,7 @@ macro_rules! block_end { }; } +/// Program statement. #[derive(Debug, Clone)] pub enum Statement { // e.g. `<%= variable %>` @@ -56,11 +58,13 @@ pub enum Statement { } impl Statement { + /// Compile statement from text. pub fn from_str(string: &str) -> Result { let tokens = string.tokenize()?; Statement::parse(&mut tokens.into_iter().peekable()) } + /// Evaluate a statement given the context. pub fn evaluate(&self, context: &Context) -> Result { match self { Statement::Render(path) => { @@ -151,6 +155,7 @@ impl Statement { } } + /// Parse the statement from a stream of tokens. This consumes tokens from the stream. pub fn parse( iter: &mut Peekable>, ) -> Result { diff --git a/rwf/src/view/template/language/term.rs b/rwf/src/view/template/language/term.rs index e36388dd..ea811bba 100644 --- a/rwf/src/view/template/language/term.rs +++ b/rwf/src/view/template/language/term.rs @@ -1,9 +1,11 @@ +//! Expression term, a single entity in an expression. use super::super::{ lexer::{Token, Value}, Context, }; use crate::view::template::error::Error; +/// Expression term. #[derive(Debug, Clone, PartialEq)] pub enum Term { Constant(Value), @@ -12,18 +14,22 @@ pub enum Term { } impl Term { + /// Convert a token into a term. If the token isn't a term, return `None`. pub fn from_token(token: Token) -> Option { Option::::from(token) } + /// Create a constant term from a value. Constant terms are evaluated to the value. pub fn constant(value: Value) -> Self { Term::Constant(value) } + /// Create a variable term. The term requires a context to be evaluated. pub fn variable(name: String) -> Self { Term::Variable(name) } + /// Evalutate the term given the context. pub fn evaluate(&self, context: &Context) -> Result { match self { Term::Constant(value) => Ok(value.clone()), @@ -34,6 +40,8 @@ impl Term { } } + /// Get the term name, i.e. what it's called in the code, variable or function name. + /// Constant terms don't have names. pub fn name(&self) -> &str { match self { Term::Variable(name) => name, diff --git a/rwf/src/view/template/lexer/mod.rs b/rwf/src/view/template/lexer/mod.rs index 059f199c..868ef375 100644 --- a/rwf/src/view/template/lexer/mod.rs +++ b/rwf/src/view/template/lexer/mod.rs @@ -1,3 +1,4 @@ +//! Read text and transform it into a list of known language tokens. pub mod token; pub mod value; @@ -6,6 +7,7 @@ pub use value::{ToTemplateValue, Value}; use super::Error; +/// Token with source code location context. #[derive(Debug, Clone, PartialEq)] pub struct TokenWithContext { token: Token, @@ -24,6 +26,7 @@ impl std::fmt::Display for TokenWithContext { } impl TokenWithContext { + /// Create new token with location context. pub fn new(token: Token, line: usize, column: usize) -> Self { Self { token, @@ -32,14 +35,17 @@ impl TokenWithContext { } } + /// Get line. pub fn line(&self) -> usize { self.line } + /// Get column. pub fn column(&self) -> usize { self.column } + /// Get the token. pub fn token(&self) -> Token { self.token.clone() } @@ -468,9 +474,9 @@ impl<'a> Lexer<'a> { } } -// Easily tokenize strings. +/// Easily tokenize strings. pub trait Tokenize { - // Parse a string and convert it to a list of tokens. + /// Parse a string and convert it to a list of tokens. fn tokenize(&self) -> Result, Error>; } diff --git a/rwf/src/view/template/lexer/token.rs b/rwf/src/view/template/lexer/token.rs index d318ac65..fddc5226 100644 --- a/rwf/src/view/template/lexer/token.rs +++ b/rwf/src/view/template/lexer/token.rs @@ -1,3 +1,4 @@ +//! Known language tokens. use super::Value; /// A template language token, e.g. `if` or `for`. diff --git a/rwf/src/view/template/mod.rs b/rwf/src/view/template/mod.rs index 91a7d452..a85c5d9d 100644 --- a/rwf/src/view/template/mod.rs +++ b/rwf/src/view/template/mod.rs @@ -1,3 +1,11 @@ +//! Implementation of the Rwf templating language. +//! +//! Templates are effectively +//! a translation of predefined functions and operations into equivalent Rust code. +//! Coupled with Rust memory management, this makes this template engine pretty fast. +//! +//! The interpreter has a lexer, parser, and an executor. For a language usage examples, +//! see [documentation](https://levkk.github.io/rwf/). pub mod context; pub mod error; pub mod language; @@ -18,7 +26,7 @@ use std::sync::Arc; /// Rwf template. /// -/// Contains the AST for the template. +/// Contains the executable AST. #[allow(dead_code)] #[derive(Clone, Debug)] pub struct Template { @@ -41,6 +49,16 @@ impl Template { } /// Read and compile a template from a string. + /// + /// # Example + /// + /// ``` + /// # use rwf::view::template::*; + /// let template = Template::from_str("<%= 1 + 5 %>").unwrap(); + /// let result = template.render_default().unwrap(); + /// + /// assert_eq!(result, "6"); + /// ``` pub fn from_str(template: &str) -> Result { Ok(Template { program: Program::from_str(template)?, @@ -48,7 +66,8 @@ impl Template { }) } - /// Given a context, execute the template, producing a string. + /// Execute a template, provided with the context, and produce a rendering. The rendering + /// is a string. pub fn render(&self, context: impl TryInto) -> Result { let context: Context = context.try_into()?; @@ -64,10 +83,14 @@ impl Template { } } + /// [`Self::render`] with an empty context. Used for templates that don't use any variables, or only + /// have globally defined variables. pub fn render_default(&self) -> Result { self.render(&Context::default()) } + /// Fetch the template from cache. If the template is not in cache, load it + /// from disk and store it in the cache for future use. pub fn cached(path: impl AsRef + Copy) -> Result, Error> { match Templates::cache().get(path) { Ok(template) => Ok(template), @@ -75,6 +98,7 @@ impl Template { } } + /// Load the template from disk and store it in the cache for future use. Alias for [`Self::cached`]. pub fn load(path: impl AsRef + Copy) -> Result, Error> { Self::cached(path) } @@ -85,6 +109,12 @@ impl Template { Context::defaults(context); } + /// Render a static template (without variables). If the template doesn't exist + /// and combined with the `?` operator, + /// automatically return `500 - Internal Server Error`. + /// + /// Useful inside controllers. + /// pub fn cached_static(path: impl AsRef + Copy) -> Result { match Self::cached(path) { Ok(template) => Ok(template.try_into()?),