# human-string-filler 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*. ## Sample usage The **lowest-level** handling looks like this: ```rust 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`). 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`): ```rust 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. ## Cargo features - **std** (enabled by default): remove for `#![no_std]` operation. Implies *alloc*. - Implementation of `std::error::Error` for `Error`; - Implementation of `Filler` for `&HashMap`. - **alloc** (enabled by default via *std*): - Implementation of `Filler` for `&BTreeMap`. - `fill_to_string` and `StrExt::fill_to_string`. ## The template language This is the grammar of the template language in [ABNF](https://tools.ietf.org/html/rfc5234): ```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: ```text ^([^{}]|\{\{|\}\}|\{[^{}]*\})*$ ``` Sample legal template strings: - The empty string - `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.) ## Conventions on key semantics 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: 1. 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. 2. 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}`. 3. 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.) 4. Only use [UAX #31 identifiers](https://www.unicode.org/reports/tr31/) 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. ## Author [Chris Morgan](https://chrismorgan.info/) ([chris-morgan](https://gitlab.com/chris-morgan)) is the author and maintainer of human-string-filler. ## License Copyright © 2022 Chris Morgan This project is distributed under the terms of three different licenses, at your choice: - Blue Oak Model License 1.0.0: https://blueoakcouncil.org/license/1.0.0 - MIT License: https://opensource.org/licenses/MIT - Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0 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.