#
(i.e. `#(({}))#`).
It is also possible to only iterate a sub-slice specified through a range
before the format string, i.e. `{list:i1..4}`. For open ranges range
bounds can also be omitted. To index from the end, you can use negative
range bounds.
It is also possible to index a single value by only specifying an [`isize`]
`{list:i1}`.
A [`Formattable`] implementing iter is created using [`Formattable::iter`]:
```
// HashMap macro
use collection_literals::hash;
use interpolator::{format, Formattable};
// Needs to be a slice of references because `Formattable::display` expects a
// reference
let items = [&"hello", &"hi", &"hey"].map(Formattable::display);
let items = Formattable::iter(&items);
let format_str = "Greetings: {items:i..-1(`{}{outside}`)(, )} and `{items:i-1}{outside}`";
assert_eq!(
format(format_str, &hash!("items" => items, "outside" => Formattable::display(&'!')))?,
"Greetings: `hello!`, `hi!` and `hey!`"
);
# return Ok::<(), interpolator::Error>(())
```"#
)]
#![cfg_attr(
feature = "iter",
doc = r"- [`list!`] creates a [`Formattable`] implementing supporting [iter](`i`-iter-format)."
)]
#![warn(clippy::pedantic, missing_docs)]
#![allow(
clippy::wildcard_imports,
clippy::implicit_hasher,
clippy::enum_glob_use,
clippy::module_name_repetitions
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::borrow::Borrow;
use std::collections::HashMap;
use std::error::Error as StdError;
#[cfg(feature = "pointer")]
use std::fmt::Pointer;
#[cfg(feature = "number")]
use std::fmt::{Binary, LowerExp, LowerHex, Octal, UpperExp, UpperHex};
use std::fmt::{Debug, Display, Error as FmtError, Write};
use std::hash::Hash;
use std::num::ParseIntError;
#[macro_use]
mod error;
pub use error::*;
mod fmt;
use fmt::format_value;
mod formattable;
pub use formattable::*;
mod parser;
use parser::*;
mod macros;
type Result<T = (), E = Error> = std::result::Result<T, E>;
pub fn format(format: &str, context: &impl Context) -> Result<String> {
let mut out = String::with_capacity(format.len());
write(&mut out, format, context)?;
Ok(out)
}
pub trait Context {
fn get(&self, key: &str) -> Option<Formattable>;
}
impl<K: Borrow<str> + Eq + Hash> Context for HashMap<K, Formattable<'_>> {
fn get(&self, key: &str) -> Option<Formattable> {
HashMap::get(self, key).copied()
}
}
#[cfg(feature = "iter")]
struct IterContext<'a> {
outer: &'a dyn Context,
inner: Formattable<'a>,
}
#[cfg(feature = "iter")]
impl<'a> IterContext<'a> {
fn new(outer: &'a impl Context, inner: Formattable<'a>) -> Self {
Self { outer, inner }
}
}
#[cfg(feature = "iter")]
impl<'a> Context for IterContext<'a> {
fn get(&self, key: &str) -> Option<Formattable> {
if key.is_empty() {
Some(self.inner)
} else {
self.outer.get(key)
}
}
}
pub fn write(out: &mut impl Write, mut format: &str, context: &impl Context) -> Result {
let format = &mut format;
let idx = &mut 0;
while !format.is_empty() {
if format.starts_with("{{") || format.starts_with("}}") {
out.write_str(&format[..1])
.map_err(|e| Error::Fmt(e, *idx))?;
step(2, format, idx);
continue;
}
if format.starts_with('{') {
step(1, format, idx);
let start = *idx;
let FormatArgument {
ident,
alignment,
sign,
hash,
zero,
width,
precision,
trait_,
} = FormatArgument::from_str(format, idx)?;
let value = context
.get(ident)
.ok_or(Error::MissingValue(ident.to_string(), start))?;
format_value(
out,
value.borrow(),
width,
precision,
alignment,
sign,
hash,
zero,
trait_,
*idx,
context,
)?;
ensure!(format.starts_with('}'), ParseError::Expected("}", *idx));
step(1, format, idx);
continue;
}
let next = format
.chars()
.next()
.expect("should contain a char if not empty");
out.write_char(next).map_err(|e| Error::Fmt(e, *idx))?;
step(next.len_utf8(), format, idx);
}
Ok(())
}