Expand description
§Mufmt
Mufmt is a minimal and extensible runtime formatting library.
mufmt allows arbitrary types to define a formatting syntax and compiled template
format. mufmt also provides a number of built-in formats, backed by data stored in
collection types like HashMap or Vec.
The main API entrypoints are Ast, Manifest, and Template.
In short:
- The
Astdescribes how to parse a template expression into a strongly typed format. - The
Manifestdescribes how to display anAst. - The
Templateuses theAstto compile templates and theManifestto render them.
For more information, go to:
- Introduction: for a quick introduction and basic usage
- Syntax: to understand the global syntax rules
AstandManifestoverview: for a general overview of the coreAstandManifesttraitsTemplateoverview: for an overview of theTemplatestruct, and its cousinsOneshotandTemplateSpans.- The
typesmodule: for customAstandManifestimplementations and a documentation of the implementations for the standard library.
§Introduction
mufmt templates are similar to Rust format strings:
"Hello {name}!";Parts outside braces are referred to as text and parts inside braces are referred to as expressions. See syntax for a full description of the syntax.
However, mufmt does not interpret the contents of expression. Instead, the expression syntax is
defined by an Ast implementation.
use mufmt::BorrowedTemplate;
use std::collections::HashMap;
// The `Ast` is &str
let template = BorrowedTemplate::<&str>::compile("Hello {name}!").unwrap();
// The `Manifest` is `HashMap<&str, &str>`
let mfst = HashMap::from([("name", "John")]);
assert_eq!(template.render(&mfst).unwrap(), "Hello John!");In this example, the {name} expression is parsed as a string: "name". The parsing rules are
defined by the Manifest implementation of the HashMap, which expects raw strings which
correspond to map keys. In general, the type is the associated Ast implementation.
Suppose we instead provide a Vec as the context. Then the keys are parsed as usize.
In this example, we also see that same template can be re-used with a new context without
requiring recompilation of the template.
// we don't need a type annotation here since `Vec` only accepts `usize` as the key
let template = BorrowedTemplate::compile("Letter: {1}").unwrap();
let mfst = vec!["α", "β", "γ"];
let mut buf = String::new();
template.render_fmt(&mfst, &mut buf).unwrap();
assert_eq!(buf, "Letter: β");
// we define a new context: but now, a `Vec<char>` instead of `Vec<&'static string>`
let new_mfst = vec!['a', 'b', 'c'];
// this works since (for `Vec`) the key type does not depend on the type in the container
buf.clear();
template.render_fmt(&new_mfst, &mut buf).unwrap();
assert_eq!(buf, "Letter: b");
// the `Ast` is a `usize`, which is used to index into the vec
// if the user provides an invalid type, compilation will fail
assert!(BorrowedTemplate::<usize>::compile("{-100}").is_err()); // SyntaxError::Ast(ParseIntError, ...)
assert!(BorrowedTemplate::<i8>::compile("{-100}").is_ok());Passing a template with the incorrect expression type will result in a compilation error.
let mfst = HashMap::from([("123", "456")]);
// this `HashMap` has `&str` keys, which cannot be retrieved with a `usize`
let template = BorrowedTemplate::<usize>::compile("Number: {123}").unwrap();
template.render(&mfst);Modifying the key type fixes this error.
let mfst = HashMap::from([(123, "456")]); // usize keys are compatible
let template = BorrowedTemplate::<usize>::compile("Number: {123}").unwrap();
template.render(&mfst);§Syntax
A mufmt template is a UTF-8 string where brace-delimited expressions {...} are replaced with values
when the string is rendered.
- The template not inside an expression are referred to as text, and the parts inside an expression are referred to as expressions.
- Expressions are whitespace trimmed according to the rules of
trim. - To include braces inside text, doubled braces (like
{{and}}) result in text consisting of a single brace. - To include braces inside expressions, you can use extended expression delimiters: the initial
{of an expression may be followed by any number of#characters. Then, the expression can only be closed by an equal number of#characters, followed by}.
Otherwise, the interpretation of contents of the expressions are defined by the specific Ast implementation.
Here are examples for each of the above points.
"Hello {name}!"is textHello, then an expressionname, then text!."Hello { name }!"is equivalent to the above example."{{{contents}"is text{, then an expressioncontents."{## #}##}"is an expression#}.
§API overview
Broadly speaking, template rendering is split into two independent phases.
- A template string
"Hello {name}"is compiled by the mufmt syntax and the expression parsing rules defined by theAstimplementation. The compiled representation is aTemplateand an error during this phase is aSyntaxError. - The compiled template is combined with additional data via a
Manifestimplementation to obtain a type which can be displayed. An error during this phase is the associated error typeManifest::Error.
The precise dividing line between where the Ast parsing ends and template rendering begins is
intentionally unspecified and depends on the use-case. However, a good rule of thumb
(especially if you are reusing templates) is to do as much as possible when parsing into the
Ast and to minimize errors during rendering.
§The Ast trait
An Ast implementation is responsible for parsing the contents of a {expression}.
pub trait Ast<'fmt>: Sized {
type Error;
fn from_expr(expr: &'fmt str) -> Result<Self, Self::Error>;
}The Ast::from_expr method is called by Template::compile on each expression in the template
string.
The associated 'fmt lifetime corresponds to the lifetime of the original template string and
can be used if the Ast benefits from borrowing from the text of the expression itself.
The simplest Ast implementation is the one for &'fmt str which is exactly as follows:
impl<'fmt> Ast<'fmt> for &'fmt str {
// Always succeeds
type Error = std::convert::Infallible;
fn from_expr(expr: &'fmt str) -> Result<Self, Self::Error> {
Ok(expr)
}
}§The Manifest trait
The Manifest trait describes how to display the Ast corresponding to each expression
produced by an Ast implementation.
use std::fmt::Display;
pub trait Manifest<A> {
type Error;
fn manifest(&self, ast: &A) -> Result<impl Display, Self::Error>;
}A Manifest implementation is one which knows how to display any Ast of type A. A single
type can implement Manifest multiple times, depending on the keys which it accepts.
A Vec implemenets Manifest by converting an index to the corresponding item at that
index:
use mufmt::types::IndexOutOfRange;
impl<T: Display> Manifest<usize> for Vec<T> {
type Error = IndexOutOfRange;
fn manifest(&self, n: &usize) -> Result<impl Display, Self::Error> {
self.get(*n).ok_or(IndexOutOfRange(*n))
}
}The returned Display implementation is ephemeral. It may borrow from self and
also from the ast.
Also see ManifestMut, which permits temporary mutable state while rendering a template.
§Template overview
When you want to work with a template string, you have essentially three options:
§Template struct
By default, you should use the Template struct. This contains a compiled representation of
a template string, with expressions compiled according to the rules of the associated Ast typed.
You can construct a Template from a template string using Template::compile, which will
immediately report syntax errors. Then, the Template struct can be rendered using three methods:
Template::render, to allocate a newStringwith the rendered contents.Template::render_io, to write into anio::Writebuffer.Template::render_fmt, to write into afmt::Writebuffer.
These methods do not consume the template which allows repeated rendering of the same template. Moreover, since compilation is separate from rendering, you can report errors early, before rendering the template.
The Template::render* methods accept a ManifestMut implementation, but note that any type which implements
Manifest automatically implements ManifestMut as well.
Since a Template is generic over the text type, some aliases are provided which may be more
convenient:
BorrowedTemplate, with the lifetime of the text tied to the original template string.OwnedTemplate, which decouples the lifetime of the text at the cost of additional allocations.
§Oneshot struct
If you know you will only render a template exactly once, you can use the Oneshot struct.
It has similar same rendering methods as a Template:
Oneshot::render, to allocate a newStringwith the rendered contents.Oneshot::render_io, to write into anio::Writebuffer.Oneshot::render_fmt, to write into afmt::Writebuffer.
The main gain is that we skip the intermediate compiled representation. This can result in reduced memory usage if the template is extremely large.
The main downsides are:
- The error types are more general (returns a global
Error, rather than an error type specialized to the method) - The template cannot be reused.
- The lifetime is also tied to the lifetime of the template string.
It also exposes validation methods to check syntax without rendering the template:
§TemplateSpans iterator
Finally, if you want to work with the template directly and are not interested in
rendering, you can use the TemplateSpans iterator.
This is an iterator over Result<Span, SyntaxError>, where a Span represents a contiguous
substring of the template string. The key additional feature is error
recovery: this iterator can recover from non-fatal parsing
errors, such as expressions which are invalid for the provided Ast.
A TemplateSpans struct can also be obtained with Oneshot::spans.
If you already have a Template, you might be interested Template::spans, which just
contains the Spans without the possible syntax errors.
Modules§
- types
AstandManifestimplementations
Structs§
- Oneshot
- A template which can be rendered at most once.
- Syntax
Error - An error while compiling a template string.
- Template
- A compiled representation of a template string with expression syntax defined by an
Ast. - Template
Spans - An incrementally parsed iterator of the spans of a template string.
Enums§
- Error
- A general error type capturing any error which could occur while compiling or rendering a template string.
- FmtRender
Error - An error while rendering a compiled template string into a
fmt::Write. - IORender
Error - An error while rendering a compiled template string into an
io::Write. - Span
- A component of a template, either text or an expression.
- Syntax
Error Kind - The type of syntax error which occured.
Traits§
- Ast
- A typed representation of an expression which does not interpret the contents.
- Manifest
- A manifest knows how to display an
Ast. - Manifest
Mut - A manifest which can display an
Astusing mutable state.
Type Aliases§
- Borrowed
Template - A type alias for a template which borrows from the template string.
- Owned
Template - A type alias for a template which owns its own data.