pochoir 0.15.1

Main crate of the pochoir template engine used to compile and render pochoir files with components
Documentation
<div align="center">
  <img src="https://gitlab.com/encre-org/pochoir/-/raw/main/.assets/logo.svg" />
  <h1>pochoir</h1>
  <p>A modern mustache template engine featuring a custom server-side Web Components implementation</p>

  <a href="https://gitlab.com/encre-org/pochoir/blob/main/LICENSE">
    <img alt="MIT License" src="https://img.shields.io/badge/license-MIT-success" />
  </a>

  <a href="https://gitlab.com/encre-org/pochoir/-/pipelines">
    <img alt="Pipeline status" src="https://gitlab.com/encre-org/pochoir/badges/main/pipeline.svg" />
  </a>

  <a href="https://deps.rs/repo/gitlab/encre-org/pochoir">
    <img alt="Dependency status" src="https://deps.rs/repo/gitlab/encre-org/pochoir/status.svg" />
  </a>

  <a href="https://crates.io/crates/pochoir">
    <img alt="Published on crates.io" src="https://img.shields.io/crates/v/pochoir" />
  </a>

  <a href="https://docs.rs/pochoir">
    <img alt="Documentation on docs.rs" src="https://img.shields.io/docsrs/pochoir" />
  </a>
</div>

### 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`:

```toml
[dependencies]
pochoir = "0.15.1"
```

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:

```rust ,ignore
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.

```rust ,ignore
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:

```rust
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.

```rust ,ignore
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](https://docs.rs/pochoir/latest/pochoir/transformers/index.html).

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](https://docs.rs/pochoir-lang/latest/pochoir_lang/#functions-and-rust-integration).

### 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]https://gitlab.com/encre-org/pochoir/-/tree/main/crates/pochoir/examples to better know what is possible to do with `pochoir`
- Learn the [syntax of components]https://docs.rs/pochoir/latest/pochoir/compiler/index.html to start making dynamic and composable templates
- Go to the [API reference]https://docs.rs/pochoir 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 `Transformer`s 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](https://gitlab.com/encre-org/pochoir/blob/main/LICENSE).