cargo-binstall/crates/leon/src/template.rs
Jiahao XU e5567ae526
Fix clippy lint and format (#1328)
Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
2023-08-25 08:53:50 +00:00

278 lines
7.9 KiB
Rust

use std::{borrow::Cow, fmt::Display, io::Write, ops};
use crate::{ParseError, RenderError, Values};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Template<'s> {
pub items: Cow<'s, [Item<'s>]>,
pub default: Option<Cow<'s, str>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Item<'s> {
Text(&'s str),
Key(&'s str),
}
impl<'s> Template<'s> {
/// Construct a template with the given items and default.
///
/// You can write a template literal without any help by constructing it directly:
///
/// ```
/// use std::borrow::Cow;
/// use leon::{Item, Template};
/// const TEMPLATE: Template = Template {
/// items: Cow::Borrowed({
/// const ITEMS: &'static [Item<'static>] = &[
/// Item::Text("Hello"),
/// Item::Key("name"),
/// ];
/// ITEMS
/// }),
/// default: None,
/// };
/// assert_eq!(TEMPLATE.render(&[("name", "world")]).unwrap(), "Helloworld");
/// ```
///
/// As that's a bit verbose, using this function and the enum shorthands can be helpful:
///
/// ```
/// use leon::{Item, Item::*, Template};
/// const TEMPLATE: Template = Template::new({
/// const ITEMS: &'static [Item<'static>] = &[Text("Hello "), Key("name")];
/// ITEMS
/// }, Some("world"));
///
/// assert_eq!(TEMPLATE.render(&[("unrelated", "value")]).unwrap(), "Hello world");
/// ```
///
/// For an even more ergonomic syntax, see the [`leon::template!`](crate::template!) macro.
pub const fn new(items: &'s [Item<'s>], default: Option<&'s str>) -> Template<'s> {
Template {
items: Cow::Borrowed(items),
default: match default {
Some(default) => Some(Cow::Borrowed(default)),
None => None,
},
}
}
/// Parse a template from a string.
///
/// # Syntax
///
/// ```plain
/// it is better to rule { group }
/// one can live {adverb} without power
/// ```
///
/// A replacement is denoted by `{` and `}`. The contents of the braces, trimmed
/// of any whitespace, are the key. Any text outside of braces is left as-is.
///
/// To escape a brace, use `\{` or `\}`. To escape a backslash, use `\\`. Keys
/// cannot contain escapes.
///
/// ```plain
/// \{ leon \}
/// ```
///
/// The above examples, given the values `group = "no one"` and
/// `adverb = "honourably"`, would render to:
///
/// ```plain
/// it is better to rule no one
/// one can live honourably without power
/// { leon }
/// ```
///
/// # Example
///
/// ```
/// use leon::Template;
/// let template = Template::parse("hello {name}").unwrap();
/// ```
///
pub fn parse(s: &'s str) -> Result<Self, ParseError> {
Self::parse_items(s).map(|items| Template {
items: Cow::Owned(items),
default: None,
})
}
pub fn render_into(
&self,
writer: &mut dyn Write,
values: &dyn Values,
) -> Result<(), RenderError> {
for token in self.items.as_ref() {
match token {
Item::Text(text) => writer.write_all(text.as_bytes())?,
Item::Key(key) => {
if let Some(value) = values.get_value(key) {
writer.write_all(value.as_bytes())?;
} else if let Some(default) = &self.default {
writer.write_all(default.as_bytes())?;
} else {
return Err(RenderError::MissingKey(key.to_string()));
}
}
}
}
Ok(())
}
pub fn render(&self, values: &dyn Values) -> Result<String, RenderError> {
let mut buf = Vec::with_capacity(
self.items
.iter()
.map(|item| match item {
Item::Key(_) => 0,
Item::Text(t) => t.len(),
})
.sum(),
);
self.render_into(&mut buf, values)?;
// UNWRAP: We know that the buffer is valid UTF-8 because we only write strings.
Ok(String::from_utf8(buf).unwrap())
}
/// If the template contains key `key`.
pub fn has_key(&self, key: &str) -> bool {
self.has_any_of_keys(&[key])
}
/// If the template contains any one of the `keys`.
pub fn has_any_of_keys(&self, keys: &[&str]) -> bool {
self.items.iter().any(|token| match token {
Item::Key(k) => keys.contains(k),
_ => false,
})
}
/// Returns all keys in this template.
pub fn keys(&self) -> impl Iterator<Item = &&str> {
self.items.iter().filter_map(|token| match token {
Item::Key(k) => Some(k),
_ => None,
})
}
/// Sets the default value for this template.
pub fn set_default(&mut self, default: &dyn Display) {
self.default = Some(Cow::Owned(default.to_string()));
}
/// Cast `Template<'s>` to `Template<'t>` where `'s` is a subtype of `'t`,
/// meaning that `Template<'s>` outlives `Template<'t>`.
pub fn cast<'t>(self) -> Template<'t>
where
's: 't,
{
Template {
items: match self.items {
Cow::Owned(vec) => Cow::Owned(vec),
Cow::Borrowed(slice) => Cow::Borrowed(slice as &'t [Item<'t>]),
},
default: self.default.map(|default| default as Cow<'t, str>),
}
}
}
impl<'s, 'rhs: 's> ops::AddAssign<&Template<'rhs>> for Template<'s> {
fn add_assign(&mut self, rhs: &Template<'rhs>) {
self.items
.to_mut()
.extend(rhs.items.as_ref().iter().cloned());
if let Some(default) = &rhs.default {
self.default = Some(default.clone());
}
}
}
impl<'s, 'rhs: 's> ops::AddAssign<Template<'rhs>> for Template<'s> {
fn add_assign(&mut self, rhs: Template<'rhs>) {
match rhs.items {
Cow::Borrowed(items) => self.items.to_mut().extend(items.iter().cloned()),
Cow::Owned(items) => self.items.to_mut().extend(items),
}
if let Some(default) = rhs.default {
self.default = Some(default);
}
}
}
impl<'s, 'item: 's> ops::AddAssign<Item<'item>> for Template<'s> {
fn add_assign(&mut self, item: Item<'item>) {
self.items.to_mut().push(item);
}
}
impl<'s, 'item: 's> ops::AddAssign<&Item<'item>> for Template<'s> {
fn add_assign(&mut self, item: &Item<'item>) {
self.add_assign(item.clone())
}
}
impl<'s, 'rhs: 's> ops::Add<Template<'rhs>> for Template<'s> {
type Output = Self;
fn add(mut self, rhs: Template<'rhs>) -> Self::Output {
self += rhs;
self
}
}
impl<'s, 'rhs: 's> ops::Add<&Template<'rhs>> for Template<'s> {
type Output = Self;
fn add(mut self, rhs: &Template<'rhs>) -> Self::Output {
self += rhs;
self
}
}
impl<'s, 'item: 's> ops::Add<Item<'item>> for Template<'s> {
type Output = Self;
fn add(mut self, item: Item<'item>) -> Self::Output {
self += item;
self
}
}
impl<'s, 'item: 's> ops::Add<&Item<'item>> for Template<'s> {
type Output = Self;
fn add(mut self, item: &Item<'item>) -> Self::Output {
self += item;
self
}
}
#[cfg(test)]
mod test {
use crate::Template;
#[test]
fn concat_templates() {
let t1 = crate::template!("Hello", { "name" });
let t2 = crate::template!("have a", { "adjective" }, "day");
assert_eq!(
t1 + t2,
crate::template!("Hello", { "name" }, "have a", { "adjective" }, "day"),
);
}
#[test]
fn test_cast() {
fn inner<'a>(_: &'a u32, _: Template<'a>) {}
let template: Template<'static> = crate::template!("hello");
let i = 1;
inner(&i, template.cast());
}
}