Skip to content

Commit

Permalink
More docs
Browse files Browse the repository at this point in the history
  • Loading branch information
levkk committed Nov 19, 2024
1 parent d9511b8 commit a87be75
Show file tree
Hide file tree
Showing 15 changed files with 138 additions and 6 deletions.
2 changes: 1 addition & 1 deletion rwf/src/job/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 18 additions & 1 deletion rwf/src/view/cache.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
//! 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;
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<Mutex<Templates>> = Lazy::new(|| Mutex::new(Templates::new()));

/// Templates cache.
pub struct Templates {
templates: HashMap<PathBuf, Arc<Template>>,
}

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<Path> + Copy) -> Result<Arc<Template>, Error> {
let cache_templates = get_config().general.cache_templates;

Expand All @@ -38,6 +54,7 @@ impl Templates {
}
}

/// Obtain a lock to the global template cache.
pub fn cache() -> MutexGuard<'static, Templates> {
TEMPLATES.lock()
}
Expand Down
20 changes: 20 additions & 0 deletions rwf/src/view/mod.rs
Original file line number Diff line number Diff line change
@@ -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("<h1><%= title %></h1>").unwrap();
//! let mut context = Context::new();
//!
//! context.set("title", "Hello from Rwf!").unwrap();
//!
//! let rendered = template.render(&context).unwrap();
//!
//! assert_eq!(rendered, "<h1>Hello from Rwf!</h1>");
//! ```
//!
//! # User guides
//!
//! See [documentation](https://levkk.github.io/rwf/views/) on how to use templates.
pub mod cache;
pub mod prelude;
Expand Down
1 change: 1 addition & 0 deletions rwf/src/view/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Types useful when working with templates.
pub use super::template::{ToTemplateValue, Value};
pub use super::Template;
pub use super::Templates;
16 changes: 16 additions & 0 deletions rwf/src/view/template/context.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,26 +20,31 @@ use once_cell::sync::Lazy;
static DEFAULTS: Lazy<Arc<RwLock<Context>>> =
Lazy::new(|| Arc::new(RwLock::new(Context::default())));

/// Template context.
#[derive(Debug, Default, Clone)]
pub struct Context {
values: HashMap<String, Value>,
}

impl Context {
/// Create new empty context.
pub fn new() -> Self {
DEFAULTS.read().clone()
}

/// Get a variable value.
pub fn get(&self, key: &str) -> Option<Value> {
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;
}
Expand Down
4 changes: 4 additions & 0 deletions rwf/src/view/template/error.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down Expand Up @@ -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<impl AsRef<Path> + Copy>) -> Self {
let token = match self {
Expand Down
5 changes: 5 additions & 0 deletions rwf/src/view/template/language/expression.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Language expression. When evaluated, produces a single value.
use super::{
super::lexer::{Token, TokenWithContext, Tokenize, Value},
super::Context,
Expand Down Expand Up @@ -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<Value, Error>;

/// Evaluate expression with an empty context.
fn evaluate_default(&self) -> Result<Value, Error> {
self.evaluate(&Context::default())
}
Expand Down
3 changes: 3 additions & 0 deletions rwf/src/view/template/language/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! Implementation of the template language.
//!
//! Includes the parser and runtime.
pub mod expression;
pub mod op;
pub mod program;
Expand Down
9 changes: 9 additions & 0 deletions rwf/src/view/template/language/op.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -30,17 +32,21 @@ 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<Self> {
Option::<Self>::from(token)
}

/// Is this a binary operator, i.e. an operation between two terms?
pub fn binary(&self) -> bool {
match self {
Op::Not => false,
_ => true,
}
}

/// Evaluate the operation on a value.
pub fn evaluate_unary(&self, value: &Value) -> Result<Value, Error> {
match self {
Op::Not => Ok(Value::Boolean(!value.truthy())),
Expand All @@ -54,6 +60,7 @@ impl Op {
}
}

/// Combinate two terms into one using the operation.
pub fn evaluate_binary(&self, left: &Value, right: &Value) -> Result<Value, Error> {
match self {
Op::Equals => Ok(Value::Boolean(left == right)),
Expand All @@ -72,6 +79,8 @@ impl Op {
}
}

/// Calculate operator precendence, i.e. in an expression with multiple
/// operations, determine their order of execution.
// Source: <https://en.cppreference.com/w/c/language/operator_precedence>
pub fn precendence(&self) -> u8 {
match self {
Expand Down
7 changes: 7 additions & 0 deletions rwf/src/view/template/language/program.rs
Original file line number Diff line number Diff line change
@@ -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<Statement>,
}

impl Program {
/// Evaluate the program given the context. The context contains variable definitions.
pub fn evaluate(&self, context: &Context) -> Result<String, Error> {
let mut result = String::new();
for statement in &self.statements {
Expand All @@ -16,6 +21,7 @@ impl Program {
Ok(result)
}

/// Parse the program from a list of tokens.
pub fn parse(tokens: Vec<TokenWithContext>) -> Result<Self, Error> {
let mut iter = tokens.into_iter().peekable();
let mut statements = vec![];
Expand All @@ -28,6 +34,7 @@ impl Program {
Ok(Program { statements })
}

/// Compile the program from source.
pub fn from_str(source: &str) -> Result<Self, Error> {
let tokens = source.tokenize()?;
Program::parse(tokens)
Expand Down
5 changes: 5 additions & 0 deletions rwf/src/view/template/language/statement.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -25,6 +26,7 @@ macro_rules! block_end {
};
}

/// Program statement.
#[derive(Debug, Clone)]
pub enum Statement {
// e.g. `<%= variable %>`
Expand Down Expand Up @@ -56,11 +58,13 @@ pub enum Statement {
}

impl Statement {
/// Compile statement from text.
pub fn from_str(string: &str) -> Result<Self, Error> {
let tokens = string.tokenize()?;
Statement::parse(&mut tokens.into_iter().peekable())
}

/// Evaluate a statement given the context.
pub fn evaluate(&self, context: &Context) -> Result<String, Error> {
match self {
Statement::Render(path) => {
Expand Down Expand Up @@ -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<impl Iterator<Item = TokenWithContext>>,
) -> Result<Statement, Error> {
Expand Down
8 changes: 8 additions & 0 deletions rwf/src/view/template/language/term.rs
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -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<Self> {
Option::<Self>::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<Value, Error> {
match self {
Term::Constant(value) => Ok(value.clone()),
Expand All @@ -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,
Expand Down
10 changes: 8 additions & 2 deletions rwf/src/view/template/lexer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Read text and transform it into a list of known language tokens.
pub mod token;
pub mod value;

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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()
}
Expand Down Expand Up @@ -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<Vec<TokenWithContext>, Error>;
}

Expand Down
Loading

0 comments on commit a87be75

Please sign in to comment.