From 67a2153ca9a840af94f95835a0a2e819d0c0b34f Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Wed, 29 Apr 2026 12:40:34 +0200 Subject: [PATCH] Add docs --- tera/src/args.rs | 4 +++ tera/src/components.rs | 8 ++++++ tera/src/errors.rs | 17 +++++++++-- tera/src/lib.rs | 3 +- tera/src/tera.rs | 60 +++++++++++++++++++++++++++++++++++++-- tera/src/tests.rs | 1 + tera/src/value/key.rs | 9 ++++++ tera/src/value/mod.rs | 61 ++++++++++++++++++++++++++++++++++++++-- tera/src/value/number.rs | 10 ++++++- tera/src/vm/state.rs | 2 ++ 10 files changed, 167 insertions(+), 8 deletions(-) diff --git a/tera/src/args.rs b/tera/src/args.rs index d5bd5331..76addaf3 100644 --- a/tera/src/args.rs +++ b/tera/src/args.rs @@ -207,16 +207,20 @@ impl<'k, T: ArgFromValue<'k, Output = T>> ArgFromValue<'k> for Vec { } } +/// The keyword arguments of a filter/function #[derive(Debug, Clone, Default)] pub struct Kwargs { values: Arc, } impl Kwargs { + /// Creates a new Kwargs struct from a Map. The Map is Arc<_> since internally + /// that's what we have. pub fn new(map: Arc) -> Self { Self { values: map } } + /// Deserialize the kwargs into something that impl Deserialize pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> TeraResult { T::deserialize(&Value { inner: ValueInner::Map(self.values.clone()), diff --git a/tera/src/components.rs b/tera/src/components.rs index 4af78b33..d0f173ed 100644 --- a/tera/src/components.rs +++ b/tera/src/components.rs @@ -7,16 +7,24 @@ use crate::value::Value; /// The type of component arguments. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ComponentArgType { + #[allow(missing_docs)] String, + #[allow(missing_docs)] Bool, + #[allow(missing_docs)] Integer, + #[allow(missing_docs)] Float, + #[allow(missing_docs)] Number, + #[allow(missing_docs)] Array, + #[allow(missing_docs)] Map, } impl ComponentArgType { + /// Returns the name of this argument type pub fn as_str(&self) -> &'static str { match self { ComponentArgType::String => "string", diff --git a/tera/src/errors.rs b/tera/src/errors.rs index 68feb58e..9e34ef4f 100644 --- a/tera/src/errors.rs +++ b/tera/src/errors.rs @@ -1,8 +1,8 @@ //! The Tera error type, with optional nice terminal error reporting. +use std::error::Error as StdError; use std::fmt::{self}; use crate::reporting::generate_report; -use std::error::Error as StdError; use crate::utils::Span; @@ -13,6 +13,7 @@ pub(crate) struct Note { pub(crate) span: Span, } +/// An error that knows how to present itself nicely, with the right spans/notes etc. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ReportError { pub(crate) message: String, @@ -82,6 +83,8 @@ impl ReportError { } } +/// All the kind of errors Tera can produce. +/// Non-exhaustive so we can add more if needed without a breaking change. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum ErrorKind { @@ -135,11 +138,16 @@ pub enum ErrorKind { ComponentNotFound(String), /// A filter/test main value was not the expected type InvalidArgument { + #[allow(missing_docs)] expected_type: String, + #[allow(missing_docs)] actual_type: String, }, /// A function/test/filter was expecting an argument but it wasn't found - MissingArgument { arg_name: String }, + MissingArgument { + #[allow(missing_docs)] + arg_name: String, + }, /// An IO error occurred Io(std::io::ErrorKind), /// UTF-8 conversion error when converting output to UTF-8 @@ -208,6 +216,7 @@ impl fmt::Display for ErrorKind { } } +/// The Error struct for Tera. #[derive(Debug)] pub struct Error { pub(crate) kind: ErrorKind, @@ -222,10 +231,12 @@ impl fmt::Display for Error { } impl Error { + /// Creates a new error of the given kind. pub fn new(kind: ErrorKind) -> Self { Self { kind, source: None } } + /// Returns the kind of error pub fn kind(&self) -> &ErrorKind { &self.kind } @@ -238,6 +249,7 @@ impl Error { } } + /// Creates generic error with a message and no source. pub fn message(message: impl ToString) -> Self { Self { kind: ErrorKind::Msg(message.to_string()), @@ -351,6 +363,7 @@ impl From for Error { } } +/// A custom Result type for this library pub type TeraResult = Result; #[cfg(test)] diff --git a/tera/src/lib.rs b/tera/src/lib.rs index ffc933bc..ad3d78ee 100644 --- a/tera/src/lib.rs +++ b/tera/src/lib.rs @@ -55,7 +55,7 @@ //! [Jinja2]: http://jinja.pocoo.org/ //! [Django]: https://docs.djangoproject.com/en/3.1/topics/templates/ -//#![deny(missing_docs)] +#![deny(missing_docs)] mod args; mod components; @@ -72,6 +72,7 @@ mod template; mod tera; mod tests; mod utils; +/// The value type used by Tera and supporting types (`Key`, `Map`, `Number`, `ValueKind`). pub mod value; pub(crate) mod vm; diff --git a/tera/src/tera.rs b/tera/src/tera.rs index ef11c628..04f4b666 100644 --- a/tera/src/tera.rs +++ b/tera/src/tera.rs @@ -29,6 +29,41 @@ const ONE_OFF_TEMPLATE_NAME: &str = "__tera_one_off"; /// The escape function type definition pub type EscapeFn = fn(&[u8], &mut dyn Write) -> std::io::Result<()>; +/// Main point of interaction in this library. +/// +/// The [`Tera`] struct is the primary interface for working with the Tera template engine. It contains parsed templates, registered filters (which can filter +/// data), functions, and testers. It also contains some configuration options, such as a list of +/// suffixes for files that have autoescaping turned on. +/// +/// It is responsible for: +/// +/// - Loading and managing templates from files or strings +/// - Parsing templates and checking for syntax errors +/// - Maintaining a cache of compiled templates for efficient rendering +/// - Providing an interface for rendering templates with given contexts +/// - Managing template inheritance and includes +/// - Handling custom filters and functions +/// - Overriding settings, such as autoescape rules +/// +/// # Example +/// +/// Basic usage: +/// +/// ``` +/// use tera::Tera; +/// +/// let mut tera = Tera::default(); +/// tera.load_from_glob("examples/basic/templates/**/*").unwrap(); +/// tera.add_raw_template("hello", "Hello, {{ name }}!").unwrap(); +/// +/// // Prepare the context with some data +/// let mut context = tera::Context::new(); +/// context.insert("name", "World"); +/// +/// // Render the template with the given context +/// let rendered = tera.render("hello", &context).unwrap(); +/// assert_eq!(rendered, "Hello, World!"); +/// ``` #[derive(Clone)] pub struct Tera { /// The glob used to load templates if there was one. @@ -53,10 +88,31 @@ pub struct Tera { } impl Tera { + /// Create a new instance of Tera. Equivalent of `Tera::default()`. pub fn new() -> Self { Self::default() } + /// Loads all the parsed templates found in the `dir` glob. + /// + /// A glob is a pattern for matching multiple file paths, employing special characters such as + /// the single asterisk (`*`) to match any sequence of characters within a single directory + /// level, and the double asterisk (`**`) to match any sequence of characters across multiple + /// directory levels, thereby providing a flexible and concise way to select files based on + /// their names, extensions, or hierarchical relationships. For example, the glob pattern + /// `templates/*.html` will match all files with the `.html` extension located directly inside + /// the `templates` folder, while the glob pattern `templates/**/*.html` will match all files + /// with the `.html` extension directly inside or in a subdirectory of `templates`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use tera::Tera; + /// let mut tera = Tera::default(); + /// tera.load_from_glob("examples/basic/templates/**/*").unwrap(); + /// ``` #[cfg(feature = "glob_fs")] pub fn load_from_glob(&mut self, glob: &str) -> TeraResult<()> { self.glob = Some(glob.to_string()); @@ -182,9 +238,9 @@ impl Tera { /// /// // Override escape function to escape the capital letter A, why not /// tera.set_escape_fn(|input: &[u8], output: &mut dyn Write| { - /// for &byte in input { + /// for &byte in input {< /// match byte { - /// b'A' => output.write_all(b"Ɐ")?, + /// b'A' => output.write_all(b"\xc6\x90")?, /// _ => output.write_all(&[byte])?, /// } /// } diff --git a/tera/src/tests.rs b/tera/src/tests.rs index 591a9560..f9f03491 100644 --- a/tera/src/tests.rs +++ b/tera/src/tests.rs @@ -25,6 +25,7 @@ impl TestResult for bool { /// The test function type definition pub trait Test: Sync + Send + 'static { + /// The test function type definition fn call(&self, value: Arg, kwargs: Kwargs, state: &State) -> Res; } diff --git a/tera/src/value/key.rs b/tera/src/value/key.rs index 6f1e57db..fbcf2a7f 100644 --- a/tera/src/value/key.rs +++ b/tera/src/value/key.rs @@ -10,16 +10,24 @@ use std::sync::Arc; /// The key of anything looking like a hashmap (struct/hashmaps) #[derive(Debug, Clone)] pub enum Key<'a> { + #[allow(missing_docs)] Bool(bool), + #[allow(missing_docs)] U64(u64), + #[allow(missing_docs)] I64(i64), + #[allow(missing_docs)] U128(u128), + #[allow(missing_docs)] I128(i128), + #[allow(missing_docs)] String(Arc), + #[allow(missing_docs)] Str(&'a str), } impl<'a> Key<'a> { + /// Returns the content if the key is a string pub fn as_str(&self) -> Option<&str> { match self { Key::String(s) => Some(s), @@ -28,6 +36,7 @@ impl<'a> Key<'a> { } } + #[allow(missing_docs)] pub fn as_value(&self) -> Value { match self { Key::Bool(b) => Value::from(*b), diff --git a/tera/src/value/mod.rs b/tera/src/value/mod.rs index 9bec1f30..afa0787c 100644 --- a/tera/src/value/mod.rs +++ b/tera/src/value/mod.rs @@ -22,9 +22,11 @@ use crate::errors::{Error, TeraResult}; use crate::value::number::Number; pub use key::Key; +/// The internal HashMap type used by Tera. #[cfg(not(feature = "preserve_order"))] pub type Map = HashMap, Value>; +/// The internal HashMap type used by Tera. #[cfg(feature = "preserve_order")] pub type Map = indexmap::IndexMap, Value>; @@ -61,19 +63,33 @@ pub(crate) enum StringKind { Safe, } +/// The kind of values Tera can handle #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ValueKind { + /// This is mostly used internally to represent a lookup failure and you're unlikely to need to + /// use that except when writing some special filters/functions that lookups data in the state. Undefined, + /// An explicit `None` value: it is there and found, but it is `None`. None, + #[allow(missing_docs)] Bool, + #[allow(missing_docs)] U64, + #[allow(missing_docs)] I64, + #[allow(missing_docs)] U128, + #[allow(missing_docs)] I128, + #[allow(missing_docs)] F64, + #[allow(missing_docs)] String, + #[allow(missing_docs)] Array, + #[allow(missing_docs)] Map, + #[allow(missing_docs)] Bytes, } @@ -179,6 +195,7 @@ pub(crate) enum ValueInner { Bytes(Arc>), } +/// The Value type that Tera uses internally and that will handle ser/de #[derive(Clone)] pub struct Value { pub(crate) inner: ValueInner, @@ -319,18 +336,21 @@ fn resolve_index(item: &Value, len: usize, kind: &str) -> TeraResult Self { Value { inner: ValueInner::None, } } + #[allow(missing_docs)] pub fn undefined() -> Self { Value { inner: ValueInner::Undefined, } } + #[allow(missing_docs)] pub fn kind(&self) -> ValueKind { match &self.inner { ValueInner::Undefined => ValueKind::Undefined, @@ -349,39 +369,51 @@ impl Value { } // Type checks + #[allow(missing_docs)] pub fn is_undefined(&self) -> bool { matches!(self.kind(), ValueKind::Undefined) } + #[allow(missing_docs)] pub fn is_none(&self) -> bool { matches!(self.kind(), ValueKind::None) } + #[allow(missing_docs)] pub fn is_bool(&self) -> bool { matches!(self.kind(), ValueKind::Bool) } + #[allow(missing_docs)] pub fn is_string(&self) -> bool { matches!(self.kind(), ValueKind::String) } + #[allow(missing_docs)] pub fn is_i128(&self) -> bool { matches!(self.kind(), ValueKind::I128) } + #[allow(missing_docs)] pub fn is_u128(&self) -> bool { matches!(self.kind(), ValueKind::U128) } + #[allow(missing_docs)] pub fn is_i64(&self) -> bool { matches!(self.kind(), ValueKind::I64) } + #[allow(missing_docs)] pub fn is_u64(&self) -> bool { matches!(self.kind(), ValueKind::U64) } + #[allow(missing_docs)] pub fn is_f64(&self) -> bool { matches!(self.kind(), ValueKind::F64) } + #[allow(missing_docs)] pub fn is_array(&self) -> bool { matches!(self.kind(), ValueKind::Array) } + #[allow(missing_docs)] pub fn is_map(&self) -> bool { matches!(self.kind(), ValueKind::Map) } + #[allow(missing_docs)] pub fn is_bytes(&self) -> bool { matches!(self.kind(), ValueKind::Bytes) } @@ -452,27 +484,33 @@ impl Value { } } + /// Creates a Value from something that impl Serialize. + /// Panics if serialization fails; see [`Self::try_from_serializable`] for the fallible variant. pub fn from_serializable(value: &T) -> Value { Self::try_from_serializable(value).unwrap() } + /// Fallible way to create a Value from something that impl Serialize pub fn try_from_serializable(value: &T) -> TeraResult { Serialize::serialize(value, ser::ValueSerializer) .map_err(|err| Error::message(err.to_string())) } + /// Creates a normal string that will be escaped pub fn normal_string(val: &str) -> Value { Value { inner: ValueInner::String(SmartString::new(val, StringKind::Normal)), } } + /// Creates a safe string that won't be escaped pub fn safe_string(val: &str) -> Value { Value { inner: ValueInner::String(SmartString::new(val, StringKind::Safe)), } } + /// If the Value is an integer that can fit in a i128, return that otherwise None. pub fn as_i128(&self) -> Option { match &self.inner { ValueInner::U64(v) => Some(*v as i128), @@ -483,6 +521,7 @@ impl Value { } } + /// If the Value is an integer that can fit in a u128, return that otherwise None. pub fn as_u128(&self) -> Option { match &self.inner { ValueInner::U64(v) => Some(*v as u128), @@ -493,7 +532,8 @@ impl Value { } } - pub(crate) fn as_f64(&self) -> Option { + /// If the Value is a f64 return the associated f64, otherwise None. + pub fn as_f64(&self) -> Option { const MAX: u128 = 1u128 << f64::MANTISSA_DIGITS; match &self.inner { ValueInner::F64(v) => Some(*v), @@ -518,6 +558,7 @@ impl Value { } } + /// Returns `true` if the value is an integer or a float pub fn is_number(&self) -> bool { matches!( &self.inner, @@ -529,6 +570,7 @@ impl Value { ) } + /// If the Value is a string return the associated str, otherwise None. pub fn as_str(&self) -> Option<&str> { match &self.inner { ValueInner::String(s) => Some(s.as_str()), @@ -536,6 +578,7 @@ impl Value { } } + /// If the Value is a bool return the associated bool, otherwise None. pub fn as_bool(&self) -> Option { match &self.inner { ValueInner::Bool(b) => Some(*b), @@ -543,6 +586,8 @@ impl Value { } } + /// Returns whether the value is safe (i.e. does not need escaping). Non-safe strings and + /// arrays/maps/bytes return `false`; safe strings and all other scalar kinds return `true`. #[inline] pub fn is_safe(&self) -> bool { match &self.inner { @@ -552,6 +597,7 @@ impl Value { } } + /// Consumes the Value, marking it as a safe string if it is one. #[inline] pub fn mark_safe(self) -> Self { match self.inner { @@ -562,6 +608,7 @@ impl Value { } } + /// If the Value is a map return the associated Map, otherwise None. pub fn as_map(&self) -> Option<&Map> { match &self.inner { ValueInner::Map(s) => Some(s), @@ -569,6 +616,7 @@ impl Value { } } + /// If the Value is an array return the associated Vec, otherwise None. pub fn as_vec(&self) -> Option<&Vec> { match &self.inner { ValueInner::Array(s) => Some(s), @@ -576,6 +624,7 @@ impl Value { } } + /// If the Value is a Bytes return the associated bytes, otherwise None. pub fn as_bytes(&self) -> Option<&[u8]> { match &self.inner { ValueInner::Bytes(s) => Some(s), @@ -583,6 +632,7 @@ impl Value { } } + /// Consumes the current Value to return its inner Map if it is one, otherwise None. pub fn into_map(self) -> Option { match self.inner { ValueInner::Map(arc) => Some(Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone())), @@ -645,6 +695,8 @@ impl Value { current.clone() } + /// Returns the truthiness of a value, eg not empty map/arrays/string and numbers different + /// from 0 pub fn is_truthy(&self) -> bool { match &self.inner { ValueInner::Undefined => false, @@ -662,6 +714,9 @@ impl Value { } } + /// Returns the length of the value. Only works with maps, arrays, bytes and strings. + /// For strings, if you use the `unicode` feature it will return the number of graphemes otherwise + /// the number of chars. #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> Option { match &self.inner { @@ -682,6 +737,7 @@ impl Value { } } + /// Reverses the content: only works for arrays, bytes and strings pub fn reverse(&self) -> TeraResult { match &self.inner { ValueInner::Array(v) => { @@ -1164,8 +1220,9 @@ impl>, T: Into> From> for V } } -// TODO: move somewhere else +/// The trait that automatically converts a value into a `TeraResult` pub trait FunctionResult { + #[allow(missing_docs)] fn into_result(self) -> TeraResult; } diff --git a/tera/src/value/number.rs b/tera/src/value/number.rs index a89e06a9..6098e2a4 100644 --- a/tera/src/value/number.rs +++ b/tera/src/value/number.rs @@ -7,7 +7,10 @@ use std::fmt::Formatter; /// Also can be used for custom filters/tests/fn when you want to ensure you get a number #[derive(Debug, Copy, Clone)] pub enum Number { + /// Integers are stored in i128, which means we can't use numbers from u128 above i128::MAX + /// for math, which is probably ok for a template engine. Integer(i128), + #[allow(missing_docs)] Float(f64), } @@ -21,21 +24,25 @@ impl fmt::Display for Number { } impl Number { + /// Is the number a float? pub fn is_float(&self) -> bool { matches!(self, Number::Float(..)) } + /// Is the number an integer? Does not check whether the float could be converted + /// to an integer losslessly, just checks the type. pub fn is_integer(&self) -> bool { matches!(self, Number::Integer(..)) } - pub fn into_float(self) -> Self { + pub(crate) fn into_float(self) -> Self { match self { Number::Float(f) => Number::Float(f), Number::Integer(f) => Number::Float(f as f64), } } + /// Converts a number to a float. pub fn as_float(&self) -> f64 { match self { Number::Float(f) => *f, @@ -43,6 +50,7 @@ impl Number { } } + /// Tries to convert a number to a i128. pub fn as_integer(&self) -> Option { match self { // `i128::MAX as f64` rounds up to `2^127`, so the upper bound is exclusive. diff --git a/tera/src/vm/state.rs b/tera/src/vm/state.rs index b57c0580..10de44f0 100644 --- a/tera/src/vm/state.rs +++ b/tera/src/vm/state.rs @@ -48,6 +48,8 @@ impl<'t> State<'t> { s } + /// Creates a new state from a `Context`. + /// Public since it's needed to test filters/fns/tests. pub fn new(context: &'t Context) -> Self { Self { stack: Stack::new(),