1 unstable release
0.1.0 | May 16, 2023 |
---|
#539 in Template engine
15KB
281 lines
Overview
This crate defines a simple templating language in the format %(SELECTOR)
that
can operate on a structure. A given SELECTOR
accesses a given structure of
type T
via a closure Fn(&T) -> Option<String>
. Using a closure allows
complex logic to accessing the structure e.g. "if field x does not exist,
then try field y, otherwise return a default value or None
".
(This library is most definitely not production-ready)
Terminology
- A template is a string
%(X)
whereX
is a selector - A selector is any string (including the empty string) without a round, closing parenthesis
)
- The closure associated with a given selector is known as an accessor
- A format string is a string containing zero or more templates with other non-templating characters e.g.
%(id) - %(title)
Quick Start
In Cargo.toml
:
[dependencies]
struct_string_template = "0.1.0"
Say we have a struct
definition, and an instance of the struct
:
struct Book {
id: i64,
title: String,
author: String,
contributors: Option<String>
}
let my_book = Book {
id: 9784832275492,
title: "Hidamari Sketch".to_owned(),
author: "蒼樹うめ".to_owned(),
contributors: None,
};
Define a format string:
let format_string = "[%(id)] %(title) %(所有作者)";
Build a Templater<Book>
by doing:
use struct_string_template::TemplaterBuilder;
let templater = TemplaterBuilder::<Book>::new()
.with_selector("id", |book| Some(book.id.to_string()))
.with_selector("title", |book| Some(book.title.clone()))
.with_selector("所有作者", |book| {
Some(format!("(By: {}{})",
&book.author,
&book.contributors.clone().map(|x| format!(", {}", x)).or(Some("".to_owned())).unwrap())
)
})
.build();
Render it using Templater
's render
function
let result = templater.render(&my_book, format_string).ok().unwrap();
println!("{}", &result);
Advanced Use
Building Templater
without TemplateBuilder
If the Templater
needs to be built iteratively instead of using the builder
class, use Templater::new()
, then add closures using the insert
or extend
methods.
let mut templater = Templater::<Book>::new();
templater.insert("id", |book| Some(book.id.to_string()));
templater.insert("title", |book| Some(book.title.clone()));
templater.insert("所有作者", |book| {
Some(format!("(By: {}{})",
&book.author,
&book.contributors.clone().map(|x| format!(", {}", x)).or(Some("".to_owned())).unwrap())
)
});
Formatter
If you plan to use a format string many times, you can "precompile" it similar
to a regex for better performance using the Formatter
class (render
turns
the format string into a Formatter
internally), then pass the Formatter
variable into the renderf
function:
use struct_string_template::Formatter;
let formatter = Formatter::build(format_string).ok().unwrap();
let result = templater.renderf(&my_book, &formatter);
println!("{}", &result);
Error Handling
See src/err.rs
for the errors thrown.
Template Rules
In this section, X
is any selector
%%
is treated as a literal%
%
followed by a character (or end of string) that is not(
is invalid%(X)
is always valid%(X)A
whereA
is any valid string is valid%(X
(template is not terminated) is invalid- Having no closure associated with
X
is invalid - If the return value of the closure of
X
on the structure isNone
,NA
is printed. This is currently unconfigurable except by modifying the closure itself.
Limitations
Currently the Templater
object cannot be copied around due to the closure
types. I've not found a work-around for this yet.
Motivation
I use this library in a few personal applications of mine, and I've found it annoying to keep changes to the library in sync between them.
Dependencies
~0.6–1.1MB
~22K SLoC