Crates.io | human-string-filler |
lib.rs | human-string-filler |
version | 1.0.0 |
source | src |
created_at | 2022-01-06 08:25:54.014375 |
updated_at | 2022-01-06 08:25:54.014375 |
description | A tiny template language for human-friendly string substitutions |
homepage | |
repository | https://gitlab.com/chris-morgan/human-string-filler |
max_upload_size | |
id | 508828 |
size | 41,470 |
A tiny template language for human-friendly string substitutions.
This crate is intended for situations where you need the user to be able to write simple templated strings, and conveniently evaluate them. It’s deliberately simple so that there are no surprises in its performance or functionality, and so that it’s not accidentally tied to Rust (e.g. you can readily implement it in a JavaScript-powered web app), which would happen if things like number formatting specifiers were included out of the box—instead, if you want that sort of thing, you’ll have to implement it yourself (don’t worry, it won’t be hard).
No logic is provided in this template language, only simple string formatting: {…}
template
regions get replaced in whatever way you decide, curly braces get escaped by doubling them
({{
and }}
), and that’s it.
The lowest-level handling looks like this:
use human_string_filler::{StrExt, SimpleFillerError};
let mut output = String::new();
"Hello, {name}!".fill_into(&mut output, |output: &mut String, key: &str| {
match key {
"name" => output.push_str("world"),
_ => return Err(SimpleFillerError::NoSuchKey),
}
Ok(())
}).unwrap();
assert_eq!(output, "Hello, world!");
template.fill_into(output, filler)
(provided by StrExt
) can also be spelled
fill(template, filler, output)
if you prefer a function to a method
(I reckon the method syntax is clearer, but opinions will differ so I provided both).
The filler function appends to the string directly for efficiency in case of computed values,
and returns Result<(), E>
; any error will become Err(Error::BadReplacement { error, .. })
on the fill call. (In this example I’ve used SimpleFillerError::NoSuchKey
, but ()
would
work almost as well, or you can write your own error type altogether.)
This example showed a closure that took &mut String
and used .push_str(…)
, but this crate
is not tied to String
in any way: for greater generality you would use a function generic
over a type that implements std::fmt::Write
, and use .write_str(…)?
inside (?
works there
because SimpleFillerError
implements From<std::fmt::Error>
).
At a higher level, you can use a string-string map as a filler, and you can also fill
directly to a String
with .fill_to_string()
(also available as a standalone function
fill_to_string
):
use std::collections::HashMap;
use human_string_filler::StrExt;
let mut map = HashMap::new();
map.insert("name", "world");
let s = "Hello, {name}!".fill_to_string(&map);
assert_eq!(s.unwrap(), "Hello, world!");
Or you can implement the Filler
trait for some other type of your own if you like.
std (enabled by default): remove for #![no_std]
operation. Implies alloc.
std::error::Error
for Error
;Filler
for &HashMap
.alloc (enabled by default via std):
Filler
for &BTreeMap
.fill_to_string
and StrExt::fill_to_string
.This is the grammar of the template language in ABNF:
unescaped-normal-char = %x00-7A / %x7C / %x7E-D7FF / %xE000-10FFFF
; any Unicode scalar value except for "{" and "}"
normal-char = unescaped-normal-char / "{{" / "}}"
template-region = "{" *unescaped-normal-char "}"
template-string = *( normal-char / template-region )
This regular expression will validate a template string:
^([^{}]|\{\{|\}\}|\{[^{}]*\})*$
Sample legal template strings:
Hello, {name}!
: one template region with key "name".Today is {date:short}
: one template region with key "date:short". (Although there’s no
format specification like with the format!()
macro, a colon convention is one reasonable
option—see the next section.)Hello, {}!
: one template region with an empty key, not recommended but allowed.Escaped {{ braces {and replacements} for {fun}!
: string "Escaped { braces ", followed by a
template region with key "and replacements", followed by string " for ", followed by a
template region with key "fun", followed by string "!".Sample illegal template strings:
hello, {world}foo}
: opening and closing curlies must match; any others (specifically, the
last character of the string) must be escaped by doubling.{{thing}
: the {{
is an escaped opening curly, so the }
is unmatched.{thi{{n}}g}
: no curlies of any form inside template region keys. (It’s possible that a
future version may make it possible to escape curlies inside template regions, if it proves
to be useful in something like format specifiers; but not at this time.)The key is an arbitrary string (except that it can’t contain {
or }
) with explicitly no
defined semantics, but here are some suggestions, including helper functions:
If it makes sense to have a format specifier (e.g. to specify a date format to use, or
whether to pad numbers with leading zeroes, &c.), split once on a character like :
.
To do this most conveniently, a function split_on
is provided.
For more advanced formatting where you have multiple properties you could wish to set,
split_propertied
offers some sound and similarly simple semantics for such strings as
{key prop1 prop2=val2}
and {key:prop1,prop2=val2}
.
If it makes sense to have nested property access, split on .
with the key.split('.')
iterator. (If you’re using split_on
or split_propertied
as mentioned above, you
probably want to apply them first to separate out the key part.)
Only use UAX #31 identifiers for the key (or keys, if supporting nested property access). Most of the time, empty strings and numbers are probably not a good idea.
With these suggestions, you might end up with the key foo.bar:baz
being interpreted as
retrieving the “bar” property from the “foo” object, and formatting it according to “baz”; or
aleph.beth.gimmel|alpha beta=5
as retrieving “gimmel” from “beth” of “aleph”, and formatting
it with properties “alpha” set to true and “beta” set to 5. What those things actually mean
is up to you to decide. I certainly haven’t a clue.
Chris Morgan (chris-morgan) is the author and maintainer of human-string-filler.
Copyright © 2022 Chris Morgan
This project is distributed under the terms of three different licenses, at your choice:
If you do not have particular cause to select the MIT or the Apache-2.0 license, Chris Morgan recommends that you select BlueOak-1.0.0, which is better and simpler than both MIT and Apache-2.0, which are only offered due to their greater recognition and their conventional use in the Rust ecosystem. (BlueOak-1.0.0 was only published in March 2019.)
When using this code, ensure you comply with the terms of at least one of these licenses.