1 #![cfg_attr(not(feature = "std"), no_std)]
3 #![cfg_attr(docsrs, feature(doc_cfg))]
4 //! A tiny template language for human-friendly string substitutions.
6 //! This crate is intended for situations where you need the user to be able to write simple
7 //! templated strings, and conveniently evaluate them. It’s deliberately simple so that there are
8 //! no surprises in its performance or functionality, and so that it’s not accidentally tied to
9 //! Rust (e.g. you can readily implement it in a JavaScript-powered web app), which would happen
10 //! if things like number formatting specifiers were included out of the box—instead, if you want
11 //! that sort of thing, you’ll have to implement it yourself (don’t worry, it won’t be hard).
13 //! No logic is provided in this template language, only simple string formatting: `{…}` template
14 //! regions get replaced in whatever way you decide, curly braces get escaped by doubling them
15 //! (`{{` and `}}`), and *that’s it*.
19 //! The **lowest-level** handling looks like this:
22 //! use human_string_filler::{StrExt, SimpleFillerError};
24 //! let mut output = String::new();
25 //! "Hello, {name}!".fill_into(&mut output, |output: &mut String, key: &str| {
27 //! "name" => output.push_str("world"),
28 //! _ => return Err(SimpleFillerError::NoSuchKey),
33 //! assert_eq!(output, "Hello, world!");
36 //! `template.fill_into(output, filler)` (provided by `StrExt`) can also be spelled
37 //! `fill(template, filler, output)` if you prefer a function to a method
38 //! (I reckon the method syntax is clearer, but opinions will differ so I provided both).
40 //! The filler function appends to the string directly for efficiency in case of computed values,
41 //! and returns `Result<(), E>`; any error will become `Err(Error::BadReplacement { error, .. })`
42 //! on the fill call. (In this example I’ve used `SimpleFillerError::NoSuchKey`, but `()` would
43 //! work almost as well, or you can write your own error type altogether.)
45 //! This example showed a closure that took `&mut String` and used `.push_str(…)`, but this crate
46 //! is not tied to `String` in any way: for greater generality you would use a function generic
47 //! over a type that implements `std::fmt::Write`, and use `.write_str(…)?` inside (`?` works there
48 //! because `SimpleFillerError` implements `From<std::fmt::Error>`).
50 //! At a **higher level**, you can use a string-string map as a filler, and you can also fill
51 //! directly to a `String` with `.fill_to_string()` (also available as a standalone function
52 //! `fill_to_string`):
55 //! # #[cfg(feature = "std")] {
56 //! use std::collections::HashMap;
57 //! use human_string_filler::StrExt;
59 //! let mut map = HashMap::new();
60 //! map.insert("name", "world");
62 //! let s = "Hello, {name}!".fill_to_string(&map);
64 //! assert_eq!(s.unwrap(), "Hello, world!");
68 //! Or you can implement the [`Filler`] trait for some other type of your own if you like.
75 - **std** (enabled by default, enabled in this build): remove for `#![no_std]` operation. \
82 - **std** (enabled by default, *disabled* in this build): remove for `#![no_std]` operation. \
86 //! - Implementation of `std::error::Error` for `Error`;
87 //! - Implementation of `Filler` for `&HashMap`.
92 - **alloc** (enabled by default via *std*, enabled in this build):\
96 not(feature
= "alloc"),
98 - **alloc** (enabled by default via *std*, disabled in this build):\
101 //! - Implementation of `Filler` for `&BTreeMap`.
102 //! - `fill_to_string` and `StrExt::fill_to_string`.
104 //! ## The template language
106 //! This is the grammar of the template language in [ABNF](https://tools.ietf.org/html/rfc5234):
109 //! unescaped-normal-char = %x00-7A / %x7C / %x7E-D7FF / %xE000-10FFFF
110 //! ; any Unicode scalar value except for "{" and "}"
112 //! normal-char = unescaped-normal-char / "{{" / "}}"
114 //! template-region = "{" *unescaped-normal-char "}"
116 //! template-string = *( normal-char / template-region )
119 //! This regular expression will validate a template string:
122 //! ^([^{}]|\{\{|\}\}|\{[^{}]*\})*$
125 //! Sample legal template strings:
127 //! - The empty string
128 //! - `Hello, {name}!`: one template region with key "name".
129 //! - `Today is {date:short}`: one template region with key "date:short". (Although there’s no
130 //! format specification like with the `format!()` macro, a colon convention is one reasonable
131 //! option—see the next section.)
132 //! - `Hello, {}!`: one template region with an empty key, not recommended but allowed.
133 //! - `Escaped {{ braces {and replacements} for {fun}!`: string "Escaped { braces ", followed by a
134 //! template region with key "and replacements", followed by string " for ", followed by a
135 //! template region with key "fun", followed by string "!".
137 //! Sample illegal template strings:
139 //! - `hello, {world}foo}`: opening and closing curlies must match; any others (specifically, the
140 //! last character of the string) must be escaped by doubling.
141 //! - `{{thing}`: the `{{` is an escaped opening curly, so the `}` is unmatched.
142 //! - `{thi{{n}}g}`: no curlies of any form inside template region keys. (It’s possible that a
143 //! future version may make it possible to escape curlies inside template regions, if it proves
144 //! to be useful in something like format specifiers; but not at this time.)
146 //! ## Conventions on key semantics
148 //! The key is an arbitrary string (except that it can’t contain `{` or `}`) with explicitly no
149 //! defined semantics, but here are some suggestions, including helper functions:
151 //! 1. If it makes sense to have a format specifier (e.g. to specify a date format to use, or
152 //! whether to pad numbers with leading zeroes, *&c.*), split once on a character like `:`.
153 //! To do this most conveniently, a function [`split_on`] is provided.
155 //! 2. For more advanced formatting where you have multiple properties you could wish to set,
156 //! [`split_propertied`] offers some sound and similarly simple semantics for such strings as
157 //! `{key prop1 prop2=val2}` and `{key:prop1,prop2=val2}`.
159 //! 3. If it makes sense to have nested property access, split on `.` with the `key.split('.')`
160 //! iterator. (If you’re using `split_on` or `split_propertied` as mentioned above, you
161 //! probably want to apply them first to separate out the key part.)
163 //! 4. Only use [UAX #31 identifiers](https://www.unicode.org/reports/tr31/) for the key
164 //! (or keys, if supporting nested property access). Most of the time, empty strings and
165 //! numbers are probably not a good idea.
167 //! With these suggestions, you might end up with the key `foo.bar:baz` being interpreted as
168 //! retrieving the “bar” property from the “foo” object, and formatting it according to “baz”; or
169 //! `aleph.beth.gimmel|alpha beta=5` as retrieving “gimmel” from “beth” of “aleph”, and formatting
170 //! it with properties “alpha” set to true and “beta” set to 5. What those things actually *mean*
171 //! is up to you to decide. *I* certainly haven’t a clue.
174 use core
::iter
::FusedIterator
;
175 use core
::ops
::Range
;
177 #[cfg(feature = "alloc")]
180 #[cfg(feature = "alloc")]
181 use alloc
::string
::String
;
183 #[cfg(feature = "alloc")]
184 use alloc
::collections
::BTreeMap
;
185 #[cfg(feature = "alloc")]
186 use core
::borrow
::Borrow
;
187 #[cfg(feature = "std")]
188 use std
::collections
::HashMap
;
189 #[cfg(feature = "std")]
192 /// Any error that occurs when filling a template string.
194 /// Template parsing and filling is all done in a single pass; so a failed replacement due to an
195 /// unknown key will shadow a syntax error later in the string.
196 #[derive(Debug, PartialEq, Eq)]
197 pub enum Error
<'a
, E
> {
198 /// A template region was not closed.
199 /// That is, an opening curly brace (`{`) with no matching closing curly brace (`}`).
204 /// # #[cfg(feature = "alloc")] {
205 /// # use human_string_filler::{StrExt, Error};
208 /// # .fill_to_string(|_: &mut String, _: &str| Result::<(), ()>::Ok(())),
209 /// # Err(Error::UnclosedRegion { source: "{thing", range: 7..13 }),
214 /// The text of the unclosed region, which will start with `{` and contain no other curly
217 /// The indexes of `source` within the template string.
221 /// An unescaped closing curly brace (`}`) was found, outside a template region.
226 /// # #[cfg(feature = "alloc")] {
227 /// # use human_string_filler::{StrExt, Error};
230 /// # .fill_to_string(|_: &mut String, _: &str| Result::<(), ()>::Ok(())),
231 /// # Err(Error::UnexpectedClosingBrace { index: 12 }),
234 /// "Hello, {name}, look at my magnificent moustache: (}-:"
235 /// # .fill_to_string(|_: &mut String, _: &str| Result::<(), ()>::Ok(())),
236 /// # Err(Error::UnexpectedClosingBrace { index: 50 }),
239 /// "Hello, {name}}!"
240 /// # .fill_to_string(|_: &mut String, _: &str| Result::<(), ()>::Ok(())),
241 /// # Err(Error::UnexpectedClosingBrace { index: 13 }),
245 UnexpectedClosingBrace
{
246 /// The index of the closing brace within the template string.
250 /// An opening curly brace (`{`) was found within a template region.
255 /// # #[cfg(feature = "alloc")] {
256 /// # use human_string_filler::{StrExt, Error};
258 /// "Hello, {thing{{sadness}}}"
259 /// # .fill_to_string(|_: &mut String, _: &str| Result::<(), ()>::Ok(())),
260 /// # Err(Error::UnexpectedOpeningBrace { index: 13 }),
264 UnexpectedOpeningBrace
{
265 /// The index of the opening brace within the template string.
269 /// The filler returned an error for the specified key.
271 /// The key on which the filler failed. Curly braces not included.
273 /// The indexes of `key` within the template string.
275 /// The error value returned by the filler.
279 /// Writing to the output failed.
280 WriteFailed(fmt
::Error
),
283 impl<'a
, E
> From
<fmt
::Error
> for Error
<'a
, E
> {
284 fn from(e
: fmt
::Error
) -> Self {
285 Error
::WriteFailed(e
)
289 impl<'a
, E
> fmt
::Display
for Error
<'a
, E
>
293 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
295 Error
::UnclosedRegion
{ source
, .. } => {
296 write!(f
, "Unclosed template region at \"{}\"", source
)
299 Error
::UnexpectedClosingBrace
{ index
} => {
300 write!(f
, "Unexpected closing brace at index {}", index
)
303 Error
::UnexpectedOpeningBrace
{ index
} => {
306 "Unexpected curly brace within template region at index {}",
311 Error
::BadReplacement
{ key
, error
, .. } => {
312 write!(f
, "Error in template string at \"{{{}}}\": {}", key
, error
)
315 Error
::WriteFailed(fmt
::Error
) => f
.write_str("Error in writing output"),
320 #[cfg(feature = "std")]
321 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
322 impl<'a
, E
> std
::error
::Error
for Error
<'a
, E
>
324 E
: std
::error
::Error
+ '
static,
326 fn source(&self) -> Option
<&(dyn std
::error
::Error
+ '
static)> {
328 Error
::BadReplacement
{ error
, .. } => Some(error
),
329 Error
::WriteFailed(error
) => Some(error
),
335 /// Implementers of this trait have the ability to fill template strings.
337 /// It is extremely strongly recommended that fillers only push to the output, and do not perform
338 /// any other modifications of it.
340 /// I mean, if you implement `Filler<String, _>`, you get a `&mut String` and it’s *possible* to do
341 /// other things with it, but that’s a terrible idea. I’m almost ashamed of ideas like making `{␡}`
342 /// pop the last character, and `{←rot13}` ROT-13-encode what precedes it in the string.
343 pub trait Filler
<W
, E
>
347 /// Fill the value for the given key into the output string.
348 fn fill(&mut self, output
: &mut W
, key
: &str) -> Result
<(), E
>;
351 impl<F
, W
, E
> Filler
<W
, E
> for F
353 F
: FnMut(&mut W
, &str) -> Result
<(), E
>,
356 fn fill(&mut self, output
: &mut W
, key
: &str) -> Result
<(), E
> {
361 #[cfg_attr(not(feature = "std"), allow(rustdoc::broken_intra_doc_links))]
362 /// A convenient error type for fillers; you might even like to use it yourself.
364 /// You could also use `()`, but this gives you
365 /// <code>[From](core::convert::From)<[core::fmt::Error]></code> so that you can use
366 /// `write!(out, …)?`, and sane [`core::fmt::Display`] and [`std::error::Error`] implementations.
367 #[derive(Clone, Debug, PartialEq, Eq)]
368 pub enum SimpleFillerError
{
369 /// The map didn’t contain the requested key.
371 /// Some fmt::Write operation returned an error.
372 WriteFailed(fmt
::Error
),
375 impl From
<fmt
::Error
> for SimpleFillerError
{
376 fn from(e
: fmt
::Error
) -> Self {
377 SimpleFillerError
::WriteFailed(e
)
381 impl fmt
::Display
for SimpleFillerError
{
382 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
384 SimpleFillerError
::NoSuchKey
=> f
.write_str("no such key"),
385 SimpleFillerError
::WriteFailed(fmt
::Error
) => f
.write_str("write failed"),
390 #[cfg(feature = "std")]
391 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
392 impl std
::error
::Error
for SimpleFillerError
{
393 fn source(&self) -> Option
<&(dyn std
::error
::Error
+ '
static)> {
395 SimpleFillerError
::WriteFailed(error
) => Some(error
),
401 #[cfg(feature = "std")]
402 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
403 impl<K
, V
, W
> Filler
<W
, SimpleFillerError
> for &HashMap
<K
, V
>
405 K
: Borrow
<str> + Eq
+ Hash
,
409 fn fill(&mut self, output
: &mut W
, key
: &str) -> Result
<(), SimpleFillerError
> {
411 .ok_or(SimpleFillerError
::NoSuchKey
)
412 .and_then(|value
| output
.write_str(value
.as_ref()).map_err(Into
::into
))
416 #[cfg(feature = "alloc")]
417 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
418 impl<K
, V
, W
> Filler
<W
, SimpleFillerError
> for &BTreeMap
<K
, V
>
420 K
: Borrow
<str> + Ord
,
424 fn fill(&mut self, output
: &mut W
, key
: &str) -> Result
<(), SimpleFillerError
> {
426 .ok_or(SimpleFillerError
::NoSuchKey
)
427 .and_then(|value
| output
.write_str(value
.as_ref()).map_err(Into
::into
))
431 /// String extension methods for the template string.
433 /// This is generally how I recommend using this library, because I find that the method receiver
434 /// makes code clearer: that `template.fill_into(output, filler)` is easier to understand than
435 /// `fill(template, filler, output)`.
437 /// Fill this template, producing a new string.
439 /// This is a convenience method for ergonomics in the case where you aren’t fussed about
440 /// allocations and are using the standard `String` type.
442 #[cfg_attr(feature = "std", doc = " Example, using a hash map:")]
444 not(feature
= "std"),
445 doc
= " Example, using a hash map (requires the *std* feature):"
449 /// # #[cfg(feature = "std")] {
450 /// # use human_string_filler::StrExt;
451 /// # use std::collections::HashMap;
452 /// let map = [("name", "world")].into_iter().collect::<HashMap<_, _>>();
454 /// "Hello, {name}!".fill_to_string(&map).unwrap(),
459 #[cfg(feature = "alloc")]
460 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
461 fn fill_to_string
<F
, E
>(&self, filler
: F
) -> Result
<String
, Error
<E
>>
463 F
: Filler
<String
, E
>,
465 let mut out
= String
::new();
466 self.fill_into(&mut out
, filler
).map(|()| out
)
469 /// Fill this template string into the provided string, with the provided filler.
471 /// Uses an existing string, which is more efficient if you want to push to an existing string
472 /// or can reuse a string allocation.
474 /// Example, using a closure:
477 /// # use human_string_filler::StrExt;
478 /// let filler = |output: &mut String, key: &str| {
480 /// "name" => output.push_str("world"),
481 /// _ => return Err(()),
485 /// let mut string = String::new();
486 /// assert!("Hello, {name}!".fill_into(&mut string, filler).is_ok());
487 /// assert_eq!(string, "Hello, world!");
489 fn fill_into
<F
, W
, E
>(&self, output
: &mut W
, filler
: F
) -> Result
<(), Error
<E
>>
495 impl StrExt
for str {
497 fn fill_into
<F
, W
, E
>(&self, output
: &mut W
, filler
: F
) -> Result
<(), Error
<E
>>
502 fill(self, filler
, output
)
506 /// The lowest-level form, as a function: fill the template string, into a provided writer.
508 /// This is the most efficient form. It splits a string by `{…}` sections, adding anything outside
509 /// them to the output string (with escaped curlies dedoubled) and passing template regions through
510 /// the filler, which handles pushing to the output string itself.
512 /// See also [`StrExt::fill_into`] which respells `fill(template, filler, output)` as
513 /// `template.fill_into(output, filler)`.
514 pub fn fill
<'a
, F
, W
, E
>(
515 mut template
: &'a
str,
518 ) -> Result
<(), Error
<'a
, E
>>
525 if let Some(i
) = template
.find(|c
| c
== '
{'
|| c
== '
}'
) {
526 #[allow(clippy::wildcard_in_or_patterns)]
527 match template
.as_bytes()[i
] {
528 c @
b'}' | c @
b'{' if template
.as_bytes().get(i
+ 1) == Some(&c
) => {
529 output
.write_str(&template
[0..i
+ 1])?
;
530 template
= &template
[i
+ 2..];
533 b'}' => return Err(Error
::UnexpectedClosingBrace
{ index
: index
+ i
}),
535 // (_ here just to lazily skip an unreachable!().)
536 output
.write_str(&template
[0..i
])?
;
537 template
= &template
[i
..];
539 if let Some(i
) = template
[1..].find(|c
| c
== '
{'
|| c
== '
}'
) {
540 match template
.as_bytes()[i
+ 1] {
542 if let Err(e
) = filler
.fill(output
, &template
[1..i
+ 1]) {
543 return Err(Error
::BadReplacement
{
544 key
: &template
[1..i
+ 1],
545 range
: (index
+ 1)..(index
+ i
+ 1),
549 template
= &template
[i
+ 2..];
552 // (Again, _ is unreachable.)
554 return Err(Error
::UnexpectedOpeningBrace
{
555 index
: index
+ i
+ 1,
560 return Err(Error
::UnclosedRegion
{
562 range
: index
..(index
+ template
.len()),
568 output
.write_str(template
)?
;
576 /// Fill a template, producing a new string.
578 /// This is a convenience function for ergonomics in the case where you aren’t fussed about
579 /// allocations and are using the standard `String` type.
581 /// See also [`StrExt::fill_to_string`], which respells `fill_to_string(template, filler)` as
582 /// `template.fill_to_string(filler)`.
583 #[cfg(feature = "alloc")]
584 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
585 pub fn fill_to_string
<F
, E
>(template
: &str, filler
: F
) -> Result
<String
, Error
<E
>>
587 F
: Filler
<String
, E
>,
589 let mut out
= String
::new();
590 fill(template
, filler
, &mut out
).map(|()| out
)
593 /// A convenience function to split a string on a character.
595 /// This is nicer than using `string.split(c, 2)` because it gives you the two values up-front.
601 /// 1. What comes before the split character, or the entire string if there was none; and
602 /// 2. The remainder after the split character, if there was one (even if it’s empty).
605 /// # use human_string_filler::split_on;
606 /// assert_eq!(split_on("The quick brown fox", ':'), ("The quick brown fox", None));
607 /// assert_eq!(split_on("/", '/'), ("", Some("")));
608 /// assert_eq!(split_on("harum = scarum", '='), ("harum ", Some(" scarum")));
609 /// assert_eq!(split_on("diæresis:tréma:umlaut", ':'), ("diæresis", Some("tréma:umlaut")));
611 pub fn split_on(string
: &str, c
: char) -> (&str, Option
<&str>) {
612 match string
.find(c
) {
613 Some(i
) => (&string
[..i
], Some(&string
[i
+ c
.len_utf8()..])),
614 None
=> (string
, None
),
618 /// The separators to use in [`split_propertied`].
620 /// A couple of sets of plausible-looking values (but if you want a concrete recommendation, like
621 /// Gallio of old I refuse to be a judge of these things):
623 /// - `(' ', ' ', '=')` looks like `Hello, {name first formal=false case=lower}!`.
624 /// - `('|', ',', ':')` looks like `Hello, {name|first,formal:false,case:lower}!`.
625 #[derive(Clone, Copy, Debug)]
626 pub struct Separators
{
627 /// What character indicates the end of the key and the start of the properties.
628 pub between_key_and_properties
: char,
630 /// What character indicates the end of one property’s name or value and the start of the next
632 pub between_properties
: char,
634 /// What character indicates the end of a property’s name and the start of its value.
635 /// Remember that properties aren’t required to have values, but can be booleanyish.
636 // “booleanyish” sounded better than “booleanishy”. That’s my story and I’m sticking with it.
637 /// For that matter, if you want *all* properties to be boolean, set this to the same value as
638 /// `between_properties`, because `between_properties` is greedier.
639 pub between_property_name_and_value
: char,
642 /// A convenience function to split a key that is followed by properties.
644 /// In keeping with this library in general, this is deliberately very simple and consequently not
645 /// able to express all possible values; for example, if you use space as the separator between
646 /// properties, you can’t use space in property values; and this doesn’t guard against empty keys
647 /// or property names in any way.
650 /// use human_string_filler::{Separators, split_propertied};
652 /// let (key, properties) = split_propertied("key:prop1,prop2=value2,prop3=4+5=9", Separators {
653 /// between_key_and_properties: ':',
654 /// between_properties: ',',
655 /// between_property_name_and_value: '=',
658 /// assert_eq!(key, "key");
659 /// assert_eq!(properties.collect::<Vec<_>>(),
660 /// vec![("prop1", None), ("prop2", Some("value2")), ("prop3", Some("4+5=9"))]);
663 /// This method consumes exactly one character for the separators; if space is your
664 /// between-properties separator, for example, multiple spaces will not be combined, but
665 /// you’ll get `("", None)` properties instead. As I say, this is deliberately simple.
666 pub fn split_propertied(
668 separators
: Separators
,
671 impl Iterator
<Item
= (&str, Option
<&str>)>
672 + DoubleEndedIterator
677 let (key
, properties
) = split_on(s
, separators
.between_key_and_properties
);
678 let properties
= properties
679 .map(|properties
| properties
.split(separators
.between_properties
))
681 // We need an iterator of the same type that will yield None, but Split yields an empty
682 // string first. Nice and easy: consume that, then continue on our way.
683 let mut dummy
= "".split(' '
);
687 .map(move |word
| split_on(word
, separators
.between_property_name_and_value
));
693 #[allow(unused_imports)]
696 #[cfg(feature = "alloc")]
698 ($name
:ident
, $filler
:expr
) => {
701 let filler
= $filler
;
704 "Hello, {}!".fill_to_string(&filler
).as_ref().map(|s
| &**s
),
705 Ok("Hello, (this space intentionally left blank)!"),
709 .fill_to_string(&filler
)
715 "Hello, {you}!".fill_to_string(&filler
),
716 Err(Error
::BadReplacement
{
719 error
: SimpleFillerError
::NoSuchKey
,
723 "I like {keys with SPACES!? 😱}"
724 .fill_to_string(&filler
)
727 Ok("I like identifier-only keys 👌"),
733 #[cfg(feature = "alloc")]
734 test!(closure_filler
, |out
: &mut String
, key
: &str| {
735 use core
::fmt
::Write
;
736 out
.write_str(match key
{
737 "" => "(this space intentionally left blank)",
739 "keys with SPACES!? 😱" => "identifier-only keys 👌",
740 _
=> return Err(SimpleFillerError
::NoSuchKey
),
745 #[cfg(feature = "std")]
746 test!(hash_map_fillter
, {
748 ("", "(this space intentionally left blank)"),
750 ("keys with SPACES!? 😱", "identifier-only keys 👌"),
753 .collect
::<HashMap
<_
, _
>>()
756 #[cfg(feature = "alloc")]
757 test!(btree_map_fillter
, {
759 ("", "(this space intentionally left blank)"),
761 ("keys with SPACES!? 😱", "identifier-only keys 👌"),
764 .collect
::<BTreeMap
<_
, _
>>()
768 #[cfg(feature = "alloc")]
770 let c
= |_
: &mut String
, _
: &str| -> Result
<(), ()> { Ok(()) };
773 fill_to_string("Hello, {thing", c
),
774 Err(Error
::UnclosedRegion
{
780 fill_to_string("{}/{x}/{xx}/{xxx}/{{/}}/{thing", c
),
781 Err(Error
::UnclosedRegion
{
788 fill_to_string("Hello, }thing", c
),
789 Err(Error
::UnexpectedClosingBrace
{ index
: 7 })
792 fill_to_string("{}/{x}/{xx}/{xxx}/{{/}}/}thing", c
),
793 Err(Error
::UnexpectedClosingBrace
{ index
: 24 })
797 fill_to_string("Hello, {thi{{ng}", c
),
798 Err(Error
::UnexpectedOpeningBrace
{ index
: 11 })
801 fill_to_string("{}/{x}/{xx}/{xxx}/{{/}}/{x{", c
),
802 Err(Error
::UnexpectedOpeningBrace
{ index
: 26 })
806 fill_to_string("Hello, {thi}}ng}", c
),
807 Err(Error
::UnexpectedClosingBrace
{ index
: 12 })
810 fill_to_string("{}/{x}/{xx}/{xxx}/{{/}}/}", c
),
811 Err(Error
::UnexpectedClosingBrace
{ index
: 24 })
815 // This is almost enough to make me only expose a dyn fmt::Writer.
817 #[cfg(feature = "alloc")]
818 fn do_not_do_this_at_home_kids() {
819 // Whatever possessed me!?
820 let s
= "Don’t{␡}{\b}{^H} do this at home, {who}!".fill_to_string(
821 |output
: &mut String
, key
: &str| {
823 "␡" | "\b" | "^H" => {
827 output
.push_str("kids");
834 assert_eq!(s
.unwrap(), "Do do this at home, kids!");
836 // I haven’t yet decided whether this is better or worse than the previous one.
837 let s
= "Don’t yell at {who}!{←make ASCII uppercase} (Please.)".fill_to_string(
838 |output
: &mut String
, key
: &str| {
840 "←make ASCII uppercase" => {
841 output
.make_ascii_uppercase();
844 output
.push_str("me");
851 assert_eq!(s
.unwrap(), "DON’T YELL AT ME! (Please.)");