Expand description
§μfmt
μfmt (mufmt) is a minimal and extensible runtime formatting library.
μfmt allows arbitrary types to define a formatting syntax and compiled template
format. μfmt 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
μfmt templates are similar to Rust format strings:
"Hello {name}!";Parts outside brackets are referred to as text and parts inside brackets are referred to as expressions. See syntax for a full description of the syntax.
However, μfmt 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 intermediate syntax type 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 μfmt template is a UTF-8 string where bracket-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 brackets inside text, doubled brackets (like
{{and}}) result in text consisting of a single bracket. - To include brackets 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 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 μfmt 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,Oneshot, andTemplateSpans.
§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, for convenient rendering directly into aString.Template::render_io, when you have aio::Writebuffer and want to avoid an allocation.Template::render_fmt, when you have afmt::Writebuffer and want to avoid an allocation.
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:
§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:
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 downside is that the error types are more general (returns a global Error, rather
than an error type specialized to the method) and the template cannot be reused.
It also exposes validation methods to check syntax without rendering the template:
§TemplateSpans iterator
Finally, if you actually 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
Macros§
- ast_
from_ str - A convenience macro to implement
Astfor a type which implementsFromStr.
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 - A dynamically 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.