{"id":15478399,"url":"https://github.com/rossmacarthur/upon","last_synced_at":"2025-04-13T15:53:58.494Z","repository":{"id":37975866,"uuid":"494012016","full_name":"rossmacarthur/upon","owner":"rossmacarthur","description":"📌 A lightweight and powerful template engine for Rust","archived":false,"fork":false,"pushed_at":"2025-02-08T12:04:02.000Z","size":658,"stargazers_count":47,"open_issues_count":7,"forks_count":2,"subscribers_count":2,"default_branch":"trunk","last_synced_at":"2025-04-10T02:56:55.901Z","etag":null,"topics":["rust","template-engine"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rossmacarthur.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-19T09:50:16.000Z","updated_at":"2025-04-03T14:43:04.000Z","dependencies_parsed_at":"2024-02-23T11:44:17.524Z","dependency_job_id":null,"html_url":"https://github.com/rossmacarthur/upon","commit_stats":{"total_commits":152,"total_committers":2,"mean_commits":76.0,"dds":0.006578947368421018,"last_synced_commit":"1b2de935ed867aefdda28f30b5a7268501bf67c2"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossmacarthur%2Fupon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossmacarthur%2Fupon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossmacarthur%2Fupon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossmacarthur%2Fupon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rossmacarthur","download_url":"https://codeload.github.com/rossmacarthur/upon/tar.gz/refs/heads/trunk","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248741147,"owners_count":21154249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust","template-engine"],"created_at":"2024-10-02T04:03:57.239Z","updated_at":"2025-04-13T15:53:58.487Z","avatar_url":"https://github.com/rossmacarthur.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- Generated by cargo-onedoc. DO NOT EDIT. --\u003e\n\n# upon\n\n[![Crates.io Version](https://badgers.space/crates/version/upon)](https://crates.io/crates/upon)\n[![Docs.rs Latest](https://badgers.space/badge/docs.rs/latest/orange)](https://docs.rs/upon)\n[![Build Status](https://badgers.space/github/checks/rossmacarthur/upon?label=build)](https://github.com/rossmacarthur/upon/actions/workflows/build.yaml?query=branch%3Atrunk)\n\nA simple, powerful template engine with minimal dependencies and\nconfigurable delimiters.\n\n## Table of Contents\n\n- [Overview](#overview)\n  - [Syntax](#syntax)\n  - [Engine](#engine)\n  - [Why another template engine?](#why-another-template-engine)\n  - [MSRV](#msrv)\n- [Getting started](#getting-started)\n- [Further reading](#further-reading)\n- [Features](#features)\n- [Examples](#examples)\n  - [Nested templates](#nested-templates)\n  - [Render to writer](#render-to-writer)\n  - [Borrowed templates with short lifetimes](#borrowed-templates-with-short-lifetimes)\n  - [Custom template store and function](#custom-template-store-and-function)\n- [Benchmarks](#benchmarks)\n- [License](#license)\n\n## Overview\n\n### Syntax\n\n- Expressions: `{{ user.name }}`\n- Conditionals: `{% if user.enabled %} ... {% endif %}`\n- Loops: `{% for user in users %} ... {% endfor %}`\n- Nested templates: `{% include \"nested\" %}`\n- Configurable delimiters: `\u003c? user.name ?\u003e`, `(( if user.enabled ))`\n- Arbitrary user defined filters: `{{ user.name | replace: \"\\t\", \" \" }}`\n\n### Engine\n\n- Clear and well documented API\n- Customizable value formatters: `{{ user.name | escape_html }}`\n- Render to a [`String`][string] or any [`std::io::Write`][stdiowrite] implementor\n- Render using any [`serde`][serde] serializable values\n- Convenient macro for quick rendering:\n  `upon::value!{ name: \"John\", age: 42 }`\n- Pretty error messages when displayed using `{:#}`\n- Format agnostic (does *not* escape values for HTML by default)\n- Minimal dependencies and decent runtime performance\n\n### Why another template engine?\n\nIt’s true there are already a lot of template engines for Rust!\n\nI created `upon` because I required a template engine that had runtime\ncompiled templates, configurable syntax delimiters and minimal dependencies.\nI also didn’t need support for arbitrary expressions in the template syntax\nbut occasionally I needed something more flexible than outputting simple\nvalues (hence filters). Performance was also a concern for me, template\nengines like [Handlebars] and [Tera] have a lot of features but can be up to\nfive to seven times slower to render than engines like [TinyTemplate].\n\nBasically I wanted something like [TinyTemplate] with support for\nconfigurable delimiters and user defined filter functions. The syntax is\ninspired by template engines like [Liquid] and [Jinja].\n\n### MSRV\n\nCurrently the minimum supported version for `upon` is Rust 1.66. The MSRV\nwill only ever be increased in a breaking release.\n\n## Getting started\n\nFirst, add the crate to your Cargo manifest.\n\n```sh\ncargo add upon\n```\n\nNow construct an [`Engine`][engine]. The engine stores the syntax config, filter\nfunctions, formatters, and compiled templates. Generally, you only need to\nconstruct one engine during the lifetime of a program.\n\n```rust\nlet engine = upon::Engine::new();\n```\n\nNext, [`add_template(..)`][add_template] is used to compile and store a\ntemplate in the engine.\n\n```rust\nengine.add_template(\"hello\", \"Hello {{ user.name }}!\")?;\n```\n\nFinally, the template is rendered by fetching it using\n[`template(..)`][template], calling\n[`render(..)`][render] and rendering to a string.\n\n```rust\nlet result = engine\n    .template(\"hello\")\n    .render(upon::value!{ user: { name: \"John Smith\" }})\n    .to_string()?;\nassert_eq!(result, \"Hello John Smith!\");\n```\n\n## Further reading\n\n- The [`syntax`][syntax] module documentation outlines the template syntax.\n- The [`filters`][filters] module documentation describes filters and how they work.\n- The [`fmt`][fmt] module documentation contains information on value formatters.\n- In addition to the examples in the current document, the\n  [`examples/`](https://github.com/rossmacarthur/upon/tree/trunk/examples) directory in the repository constains some more\n  concrete code examples.\n\n## Features\n\nThe following crate features are available.\n\n- **`filters`** *(enabled by default)* — Enables support for filters in\n  templates (see [`Engine::add_filter`][engineadd_filter]). This does *not* affect value\n  formatters (see [`Engine::add_formatter`][engineadd_formatter]). Disabling this will improve\n  compile times.\n\n- **`serde`** *(enabled by default)* — Enables all serde support and pulls\n  in the [`serde`][serde] crate as a dependency. If disabled then you can use\n  [`render_from(..)`][render_from] to render templates and\n  construct the context using [`Value`][value]’s `From` impls.\n\n- **`syntax`** *(disabled by default)* — Enables support for configuring\n  custom delimiters in templates (see [`Engine::with_syntax`][enginewith_syntax]) and pulls in\n  the [`aho-corasick`][aho-corasick] crate.\n\n- **`unicode`** *(enabled by default)* — Enables unicode support and pulls\n  in the [`unicode-ident`][unicode-ident] and\n  [`unicode-width`][unicode-width] crates. If disabled then unicode\n  identifiers will no longer be allowed in templates and `.chars().count()`\n  will be used in error formatting.\n\nTo disable all features or to use a subset you need to set `default-features = false` in your Cargo manifest and then enable the features that you would\nlike. For example to use **`serde`** but disable **`filters`** and\n**`unicode`** you would do the following.\n\n```toml\n[dependencies]\nupon = { version = \"...\", default-features = false, features = [\"serde\"] }\n```\n\n## Examples\n\n### Nested templates\n\nYou can include other templates by name using `{% include .. %}`.\n\n```rust\nlet mut engine = upon::Engine::new();\nengine.add_template(\"hello\", \"Hello {{ user.name }}!\")?;\nengine.add_template(\"goodbye\", \"Goodbye {{ user.name }}!\")?;\nengine.add_template(\"nested\", \"{% include \\\"hello\\\" %}\\n{% include \\\"goodbye\\\" %}\")?;\n\nlet result = engine.template(\"nested\")\n    .render(upon::value!{ user: { name: \"John Smith\" }})\n    .to_string()?;\nassert_eq!(result, \"Hello John Smith!\\nGoodbye John Smith!\");\n```\n\n### Render to writer\n\nInstead of rendering to a string it is possible to render the template to\nany [`std::io::Write`][stdiowrite] implementor using\n[`to_writer(..)`][to_writer].\n\n```rust\nuse std::io;\n\nlet mut engine = upon::Engine::new();\nengine.add_template(\"hello\", \"Hello {{ user.name }}!\")?;\n\nlet mut stdout = io::BufWriter::new(io::stdout());\nengine\n    .template(\"hello\")\n    .render(upon::value!{ user: { name: \"John Smith\" }})\n    .to_writer(\u0026mut stdout)?;\n// Prints: Hello John Smith!\n```\n\n### Borrowed templates with short lifetimes\n\nIf the lifetime of the template source is shorter than the engine lifetime\nor you don’t need to store the compiled template then you can also use the\n[`compile(..)`][compile] function to return the template directly.\n\n```rust\nlet template = engine.compile(\"Hello {{ user.name }}!\")?;\nlet result = template\n    .render(\u0026engine, upon::value!{ user: { name: \"John Smith\" }})\n    .to_string()?;\nassert_eq!(result, \"Hello John Smith!\");\n```\n\n### Custom template store and function\n\nThe [`compile(..)`][compile] function can also be used in\nconjunction with a custom template store which can allow for more advanced\nuse cases. For example: relative template paths or controlling template\naccess.\n\n```rust\nlet mut store = std::collections::HashMap::\u003c\u0026str, upon::Template\u003e::new();\nstore.insert(\"hello\", engine.compile(\"Hello {{ user.name }}!\")?);\nstore.insert(\"goodbye\", engine.compile(\"Goodbye {{ user.name }}!\")?);\nstore.insert(\"nested\", engine.compile(\"{% include \\\"hello\\\" %}\\n{% include \\\"goodbye\\\" %}\")?);\n\nlet result = store.get(\"nested\")\n    .unwrap()\n    .render(\u0026engine, upon::value!{ user: { name: \"John Smith\" }})\n    .with_template_fn(|name| {\n        store\n            .get(name)\n            .ok_or_else(|| String::from(\"template not found\"))\n    })\n    .to_string()?;\nassert_eq!(result, \"Hello John Smith!\\nGoodbye John Smith!\");\n```\n\n[Handlebars]: https://crates.io/crates/handlebars\n[Tera]: https://crates.io/crates/tera\n[TinyTemplate]: https://crates.io/crates/tinytemplate\n[Liquid]: https://liquidjs.com\n[Jinja]: https://jinja.palletsprojects.com\n\n## Benchmarks\n\n`upon` was benchmarked against several popular template rendering engines in the\nRust ecosystem. Obviously, each of these engines has a completely different\nfeature set so the benchmark just compares the performance of some of the\nfeatures that they share.\n\n- [handlebars](https://crates.io/crates/handlebars) v6.3.0\n- [liquid](https://crates.io/crates/liquid) v0.26.9\n- [minijinja](https://crates.io/crates/minijinja) v2.6.0\n- [tera](https://crates.io/crates/tera) v1.20.0\n- [tinytemplate](https://crates.io/crates/tinytemplate) v1.2.1\n- upon v0.9.0\n\n![Violin plot of compile results](./benches/results/compile.svg)\n![Violin plot of render results](./benches/results/render.svg)\n![Violin plot of render with filters results](./benches/results/filters.svg)\n\nBenchmarking was done using [criterion](https://crates.io/crates/criterion).\n\n**Host**\n\n- Apple M2 Max\n- 32 GB RAM\n- macOS 15.2\n- Rust 1.84.1\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or\n  http://www.apache.org/licenses/LICENSE-2.0)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n\n[add_template]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.add_template\n[aho-corasick]: https://crates.io/crates/aho-corasick\n[compile]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.compile\n[engine]: https://docs.rs/upon/latest/upon/struct.Engine.html\n[engineadd_filter]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.add_filter\n[engineadd_formatter]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.add_formatter\n[enginewith_syntax]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.with_syntax\n[filters]: https://docs.rs/upon/latest/upon/filters/index.html\n[fmt]: https://docs.rs/upon/latest/upon/fmt/index.html\n[render]: https://docs.rs/upon/latest/upon/struct.TemplateRef.html#method.render\n[render_from]: https://docs.rs/upon/latest/upon/struct.TemplateRef.html#method.render_from\n[serde]: https://crates.io/crates/serde\n[stdiowrite]: https://doc.rust-lang.org/stable/std/io/trait.Write.html\n[string]: https://doc.rust-lang.org/stable/std/string/struct.String.html\n[syntax]: ./SYNTAX.md\n[template]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.template\n[to_writer]: https://docs.rs/upon/latest/upon/struct.TemplateRef.html#method.to_writer\n[unicode-ident]: https://crates.io/crates/unicode-ident\n[unicode-width]: https://crates.io/crates/unicode-width\n[value]: https://docs.rs/upon/latest/upon/enum.Value.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frossmacarthur%2Fupon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frossmacarthur%2Fupon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frossmacarthur%2Fupon/lists"}