Crate mufmt

Crate mufmt 

Source
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 Ast describes how to parse a template expression into a strongly typed format.
  • The Manifest describes how to display an Ast.
  • The Template uses the Ast to compile templates and the Manifest to render them.

For more information, go to:

§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.

  1. The template not inside an expression are referred to as text, and the parts inside an expression are referred to as expressions.
  2. Expressions are whitespace trimmed according to the rules of trim.
  3. To include brackets inside text, doubled brackets (like {{ and }}) result in text consisting of a single bracket.
  4. 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.

  1. "Hello {name}!" is text Hello , then an expression name, then text !.
  2. "Hello { name }!" is equivalent to the above example.
  3. "{{{contents}" is text {{, then an expression contents.
  4. "{## #}##}" is an expression #}.

§API overview

Broadly speaking, template rendering is split into two independent phases.

  1. A template string "Hello {name}" is compiled by the μfmt syntax and the expression parsing rules defined by the Ast implementation. The compiled representation is a Template and an error during this phase is a SyntaxError.
  2. The compiled template is combined with additional data via a Manifest implementation to obtain a type which can be displayed. An error during this phase is the associated error type Manifest::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:

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
Ast and Manifest implementations

Macros§

ast_from_str
A convenience macro to implement Ast for a type which implements FromStr.

Structs§

Oneshot
A template which can be rendered at most once.
SyntaxError
An error while compiling a template string.
Template
A compiled representation of a template string with expression syntax defined by an Ast.
TemplateSpans
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.
FmtRenderError
An error while rendering a compiled template string into a fmt::Write.
IORenderError
An error while rendering a compiled template string into an io::Write.
Span
A component of a template, either text or an expression.
SyntaxErrorKind
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.
ManifestMut
A manifest which can display an Ast using mutable state.

Type Aliases§

BorrowedTemplate
A type alias for a template which borrows from the template string.
OwnedTemplate
A type alias for a template which owns its own data.