From ce1523cd19ecaf861c221433752a05f61e2b53fa Mon Sep 17 00:00:00 2001 From: Ross MacArthur Date: Sat, 28 Oct 2023 17:07:57 +0200 Subject: [PATCH] Tidy up documentation and examples --- README.md | 110 ++++++++++++++++++---- examples/README.md | 8 +- examples/{syntax.rs => custom_syntax.rs} | 0 examples/quick.rs | 12 --- examples/render_to_writer.rs | 16 ---- src/fmt.rs | 7 +- src/lib.rs | 112 +++++++++++++++++++---- src/render/core.rs | 1 - src/render/mod.rs | 8 +- 9 files changed, 196 insertions(+), 78 deletions(-) rename examples/{syntax.rs => custom_syntax.rs} (100%) delete mode 100644 examples/quick.rs delete mode 100644 examples/render_to_writer.rs diff --git a/README.md b/README.md index 5e2d310..11c8c7a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ configurable delimiters. - [Getting started](#getting-started) - [Further reading](#further-reading) - [Features](#features) +- [Examples](#examples) + - [Nested templates](#nested-templates) + - [Render to writer](#render-to-writer) + - [Borrowed templates with short lifetimes](#borrowed-templates-with-short-lifetimes) + - [Custom template store and function](#custom-template-store-and-function) - [Benchmarks](#benchmarks) - [License](#license) @@ -91,22 +96,14 @@ engine.add_template("hello", "Hello {{ user.name }}!")?; ``` Finally, the template is rendered by fetching it using -[`get_template`][get_template] and calling -[`render`][render]. +`template(..)`, calling +`render(..)` and rendering to a string. ```rust -let template = engine.get_template("hello").unwrap(); -let result = template.render(upon::value!{ user: { name: "John Smith" }}).to_string()?; -assert_eq!(result, "Hello John Smith!"); -``` - -If the lifetime of the template source is shorter than the engine lifetime -or you don’t need to store the compiled template then you can also use the -[`compile`][compile] function to return the template directly. - -```rust -let template = engine.compile("Hello {{ user.name }}!")?; -let result = template.render(&engine, upon::value!{ user: { name: "John Smith" }}).to_string()?; +let result = engine + .template("hello") + .render(upon::value!{ user: { name: "John Smith" }}) + .to_string()?; assert_eq!(result, "Hello John Smith!"); ``` @@ -115,8 +112,9 @@ assert_eq!(result, "Hello John Smith!"); - The [`syntax`][syntax] module documentation outlines the template syntax. - The [`filters`][filters] module documentation describes filters and how they work. - The [`fmt`][fmt] module documentation contains information on value formatters. -- The [`examples/`](https://github.com/rossmacarthur/upon/tree/trunk/examples) directory in the repository contains concrete - code examples. +- In addition to the examples in the current document, the + [`examples/`](https://github.com/rossmacarthur/upon/tree/trunk/examples) directory in the repository constains some more + concrete code examples. ## Features @@ -147,6 +145,83 @@ like. For example to use **`serde`** but disable **`filters`** and upon = { version = "...", default-features = false, features = ["serde"] } ``` +## Examples + +### Nested templates + +You can include other templates by name using `{% include .. %}`. + +```rust +let mut engine = upon::Engine::new(); +engine.add_template("hello", "Hello {{ user.name }}!")?; +engine.add_template("goodbye", "Goodbye {{ user.name }}!")?; +engine.add_template("nested", "{% include \"hello\" %}\n{% include \"goodbye\" %}")?; + +let result = engine.template("nested") + .render(upon::value!{ user: { name: "John Smith" }}) + .to_string()?; +assert_eq!(result, "Hello John Smith!\nGoodbye John Smith!"); +``` + +### Render to writer + +Instead of rendering to a string it is possible to render the template to +any [`std::io::Write`][stdiowrite] implementor using +`to_writer(..)`. + +```rust +use std::io; + +let mut engine = upon::Engine::new(); +engine.add_template("hello", "Hello {{ user.name }}!")?; + +let mut stdout = io::BufWriter::new(io::stdout()); +engine + .template("hello") + .render(upon::value!{ user: { name: "John Smith" }}) + .to_writer(&mut stdout)?; +// Prints: Hello John Smith! +``` + +### Borrowed templates with short lifetimes + +If the lifetime of the template source is shorter than the engine lifetime +or you don’t need to store the compiled template then you can also use the +`compile(..)` function to return the template directly. + +```rust +let template = engine.compile("Hello {{ user.name }}!")?; +let result = template + .render(&engine, upon::value!{ user: { name: "John Smith" }}) + .to_string()?; +assert_eq!(result, "Hello John Smith!"); +``` + +### Custom template store and function + +The `compile(..)` function can also be used in +conjunction with a custom template store which can allow for more advanced +use cases. For example: relative template paths or controlling template +access. + +```rust +let mut store = std::collections::HashMap::<&str, upon::Template>::new(); +store.insert("hello", engine.compile("Hello {{ user.name }}!")?); +store.insert("goodbye", engine.compile("Goodbye {{ user.name }}!")?); +store.insert("nested", engine.compile("{% include \"hello\" %}\n{% include \"goodbye\" %}")?); + +let result = store.get("nested") + .unwrap() + .render(&engine, upon::value!{ user: { name: "John Smith" }}) + .with_template_fn(|name| { + store + .get(name) + .ok_or_else(|| String::from("template not found")) + }) + .to_string()?; +assert_eq!(result, "Hello John Smith!\nGoodbye John Smith!"); +``` + [Handlebars]: https://crates.io/crates/handlebars [Tera]: https://crates.io/crates/tera [TinyTemplate]: https://crates.io/crates/tinytemplate @@ -194,14 +269,11 @@ at your option. [add_template]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.add_template -[compile]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.compile [engine]: https://docs.rs/upon/latest/upon/struct.Engine.html [engineadd_filter]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.add_filter [engineadd_formatter]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.add_formatter [filters]: https://docs.rs/upon/latest/upon/filters/index.html [fmt]: https://docs.rs/upon/latest/upon/fmt/index.html -[get_template]: https://docs.rs/upon/latest/upon/struct.Engine.html#method.get_template -[render]: https://docs.rs/upon/latest/upon/struct.TemplateRef.html#method.render [render_from]: https://docs.rs/upon/latest/upon/struct.TemplateRef.html#method.render_from [serde]: https://crates.io/crates/serde [stdiowrite]: https://doc.rust-lang.org/stable/std/io/trait.Write.html diff --git a/examples/README.md b/examples/README.md index b655b7d..6941ca1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,13 +6,10 @@ Examples can be run from anywhere in the repo using the following. cargo run --example ``` -- [quick](quick.rs): Demonstrates compiling and rendering a template in one line - of code. - - [serde](serde.rs): Demonstrates rendering a simple template using a custom global context that implements `serde::Serialize`. -- [syntax](syntax.rs): Demonstrates how to configure the template engine with +- [custom_syntax](custom_syntax.rs): Demonstrates how to configure the template engine with custom syntax delimiters. - [escape_html](escape_html.rs): Demonstrates how to configure a _value @@ -29,9 +26,6 @@ cargo run --example file loader. Files are are loaded at _runtime_ from the `templates/` directory and added to the engine. -- [render_to_writer](render_to_writer.rs): Demonstrates how to render directly - to a type implementing `std::io::Write` instead of to a string. - - [custom_template_store](custom_template_store.rs): Demonstrates how to implement a custom template store. This can allow for things like - relative template paths diff --git a/examples/syntax.rs b/examples/custom_syntax.rs similarity index 100% rename from examples/syntax.rs rename to examples/custom_syntax.rs diff --git a/examples/quick.rs b/examples/quick.rs deleted file mode 100644 index fb5bbef..0000000 --- a/examples/quick.rs +++ /dev/null @@ -1,12 +0,0 @@ -fn main() -> upon::Result<()> { - let engine = upon::Engine::new(); - - let out = engine - .compile("Hello {{ name }}!")? - .render(&engine, upon::value! { name: "World" }) - .to_string()?; - - println!("{out}"); - - Ok(()) -} diff --git a/examples/render_to_writer.rs b/examples/render_to_writer.rs deleted file mode 100644 index e17af5b..0000000 --- a/examples/render_to_writer.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::io; - -fn main() -> upon::Result<()> { - let mut stdout = io::BufWriter::new(io::stdout()); - - let engine = upon::Engine::new(); - - let ctx = upon::value! { user: { name: "John Smith" } }; - - engine - .compile("Hello {{ user.name }}!\n")? - .render(&engine, ctx) - .to_writer(&mut stdout)?; - - Ok(()) -} diff --git a/src/fmt.rs b/src/fmt.rs index 1ccc5d5..db14832 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -14,7 +14,8 @@ //! All formatter functions must have the following signature. //! //! ```text -//! Fn(&mut Formatter<'_>, &Value) -> Result; +//! use upon::{Value, fmt}; +//! Fn(&mut fmt::Formatter<'_>, &Value) -> fmt::Result; //! ``` //! //! Since [`Error`] implements `From` and `From<&str>` it is possible @@ -54,8 +55,8 @@ //! ### Error on [`Value::None`] //! //! The [`default`] value formatter formats [`Value::None`] as an empty string. -//! This example demonstrates how you can configure a default formatter to -//! instead error. +//! This example demonstrates how you can configure a default formatter to error +//! instead. //! //! ``` //! use std::fmt::Write; diff --git a/src/lib.rs b/src/lib.rs index 95b4b92..97f344f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,26 +78,16 @@ //! ``` //! //! Finally, the template is rendered by fetching it using -//! [`get_template`][Engine::get_template] and calling -//! [`render`][TemplateRef::render]. +//! [`template(..)`][Engine::template], calling +//! [`render(..)`][TemplateRef::render] and rendering to a string. //! //! ``` //! # let mut engine = upon::Engine::new(); //! # engine.add_template("hello", "Hello {{ user.name }}!")?; -//! let template = engine.get_template("hello").unwrap(); -//! let result = template.render(upon::value!{ user: { name: "John Smith" }}).to_string()?; -//! assert_eq!(result, "Hello John Smith!"); -//! # Ok::<(), upon::Error>(()) -//! ``` -//! -//! If the lifetime of the template source is shorter than the engine lifetime -//! or you don't need to store the compiled template then you can also use the -//! [`compile`][Engine::compile] function to return the template directly. -//! -//! ``` -//! # let engine = upon::Engine::new(); -//! let template = engine.compile("Hello {{ user.name }}!")?; -//! let result = template.render(&engine, upon::value!{ user: { name: "John Smith" }}).to_string()?; +//! let result = engine +//! .template("hello") +//! .render(upon::value!{ user: { name: "John Smith" }}) +//! .to_string()?; //! assert_eq!(result, "Hello John Smith!"); //! # Ok::<(), upon::Error>(()) //! ``` @@ -107,8 +97,9 @@ //! - The [`syntax`] module documentation outlines the template syntax. //! - The [`filters`] module documentation describes filters and how they work. //! - The [`fmt`] module documentation contains information on value formatters. -//! - The [`examples/`][examples] directory in the repository contains concrete -//! code examples. +//! - In addition to the examples in the current document, the +//! [`examples/`][examples] directory in the repository constains some more +//! concrete code examples. //! //! [examples]: https://github.com/rossmacarthur/upon/tree/trunk/examples //! @@ -141,6 +132,89 @@ //! [dependencies] //! upon = { version = "...", default-features = false, features = ["serde"] } //! ``` +//! +//! # Examples +//! +//! ## Nested templates +//! +//! You can include other templates by name using `{% include .. %}`. +//! +//! ``` +//! let mut engine = upon::Engine::new(); +//! engine.add_template("hello", "Hello {{ user.name }}!")?; +//! engine.add_template("goodbye", "Goodbye {{ user.name }}!")?; +//! engine.add_template("nested", "{% include \"hello\" %}\n{% include \"goodbye\" %}")?; +//! +//! let result = engine.template("nested") +//! .render(upon::value!{ user: { name: "John Smith" }}) +//! .to_string()?; +//! assert_eq!(result, "Hello John Smith!\nGoodbye John Smith!"); +//! # Ok::<(), upon::Error>(()) +//! ``` +//! +//! ## Render to writer +//! +//! Instead of rendering to a string it is possible to render the template to +//! any [`std::io::Write`] implementor using +//! [`to_writer(..)`][crate::Renderer::to_writer]. +//! +//! ``` +//! use std::io; +//! +//! let mut engine = upon::Engine::new(); +//! engine.add_template("hello", "Hello {{ user.name }}!")?; +//! +//! let mut stdout = io::BufWriter::new(io::stdout()); +//! engine +//! .template("hello") +//! .render(upon::value!{ user: { name: "John Smith" }}) +//! .to_writer(&mut stdout)?; +//! // Prints: Hello John Smith! +//! # Ok::<(), upon::Error>(()) +//! ``` +//! +//! ## Borrowed templates with short lifetimes +//! +//! If the lifetime of the template source is shorter than the engine lifetime +//! or you don't need to store the compiled template then you can also use the +//! [`compile(..)`][Engine::compile] function to return the template directly. +//! +//! ``` +//! # let engine = upon::Engine::new(); +//! let template = engine.compile("Hello {{ user.name }}!")?; +//! let result = template +//! .render(&engine, upon::value!{ user: { name: "John Smith" }}) +//! .to_string()?; +//! assert_eq!(result, "Hello John Smith!"); +//! # Ok::<(), upon::Error>(()) +//! ``` +//! +//! ## Custom template store and function +//! +//! The [`compile(..)`][Engine::compile] function can also be used in +//! conjunction with a custom template store which can allow for more advanced +//! use cases. For example: relative template paths or controlling template +//! access. +//! +//! ``` +//! # let engine = upon::Engine::new(); +//! let mut store = std::collections::HashMap::<&str, upon::Template>::new(); +//! store.insert("hello", engine.compile("Hello {{ user.name }}!")?); +//! store.insert("goodbye", engine.compile("Goodbye {{ user.name }}!")?); +//! store.insert("nested", engine.compile("{% include \"hello\" %}\n{% include \"goodbye\" %}")?); +//! +//! let result = store.get("nested") +//! .unwrap() +//! .render(&engine, upon::value!{ user: { name: "John Smith" }}) +//! .with_template_fn(|name| { +//! store +//! .get(name) +//! .ok_or_else(|| String::from("template not found")) +//! }) +//! .to_string()?; +//! assert_eq!(result, "Hello John Smith!\nGoodbye John Smith!"); +//! # Ok::<(), upon::Error>(()) +//! ``` #![deny(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -215,7 +289,9 @@ type ValueFn<'a> = dyn Fn(&[ValueMember]) -> std::result::Result /// [`render_from_fn`][Template::render_from_fn]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ValueMember<'a> { + /// The type of member access (direct or optional). pub op: ValueAccessOp, + /// The index or key being accessed. pub access: ValueAccess<'a>, } diff --git a/src/render/core.rs b/src/render/core.rs index 9e77cce..91da89e 100644 --- a/src/render/core.rs +++ b/src/render/core.rs @@ -9,7 +9,6 @@ use crate::types::program::{Instr, Template}; use crate::value::ValueCow; use crate::{EngineBoxFn, Error, Result}; -/// A renderer that interprets a compiled [`Template`]. #[cfg_attr(internal_debug, derive(Debug))] pub struct RendererImpl<'render, 'stack> { pub(crate) inner: RendererInner<'render>, diff --git a/src/render/mod.rs b/src/render/mod.rs index 4565a8e..e9f7118 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -35,7 +35,8 @@ where type TemplateFn<'a> = dyn FnMut(&str) -> std::result::Result<&'a crate::Template<'a>, String> + 'a; -/// A renderer that interprets a compiled [`Template`][crate::Template]. +/// A renderer that interprets a compiled [`Template`][crate::Template] or +/// [`TemplateRef`][crate::TemplateRef]. /// /// This struct is created by one of the following functions: /// - [`Template{,Ref}::render`][crate::Template::render] @@ -128,7 +129,10 @@ impl<'render> Renderer<'render> { /// Set a function that is called when a template is included. /// - /// This allows custom template resolution on a per render basis. + /// This allows custom template resolution on a per render basis. The + /// default is to look for the template with the exact matching name in the + /// engine, i.e. the same as + /// [`Engine::get_template`][crate::Engine::get_template]. pub fn with_template_fn(mut self, template_fn: F) -> Self where F: FnMut(&str) -> std::result::Result<&'render crate::Template<'render>, String> + 'render,