pochoir

Crates.iopochoir
lib.rspochoir
version0.12.2
created_at2025-07-04 19:35:25.195807+00
updated_at2025-08-10 16:42:22.179556+00
descriptionMain crate of the pochoir template engine used to compile and render pochoir files with components
homepage
repositoryhttps://gitlab.com/encre-org/pochoir.git
max_upload_size
id1738398
size242,221
(nifouprog)

documentation

https://encre-org.gitlab.io/pochoir/pochoir

README

pochoir

A modern mustache template engine featuring a custom server-side Web Components implementation

MIT License Pipeline status Dependency status Published on crates.io Documentation on docs.rs

Features

  • Contains a Django-inspired mustache templating engine to embed data
  • Contains a custom-designed language with a good Rust compatibility to manipulate the data used in expressions
  • Contains an HTML parser to do some transformations to the pages
  • Contains a component system with support for properties, slots, defining components using <template> elements
  • Contains a modular API, it is easy to transform the HTML tree with the Transformer API
  • Contains several error formatters for parsing and interpreting errors to improve DX

Getting started

Add pochoir to your Cargo.toml:

[dependencies]
pochoir = "0.12.2"

Then, you need to choose a way to get your source HTML files. You can use pre-defined providers to do that. For example, the FilesystemProvider gets the source HTML from the files in a directory:

use pochoir::{Context, FilesystemProvider};

// The `FilesystemProvider` selects files using two criterias: if they have a
// known extension (they can be configured, by default just `html` files can be
// used) and if they are in one of the inserted path. Here all files in the
// `templates` directory having a `.html` extension will be used
let provider = FilesystemProvider::new().with_path("templates");
let mut context = Context::new();

let _html = provider.compile("index", &mut context)?;

And the StaticMapProvider stores source files in a map with the component name as key.

use pochoir::{Context, StaticMapProvider};

// The last argument is the path to the file if it was read from
// the filesystem, it is used in error messages to find the HTML file source
let provider = StaticMapProvider::new().with_template("index", "<h1>Index page</h1>", None);
let mut context = Context::new();

let _html = provider.compile("index", &mut context)?;

You can quickly do some really complex things using these providers:

use pochoir::{object, Context, Function, StaticMapProvider, error};

// 1. Declare your sources: they don't need to be static, in a real world usage,
// they would be fetched from the filesystem or from a database.
let provider = StaticMapProvider::new()
    .with_template("index", r#"
    <main>
      <h1>Hi 👋, this is my blog!</h1>
      <p>I'm interested in cars, cats and caps.
      This is a list of my latest blog posts:</p>
      <ul>
        {% for post in posts %}
        <li><ui-card post="{{ post }}" /></li>
        {% endfor %}
      </ul>
    </main>"#, None)
    .with_template("ui-card", r#"
    <a href="/posts/{{ slugify(post.title) }}" class="card">
      <div class="card-thumbnail">
        <img src="{{ post.thumbnail }}">
      </div>
      <div class="card-footer">
        <h5>{{ post.title }}</h5>
        <p>{{ truncate(post.description, 80) }}</p>
      </div>
    </a>"#, None);

// 2. Then define the context (all the variables used in expressions) using Rust
// types, they will be automagically transformed to values used in the
// language (without being serialized!) using the `IntoValue` trait
let mut context = Context::new();
context.insert("posts", vec![
    // Constructing objects is done using a macro or with methods of the `Object` structure
    object! {
        "title" => "A beautiful castle",
        "description" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "thumbnail" => "https://picsum.photos/seed/41/300",
    },
    object! {
        "title" => "A beautiful beach",
        "description" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "thumbnail" => "https://picsum.photos/seed/44/300",
    },
]);

// 3. Rust functions can also be included in the Context and called in the
// expressions. Here `insert_inherited` is used in order for the component
// `ui-card` to inherit this function from the parent template. Arguments are
// normal Rust types which are converted automagically from the values used in
// the language using the `FromValue` trait
context.insert_inherited("truncate", Function::new(|value: String, max: usize| {
    Ok(if value.len() <= max {
        value
    } else {
        value[..max].to_string()
    })
}));

// 4. Finally, compile the whole template into a single HTML string. When an error happens,
// a complete, formatted error is displayed in the terminal using ANSI escape codes
let _html = provider
    .compile("index", &mut context)
    .map_err(|e| {
        error::display_ansi_error(
            &e,
            &provider.get(e.component_name())
            .expect("component should not be removed from the provider")
            .data,
        )
    });

But if you want more control over how the source files are fetched, you can use the closure API by directly using the pochoir::compile function. The closure takes the name of the component and returns a ComponentFile with the file name and data.

use pochoir::{Context, ComponentFile, error};
use std::path::Path;

let html = pochoir::compile("index", &mut Context::new(), |name| {
    // In a real world usage, you would fetch the HTML from a complex, dynamic,
    // pipeline of sources, maybe from the network.
    // A `ComponentFile` is used to associate some data with a path to a file,
    // if it was fetched from the filesystem. You can use
    // `ComponentFile::new_inline` if you don't want to provide a path, in this
    // case the path will simply be `inline`
    Ok(match name {
        "index" => ComponentFile::new_inline("<h1>Index page</h1><my-button />"),
        "my-button" => ComponentFile::new(Path::new("my-button.html"), "<button>Click me!</button>"),
        _ => return Err(error::component_not_found(name)),
    })
})?;

Extensions

The main way of extending pochoir is by using transformers. They are used to transform the HTML tree and can be used to do various, repeated tasks. For example, they can be used to enhance the CSS <style> elements used in components by providing scoped CSS, minification, autoprefixing and bundling, like what is done in the EnhancedCss structure of the pochoir-extra crate. Learn more about transformers in the API reference.

Another way is by defining custom Rust functions that will be inserted using Context::insert and used in templates. Learn more about custom functions in the API reference.

Command line interface

If you want to try pochoir without worrying about having to start a Rust project, you can try the CLI by running cargo install --git https://gitlab.com/encre-org/pochoir.git pochoir-cli. You can then try to develop some website using live-reloading by running pochoir --watch serve in a directory containing some HTML files using pochoir template expressions or by running pochoir build to build a production version inside a directory. Keep in mind that extra transformers could not be used in the CLI.

Where to go next?

  • Check out the examples to better know what is possible to do with pochoir
  • Learn the syntax of components to start making dynamic and composable templates
  • Go to the API reference to understand better how the pieces fit together

Organization

This Cargo workspace contains 8 crates:

  • pochoir-common: defines some utilities shared between all the other crates
  • pochoir-parser: a full HTML parser with support for expressions and statements
  • pochoir-lang: a parser and interpreter for the custom language used in expressions
  • pochoir-template-engine: a template engine replacing expressions and statements with real content
  • pochoir-macros: defines derive macros used to implement FromValue and IntoValue automagically
  • pochoir-extra: contains some extra Transformers like EnhancedCss or AccessibilityChecker
  • pochoir-cli: the binary for the command line interface
  • pochoir: the main crate exporting all other low-level crates (not pochoir-extra) and defining the component system compiler

About the name

pochoir means stencil in French and a stencil contains holes which are filled with something, like a template engine or a component system!

License

pochoir is published under the MIT license.

Commit count: 0

cargo fmt