3 A tiny template language for human-friendly string substitutions.
5 This crate is intended for situations where you need the user to be able to write simple
6 templated strings, and conveniently evaluate them. It’s deliberately simple so that there are
7 no surprises in its performance or functionality, and so that it’s not accidentally tied to
8 Rust (e.g. you can readily implement it in a JavaScript-powered web app), which would happen
9 if things like number formatting specifiers were included out of the box—instead, if you want
10 that sort of thing, you’ll have to implement it yourself (don’t worry, it won’t be hard).
12 No logic is provided in this template language, only simple string formatting:
`{…}` template
13 regions get replaced in whatever way you decide, curly braces get escaped by doubling them
14 (
`{{` and
`}}`), and *that’s it*.
18 The **lowest-level** handling looks like this:
21 use human_string_filler::{StrExt, SimpleFillerError};
23 let mut output = String::new();
24 "Hello, {name}!".fill_into(&mut output, |output: &mut String, key: &str| {
26 "name" => output.push_str("world"),
27 _ => return Err(SimpleFillerError::NoSuchKey),
32 assert_eq!(output, "Hello, world!");
35 `template.fill_into(output, filler)` (provided by
`StrExt`) can also be spelled
36 `fill(template, filler, output)` if you prefer a function to a method
37 (I reckon the method syntax is clearer, but opinions will differ so I provided both).
39 The filler function appends to the string directly for efficiency in case of computed values,
40 and returns
`Result<(), E>`; any error will become
`Err(Error::BadReplacement { error, .. })`
41 on the fill call. (In this example I’ve used
`SimpleFillerError::NoSuchKey`, but
`()` would
42 work almost as well, or you can write your own error type altogether.)
44 This example showed a closure that took
`&mut String` and used
`.push_str(…)`, but this crate
45 is not tied to
`String` in any way: for greater generality you would use a function generic
46 over a type that implements
`std::fmt::Write`, and use
`.write_str(…)?` inside (
`?` works there
47 because
`SimpleFillerError` implements
`From<std::fmt::Error>`).
49 At a **higher level**, you can use a string-string map as a filler, and you can also fill
50 directly to a
`String` with
`.fill_to_string()` (also available as a standalone function
54 use std::collections::HashMap;
55 use human_string_filler::StrExt;
57 let mut map = HashMap::new();
58 map.insert("name", "world");
60 let s = "Hello, {name}!".fill_to_string(&map);
62 assert_eq!(s.unwrap(), "Hello, world!");
65 Or you can implement the
`Filler` trait for some other type of your own if you like.
69 - **std** (enabled by default): remove for
`#![no_std]` operation. Implies *alloc*.
70 - Implementation of
`std::error::Error` for
`Error`;
71 - Implementation of
`Filler` for
`&HashMap`.
73 - **alloc** (enabled by default via *std*):
74 - Implementation of
`Filler` for
`&BTreeMap`.
75 - `fill_to_string` and
`StrExt::fill_to_string`.
77 ## The template language
79 This is the grammar of the template language in
[ABNF](https://tools.ietf.org/html/rfc5234):
82 unescaped-normal-char = %x00-7A / %x7C / %x7E-D7FF / %xE000-10FFFF
83 ; any Unicode scalar value except for "{" and "}"
85 normal-char = unescaped-normal-char / "{{" / "}}"
87 template-region = "{" *unescaped-normal-char "}"
89 template-string = *( normal-char / template-region )
92 This regular expression will validate a template string:
95 ^([^{}]|\{\{|\}\}|\{[^{}]*\})*$
98 Sample legal template strings:
101 - `Hello, {name}!`: one template region with key "name".
102 - `Today is {date:short}`: one template region with key "date:short". (Although there’s no
103 format specification like with the
`format!()` macro, a colon convention is one reasonable
104 option—see the next section.)
105 - `Hello, {}!`: one template region with an empty key, not recommended but allowed.
106 - `Escaped {{ braces {and replacements} for {fun}!`: string "Escaped { braces ", followed by a
107 template region with key "and replacements", followed by string " for ", followed by a
108 template region with key "fun", followed by string "!".
110 Sample illegal template strings:
112 - `hello, {world}foo}`: opening and closing curlies must match; any others (specifically, the
113 last character of the string) must be escaped by doubling.
114 - `{{thing}`: the
`{{` is an escaped opening curly, so the
`}` is unmatched.
115 - `{thi{{n}}g}`: no curlies of any form inside template region keys. (It’s possible that a
116 future version may make it possible to escape curlies inside template regions, if it proves
117 to be useful in something like format specifiers; but not at this time.)
119 ## Conventions on key semantics
121 The key is an arbitrary string (except that it can’t contain
`{` or
`}`) with explicitly no
122 defined semantics, but here are some suggestions, including helper functions:
124 1. If it makes sense to have a format specifier (e.g. to specify a date format to use, or
125 whether to pad numbers with leading zeroes, *&c.*), split once on a character like
`:`.
126 To do this most conveniently, a function
`split_on` is provided.
128 2. For more advanced formatting where you have multiple properties you could wish to set,
129 `split_propertied` offers some sound and similarly simple semantics for such strings as
130 `{key prop1 prop2=val2}` and
`{key:prop1,prop2=val2}`.
132 3. If it makes sense to have nested property access, split on
`.` with the
`key.split('.')`
133 iterator. (If you’re using
`split_on` or
`split_propertied` as mentioned above, you
134 probably want to apply them first to separate out the key part.)
136 4. Only use
[UAX #31 identifiers](https://www.unicode.org/reports/tr31/) for the key
137 (or keys, if supporting nested property access). Most of the time, empty strings and
138 numbers are probably not a good idea.
140 With these suggestions, you might end up with the key
`foo.bar:baz` being interpreted as
141 retrieving the “bar” property from the “foo” object, and formatting it according to “baz”; or
142 `aleph.beth.gimmel|alpha beta=5` as retrieving “gimmel” from “beth” of “aleph”, and formatting
143 it with properties “alpha” set to true and “beta” set to
5. What those things actually *mean*
144 is up to you to decide. *I* certainly haven’t a clue.
148 [Chris Morgan](https://chrismorgan.info/)
149 (
[chris-morgan](https://gitlab.com/chris-morgan))
150 is the author and maintainer of human-string-filler.
154 Copyright ©
2022 Chris Morgan
156 This project is distributed under the terms of three different licenses,
159 - Blue Oak Model License
1.0.0: https://blueoakcouncil.org/license/
1.0.0
160 - MIT License: https://opensource.org/licenses/MIT
161 - Apache License, Version
2.0: https://www.apache.org/licenses/LICENSE-
2.0
163 If you do not have particular cause to select the MIT or the Apache-
2.0
164 license, Chris Morgan recommends that you select BlueOak-
1.0.0, which is
165 better and simpler than both MIT and Apache-
2.0, which are only offered
166 due to their greater recognition and their conventional use in the Rust
167 ecosystem. (BlueOak-
1.0.0 was only published in March
2019.)
169 When using this code, ensure you comply with the terms of at least one of