Add documentation

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-03-16 09:41:16 +01:00
parent ea75da491d
commit 4c8938e4ff
10 changed files with 160 additions and 9 deletions

View file

@ -63,6 +63,7 @@ pub enum Instruction {
slot: VariableSlot,
},
PushScope {
#[expect(unused)]
inherit_parent: bool,
},
Abort,
@ -93,6 +94,7 @@ pub enum Instruction {
value_slot: VariableSlot,
},
LoadLiteralToSlot {
#[allow(unused)]
source: TemplateToken,
value: NomoValue,
slot: VariableSlot,

View file

@ -7,42 +7,55 @@ use thiserror::Error;
use crate::NomoValueError;
use crate::value::NomoValue;
/// Possible errors while executing a function
#[derive(Debug, Error, Display)]
pub enum NomoFunctionError {
/// Received {received} arguments, but this function only takes {expected}
#[expect(missing_docs)]
WrongArgumentCount { received: usize, expected: usize },
/// The argument at this position is of the wrong type
#[expect(missing_docs)]
InvalidArgumentType { index: usize },
/// A user-provided error
#[error(transparent)]
CustomError {
#[expect(missing_docs)]
custom: Box<dyn std::error::Error + Send + Sync>,
},
}
/// A function that can be used inside a template
pub trait NomoFunction<T>: 'static + Send + Sync {
/// Call the function with the given arguments
fn call(&self, args: Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError>;
}
#[cfg(feature = "unstable-pub")]
#[derive(Default)]
pub struct FunctionMap {
funcs: HashMap<String, ErasedNomoFunction>,
}
#[cfg(not(feature = "unstable-pub"))]
#[derive(Default)]
pub(crate) struct FunctionMap {
funcs: HashMap<String, ErasedNomoFunction>,
}
impl FunctionMap {
pub fn register<NF: NomoFunction<T>, T>(&mut self, name: impl Into<String>, func: NF) {
self.funcs
.insert(name.into(), ErasedNomoFunction::erase(func));
}
pub fn get(&self, name: impl AsRef<str>) -> Option<&ErasedNomoFunction> {
pub(crate) fn get(&self, name: impl AsRef<str>) -> Option<&ErasedNomoFunction> {
self.funcs.get(name.as_ref())
}
}
pub struct ErasedNomoFunction {
pub(crate) struct ErasedNomoFunction {
func: Box<dyn Any + Send + Sync>,
call_fn: fn(&dyn Any, Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError>,
}

View file

@ -8,6 +8,7 @@ use winnow::stream::Offset;
use winnow::stream::Stream;
use winnow::stream::StreamIsPartial;
/// The input for templates in [nomo](crate)
#[derive(Clone, PartialEq, Eq)]
pub struct NomoInput {
backing: Arc<str>,
@ -15,19 +16,26 @@ pub struct NomoInput {
}
impl NomoInput {
/// Manually create an input
///
/// While it is not unsafe to pass in mismatched informations, the outcome will most likely not
/// work and possibly even panic later
pub fn from_parts(backing: Arc<str>, range: Range<usize>) -> NomoInput {
NomoInput { backing, range }
}
/// Turn the input into its parts
pub fn into_parts(self) -> (Arc<str>, Range<usize>) {
(self.backing, self.range)
}
/// Get the range this input covers
pub fn get_range(&self) -> Range<usize> {
self.range.clone()
}
}
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct NomoInputCheckpoint {
range: Range<usize>,
@ -78,6 +86,7 @@ impl Deref for NomoInput {
}
impl NomoInput {
/// Get the input as a [`str`]
pub fn as_str(&self) -> &str {
self.deref()
}
@ -101,6 +110,8 @@ impl Offset for NomoInput {
}
}
#[doc(hidden)]
pub struct NomoInputIter {
idx: usize,
input: NomoInput,

View file

@ -1,3 +1,33 @@
//! # Nomo, the templating library
//!
//! To get started, add the crate to your project:
//!
//! ```bash
//! cargo add nomo
//! ```
//!
//! Then, load some templates:
//!
//! ```rust
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut templates = nomo::Nomo::new();
//! templates.add_template("index.html", "<!DOCTYPE html><h1>Hello {{= name }}!</h1>");
//!
//! let mut context = nomo::Context::new();
//! context.insert("name", "World");
//! let result = templates.render("index.html", &context)?;
//!
//! assert_eq!(result, "<!DOCTYPE html><h1>Hello World!</h1>");
//! # Ok(()) }
//! ```
//!
//! The crate has the following feature flags:
#![cfg_attr(
feature = "document-features",
cfg_attr(doc, doc = ::document_features::document_features!())
)]
//!
use std::collections::HashMap;
use displaydoc::Display;
@ -5,34 +35,65 @@ use thiserror::Error;
use crate::compiler::VMInstructions;
use crate::functions::FunctionMap;
use crate::functions::NomoFunction;
use crate::input::NomoInput;
use crate::value::NomoValue;
use crate::value::NomoValueError;
pub mod compiler;
pub mod eval;
macro_rules! unstable_pub {
($(#[$m:meta])* mod $name:ident) => {
$(#[$m])*
#[cfg(feature = "unstable-pub")]
pub mod $name;
#[cfg(not(feature = "unstable-pub"))]
mod $name;
};
}
unstable_pub!(
/// The compiler internals
mod compiler
);
unstable_pub!(
/// Evaluation internals
mod eval
);
unstable_pub!(
/// Lexer internals
mod lexer
);
unstable_pub!(
/// Parser internals
mod parser
);
/// Nomo Functions
pub mod functions;
/// Input for nomo
pub mod input;
pub mod lexer;
pub mod parser;
/// Values used in Nomo
pub mod value;
/// Errors related to parsing and evaluating templates
#[derive(Debug, Error, Display)]
pub enum NomoError {
/// Could not parse the given template
ParseError {
#[from]
#[expect(missing_docs)]
source: lexer::ParseFailure,
},
/// Invalid Template
AstError {
#[from]
#[expect(missing_docs)]
source: parser::AstFailure,
},
/// An error occurred while evaluating
EvaluationError {
#[from]
#[expect(missing_docs)]
source: eval::EvaluationError,
},
@ -40,6 +101,7 @@ pub enum NomoError {
UnknownTemplate(String),
}
/// The main struct and entry point for the [`nomo`](crate)
pub struct Nomo {
templates: HashMap<String, Template>,
function_map: FunctionMap,
@ -52,6 +114,7 @@ impl Default for Nomo {
}
impl Nomo {
/// Create a new Nomo Instance
pub fn new() -> Nomo {
Nomo {
templates: HashMap::new(),
@ -59,6 +122,7 @@ impl Nomo {
}
}
/// Add a new template
pub fn add_template(
&mut self,
name: impl Into<String>,
@ -76,6 +140,17 @@ impl Nomo {
Ok(())
}
/// Register a function to make it available when rendering
pub fn register_function<A>(&mut self, name: impl Into<String>, f: impl NomoFunction<A>) {
self.function_map.register(name, f);
}
/// List of currently available templates
pub fn templates(&self) -> Vec<String> {
self.templates.keys().cloned().collect()
}
/// Render a specific template
pub fn render(&self, name: &str, ctx: &Context) -> Result<String, NomoError> {
let template = self
.templates
@ -92,6 +167,7 @@ struct Template {
instructions: VMInstructions,
}
/// The context for a given render call
pub struct Context {
values: HashMap<String, NomoValue>,
}
@ -103,12 +179,16 @@ impl Default for Context {
}
impl Context {
/// Create new Context
pub fn new() -> Context {
Context {
values: HashMap::new(),
}
}
/// Add a value into the map
///
/// If you enable the `serde_json` feature, you can insert [`serde_json::Value`]s
pub fn try_insert(
&mut self,
key: impl Into<String>,
@ -119,17 +199,19 @@ impl Context {
Ok(())
}
/// Add a value into the map, panic if you can't
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<NomoValue>) {
self.values.insert(key.into(), value.into());
}
/// Access the values inside this context
pub fn values(&self) -> &HashMap<String, NomoValue> {
&self.values
}
}
#[derive(Debug, Clone)]
pub struct SourceSpan {
struct SourceSpan {
pub range: std::ops::Range<usize>,
}

View file

@ -261,6 +261,7 @@ pub enum TemplateAstExpr<'input> {
ElseConditional {
expression: Option<Box<TemplateAstExpr<'input>>>,
},
#[expect(unused)]
Invalid(&'input [TemplateToken]),
MathOperation {
op: TokenOperator,

View file

@ -1,11 +1,12 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
#[cfg(feature = "serde_json")]
use displaydoc::Display;
use thiserror::Error;
/// Values to be used inside templates
#[derive(Clone)]
#[expect(missing_docs)]
pub enum NomoValue {
String {
value: Cow<'static, str>,
@ -35,6 +36,7 @@ pub enum NomoValue {
}
impl NomoValue {
/// Return a str if there is one inside
pub fn as_str(&self) -> Option<&str> {
if let Self::String { value } = self {
Some(value)
@ -43,6 +45,7 @@ impl NomoValue {
}
}
/// Return an arry if there is one inside
pub fn as_array(&self) -> Option<&[NomoValue]> {
if let Self::Array { value } = self {
Some(value)
@ -51,6 +54,7 @@ impl NomoValue {
}
}
/// Return a bool if there is one inside
pub fn as_bool(&self) -> Option<bool> {
if let Self::Bool { value } = self {
Some(*value)
@ -61,6 +65,7 @@ impl NomoValue {
}
}
/// Return an object if there is one inside
pub fn as_object(&self) -> Option<&BTreeMap<String, NomoValue>> {
if let Self::Object { value } = self {
Some(value)
@ -69,6 +74,7 @@ impl NomoValue {
}
}
/// Return an integer if there is one inside
pub fn as_integer(&self) -> Option<u64> {
if let Self::Integer { value } = self {
Some(*value)
@ -77,6 +83,7 @@ impl NomoValue {
}
}
/// Return a float if there is one inside
pub fn as_float(&self) -> Option<f64> {
if let Self::Float { value } = self {
Some(*value)
@ -85,6 +92,7 @@ impl NomoValue {
}
}
/// Return the iterator if there is one inside
pub fn as_iterator(&self) -> Option<&dyn CloneIterator<Item = NomoValue>> {
if let Self::Iterator { value } = self {
Some(value)
@ -93,6 +101,7 @@ impl NomoValue {
}
}
/// Return the iterator mutably if there is one inside
pub fn as_iterator_mut(&mut self) -> Option<&mut dyn CloneIterator<Item = NomoValue>> {
if let Self::Iterator { value } = self {
Some(value)
@ -249,7 +258,9 @@ impl NomoValue {
}
}
/// Marker trait for iterators that can be cloned
pub trait CloneIterator: Iterator<Item = NomoValue> {
/// Create a new instance of the iterator inside a [Box]
fn clone_box(&self) -> Box<dyn CloneIterator<Item = NomoValue>>;
}