// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. //! Tools for lazily evaluating templates that produce text in a fallible//! manner. use std::cell::RefCell;use std::error;use std::fmt;use std::io;use std::io::Write;use std::iter;use std::rc::Rc; use bstr::BStr;use bstr::BString;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::config::ConfigValue;use jj_lib::op_store::TimestampRange; use crate::formatter::FormatRecorder;use crate::formatter::Formatter;use crate::formatter::FormatterExt as _;use crate::formatter::LabeledScope;use crate::formatter::PlainTextFormatter;use crate::text_util;use crate::time_util; /// Represents a printable type or a compiled template containing a placeholder/// value.////// This is analogous to [`std::fmt::Display`], but with customized error/// handling.pub trait Template { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()>;} impl<T: Template + ?Sized> Template for &T { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { <T as Template>::format(self, formatter) }} impl<T: Template + ?Sized> Template for Box<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { <T as Template>::format(self, formatter) }} // All optional printable types should be printable, and it's unlikely to// implement different formatting per type.impl<T: Template> Template for Option<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { self.as_ref().map_or(Ok(()), |t| t.format(formatter)) }} impl Template for BString { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { formatter.as_mut().write_all(self) }} impl Template for &BStr { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { formatter.as_mut().write_all(self) }} impl Template for ConfigValue { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") }} impl Template for Signature { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter.labeled("name"), "{}", self.name)?; if !self.name.is_empty() && !self.email.is_empty() { write!(formatter, " ")?; } if !self.email.is_empty() { write!(formatter, "<")?; let email = Email(self.email.clone()); email.format(formatter)?; write!(formatter, ">")?; } Ok(()) }} #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]#[serde(transparent)]pub struct Email(pub String); impl Template for Email { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let (local, domain) = text_util::split_email(&self.0); write!(formatter.labeled("local"), "{local}")?; if let Some(domain) = domain { write!(formatter, "@")?; write!(formatter.labeled("domain"), "{domain}")?; } Ok(()) }} // In template language, an integer value is represented as i64. However, we use// usize here because it's more convenient to guarantee that the lower value is// bounded to 0.pub type SizeHint = (usize, Option<usize>); impl Template for String { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") }} impl Template for &str { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") }} impl Template for Timestamp { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { match time_util::format_absolute_timestamp(self) { Ok(formatted) => write!(formatter, "{formatted}"), Err(err) => formatter.handle_error(err.into()), } }} impl Template for TimestampRange { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { self.start.format(formatter)?; write!(formatter, " - ")?; self.end.format(formatter)?; Ok(()) }} impl Template for Vec<String> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, self, " ") }} impl Template for bool { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let repr = if *self { "true" } else { "false" }; write!(formatter, "{repr}") }} impl Template for i64 { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") }} pub struct LabelTemplate<T, L> { content: T, labels: L,} impl<T, L> LabelTemplate<T, L> { pub fn new(content: T, labels: L) -> Self where T: Template, L: TemplateProperty<Output = Vec<String>>, { Self { content, labels } }} impl<T, L> Template for LabelTemplate<T, L>where T: Template, L: TemplateProperty<Output = Vec<String>>,{ fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { match self.labels.extract() { Ok(labels) => format_labeled(formatter, &self.content, &labels), Err(err) => formatter.handle_error(err), } }} pub struct RawEscapeSequenceTemplate<T>(pub T); impl<T: Template> Template for RawEscapeSequenceTemplate<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let rewrap = formatter.rewrap_fn(); let mut raw_formatter = PlainTextFormatter::new(formatter.raw()?); self.0.format(&mut rewrap(&mut raw_formatter)) }} /// Renders a hyperlink using OSC 8 escape sequences when supported, or a/// fallback otherwise.pub struct HyperlinkTemplate<U, T, F> { url: U, text: T, fallback: Option<F>,} impl<U, T, F> HyperlinkTemplate<U, T, F> { pub fn new(url: U, text: T, fallback: Option<F>) -> Self { Self { url, text, fallback, } }} impl<U, T, F> Template for HyperlinkTemplate<U, T, F>where U: TemplateProperty<Output = String>, T: Template, F: Template,{ fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { // Extract URL string let url_str = match self.url.extract() { Ok(url) => url, Err(err) => return formatter.handle_error(err), }; if !formatter.maybe_color() { if let Some(fallback) = &self.fallback { return fallback.format(formatter); } return self.text.format(formatter); } // Write OSC 8 hyperlink via raw() write!(formatter.raw()?, "\x1b]8;;{url_str}\x1b\\")?; self.text.format(formatter)?; write!(formatter.raw()?, "\x1b]8;;\x1b\\") }} /// Renders contents in order, and returns the first non-empty output.pub struct CoalesceTemplate<T>(pub Vec<T>); impl<T: Template> Template for CoalesceTemplate<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let Some((last, contents)) = self.0.split_last() else { return Ok(()); }; let record_non_empty = record_non_empty_fn(formatter); if let Some(recorder) = contents.iter().find_map(record_non_empty) { recorder?.replay(formatter.as_mut()) } else { last.format(formatter) // no need to capture the last content } }} pub struct ConcatTemplate<T>(pub Vec<T>); impl<T: Template> Template for ConcatTemplate<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { for template in &self.0 { template.format(formatter)?; } Ok(()) }} /// Renders the content to buffer, and transforms it without losing labels.pub struct ReformatTemplate<T, F> { content: T, reformat: F,} impl<T, F> ReformatTemplate<T, F> { pub fn new(content: T, reformat: F) -> Self where T: Template, F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>, { Self { content, reformat } }} impl<T, F> Template for ReformatTemplate<T, F>where T: Template, F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>,{ fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let rewrap = formatter.rewrap_fn(); let mut recorder = FormatRecorder::new(formatter.maybe_color()); self.content.format(&mut rewrap(&mut recorder))?; (self.reformat)(formatter, &recorder) }} /// Like `ConcatTemplate`, but inserts a separator between contents.pub struct JoinTemplate<S, T> { separator: S, contents: Vec<T>,} impl<S, T> JoinTemplate<S, T> { pub fn new(separator: S, contents: Vec<T>) -> Self where S: Template, T: Template, { Self { separator, contents, } }} impl<S, T> Template for JoinTemplate<S, T>where S: Template, T: Template,{ fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, &self.contents, &self.separator) }} /// Like `JoinTemplate`, but ignores empty contents.pub struct SeparateTemplate<S, T> { separator: S, contents: Vec<T>,} impl<S, T> SeparateTemplate<S, T> { pub fn new(separator: S, contents: Vec<T>) -> Self where S: Template, T: Template, { Self { separator, contents, } }} impl<S, T> Template for SeparateTemplate<S, T>where S: Template, T: Template,{ fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let record_non_empty = record_non_empty_fn(formatter); let content_recorders = self.contents.iter().filter_map(record_non_empty); format_joined_with( formatter, content_recorders, &self.separator, |formatter, recorder| recorder?.replay(formatter.as_mut()), ) }} /// Wrapper around an error occurred during template evaluation.#[derive(Debug)]pub struct TemplatePropertyError(pub Box<dyn error::Error + Send + Sync>); // Implements conversion from any error type to support `expr?` in function// binding. This type doesn't implement `std::error::Error` instead.// <https://github.com/dtolnay/anyhow/issues/25#issuecomment-544140480>impl<E> From<E> for TemplatePropertyErrorwhere E: error::Error + Send + Sync + 'static,{ fn from(err: E) -> Self { Self(err.into()) }} /// Lazily evaluated value which can fail to evaluate.pub trait TemplateProperty { type Output; fn extract(&self) -> Result<Self::Output, TemplatePropertyError>;} impl<P: TemplateProperty + ?Sized> TemplateProperty for Box<P> { type Output = <P as TemplateProperty>::Output; fn extract(&self) -> Result<Self::Output, TemplatePropertyError> { <P as TemplateProperty>::extract(self) }} impl<P: TemplateProperty> TemplateProperty for Option<P> { type Output = Option<P::Output>; fn extract(&self) -> Result<Self::Output, TemplatePropertyError> { self.as_ref().map(|property| property.extract()).transpose() }} // Implement TemplateProperty for tuplesmacro_rules! tuple_impls { ($( ( $($n:tt $T:ident),+ ) )+) => { $( impl<$($T: TemplateProperty,)+> TemplateProperty for ($($T,)+) { type Output = ($($T::Output,)+); fn extract(&self) -> Result<Self::Output, TemplatePropertyError> { Ok(($(self.$n.extract()?,)+)) } } )+ }} tuple_impls! { (0 T0) (0 T0, 1 T1) (0 T0, 1 T1, 2 T2) (0 T0, 1 T1, 2 T2, 3 T3)} /// Type-erased [`TemplateProperty`].pub type BoxedTemplateProperty<'a, O> = Box<dyn TemplateProperty<Output = O> + 'a>;pub type BoxedSerializeProperty<'a> = BoxedTemplateProperty<'a, Box<dyn erased_serde::Serialize + 'a>>; /// [`TemplateProperty`] adapters that are useful when implementing methods.pub trait TemplatePropertyExt: TemplateProperty { /// Translates to a property that will apply fallible `function` to an /// extracted value. fn and_then<O, F>(self, function: F) -> TemplateFunction<Self, F> where Self: Sized, F: Fn(Self::Output) -> Result<O, TemplatePropertyError>, { TemplateFunction::new(self, function) } /// Translates to a property that will apply `function` to an extracted /// value, leaving `Err` untouched. fn map<O, F>(self, function: F) -> impl TemplateProperty<Output = O> where Self: Sized, F: Fn(Self::Output) -> O, { TemplateFunction::new(self, move |value| Ok(function(value))) } /// Translates to a property that will unwrap an extracted `Option` value /// of the specified `type_name`, mapping `None` to `Err`. fn try_unwrap<O>(self, type_name: &str) -> impl TemplateProperty<Output = O> where Self: TemplateProperty<Output = Option<O>> + Sized, { self.and_then(move |opt| { opt.ok_or_else(|| TemplatePropertyError(format!("No {type_name} available").into())) }) } /// Converts this property into boxed serialize property. fn into_serialize<'a>(self) -> BoxedSerializeProperty<'a> where Self: Sized + 'a, Self::Output: serde::Serialize, { Box::new(self.map(|value| Box::new(value) as Box<dyn erased_serde::Serialize>)) } /// Converts this property into `Template`. fn into_template<'a>(self) -> Box<dyn Template + 'a> where Self: Sized + 'a, Self::Output: Template, { Box::new(FormattablePropertyTemplate::new(self)) } /// Converts this property into boxed trait object. fn into_dyn<'a>(self) -> BoxedTemplateProperty<'a, Self::Output> where Self: Sized + 'a, {Lines 1–500 of 1110. Load more
// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Tools for lazily evaluating templates that produce text in a fallible //! manner. use std::cell::RefCell; use std::error; use std::fmt; use std::io; use std::io::Write; use std::iter; use std::rc::Rc; use bstr::BStr; use bstr::BString; use jj_lib::backend::Signature; use jj_lib::backend::Timestamp; use jj_lib::config::ConfigValue; use jj_lib::op_store::TimestampRange; use crate::formatter::FormatRecorder; use crate::formatter::Formatter; use crate::formatter::FormatterExt as _; use crate::formatter::LabeledScope; use crate::formatter::PlainTextFormatter; use crate::text_util; use crate::time_util; /// Represents a printable type or a compiled template containing a placeholder /// value. /// /// This is analogous to [`std::fmt::Display`], but with customized error /// handling. pub trait Template { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()>; } impl<T: Template + ?Sized> Template for &T { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { <T as Template>::format(self, formatter) } } impl<T: Template + ?Sized> Template for Box<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { <T as Template>::format(self, formatter) } } // All optional printable types should be printable, and it's unlikely to // implement different formatting per type. impl<T: Template> Template for Option<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { self.as_ref().map_or(Ok(()), |t| t.format(formatter)) } } impl Template for BString { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { formatter.as_mut().write_all(self) } } impl Template for &BStr { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { formatter.as_mut().write_all(self) } } impl Template for ConfigValue { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") } } impl Template for Signature { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter.labeled("name"), "{}", self.name)?; if !self.name.is_empty() && !self.email.is_empty() { write!(formatter, " ")?; } if !self.email.is_empty() { write!(formatter, "<")?; let email = Email(self.email.clone()); email.format(formatter)?; write!(formatter, ">")?; } Ok(()) } } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] #[serde(transparent)] pub struct Email(pub String); impl Template for Email { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let (local, domain) = text_util::split_email(&self.0); write!(formatter.labeled("local"), "{local}")?; if let Some(domain) = domain { write!(formatter, "@")?; write!(formatter.labeled("domain"), "{domain}")?; } Ok(()) } } // In template language, an integer value is represented as i64. However, we use // usize here because it's more convenient to guarantee that the lower value is // bounded to 0. pub type SizeHint = (usize, Option<usize>); impl Template for String { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") } } impl Template for &str { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") } } impl Template for Timestamp { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { match time_util::format_absolute_timestamp(self) { Ok(formatted) => write!(formatter, "{formatted}"), Err(err) => formatter.handle_error(err.into()), } } } impl Template for TimestampRange { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { self.start.format(formatter)?; write!(formatter, " - ")?; self.end.format(formatter)?; Ok(()) } } impl Template for Vec<String> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, self, " ") } } impl Template for bool { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let repr = if *self { "true" } else { "false" }; write!(formatter, "{repr}") } } impl Template for i64 { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") } } pub struct LabelTemplate<T, L> { content: T, labels: L, } impl<T, L> LabelTemplate<T, L> { pub fn new(content: T, labels: L) -> Self where T: Template, L: TemplateProperty<Output = Vec<String>>, { Self { content, labels } } } impl<T, L> Template for LabelTemplate<T, L> where T: Template, L: TemplateProperty<Output = Vec<String>>, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { match self.labels.extract() { Ok(labels) => format_labeled(formatter, &self.content, &labels), Err(err) => formatter.handle_error(err), } } } pub struct RawEscapeSequenceTemplate<T>(pub T); impl<T: Template> Template for RawEscapeSequenceTemplate<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let rewrap = formatter.rewrap_fn(); let mut raw_formatter = PlainTextFormatter::new(formatter.raw()?); self.0.format(&mut rewrap(&mut raw_formatter)) } } /// Renders a hyperlink using OSC 8 escape sequences when supported, or a /// fallback otherwise. pub struct HyperlinkTemplate<U, T, F> { url: U, text: T, fallback: Option<F>, } impl<U, T, F> HyperlinkTemplate<U, T, F> { pub fn new(url: U, text: T, fallback: Option<F>) -> Self { Self { url, text, fallback, } } } impl<U, T, F> Template for HyperlinkTemplate<U, T, F> where U: TemplateProperty<Output = String>, T: Template, F: Template, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { // Extract URL string let url_str = match self.url.extract() { Ok(url) => url, Err(err) => return formatter.handle_error(err), }; if !formatter.maybe_color() { if let Some(fallback) = &self.fallback { return fallback.format(formatter); } return self.text.format(formatter); } // Write OSC 8 hyperlink via raw() write!(formatter.raw()?, "\x1b]8;;{url_str}\x1b\\")?; self.text.format(formatter)?; write!(formatter.raw()?, "\x1b]8;;\x1b\\") } } /// Renders contents in order, and returns the first non-empty output. pub struct CoalesceTemplate<T>(pub Vec<T>); impl<T: Template> Template for CoalesceTemplate<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let Some((last, contents)) = self.0.split_last() else { return Ok(()); }; let record_non_empty = record_non_empty_fn(formatter); if let Some(recorder) = contents.iter().find_map(record_non_empty) { recorder?.replay(formatter.as_mut()) } else { last.format(formatter) // no need to capture the last content } } } pub struct ConcatTemplate<T>(pub Vec<T>); impl<T: Template> Template for ConcatTemplate<T> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { for template in &self.0 { template.format(formatter)?; } Ok(()) } } /// Renders the content to buffer, and transforms it without losing labels. pub struct ReformatTemplate<T, F> { content: T, reformat: F, } impl<T, F> ReformatTemplate<T, F> { pub fn new(content: T, reformat: F) -> Self where T: Template, F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>, { Self { content, reformat } } } impl<T, F> Template for ReformatTemplate<T, F> where T: Template, F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let rewrap = formatter.rewrap_fn(); let mut recorder = FormatRecorder::new(formatter.maybe_color()); self.content.format(&mut rewrap(&mut recorder))?; (self.reformat)(formatter, &recorder) } } /// Like `ConcatTemplate`, but inserts a separator between contents. pub struct JoinTemplate<S, T> { separator: S, contents: Vec<T>, } impl<S, T> JoinTemplate<S, T> { pub fn new(separator: S, contents: Vec<T>) -> Self where S: Template, T: Template, { Self { separator, contents, } } } impl<S, T> Template for JoinTemplate<S, T> where S: Template, T: Template, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, &self.contents, &self.separator) } } /// Like `JoinTemplate`, but ignores empty contents. pub struct SeparateTemplate<S, T> { separator: S, contents: Vec<T>, } impl<S, T> SeparateTemplate<S, T> { pub fn new(separator: S, contents: Vec<T>) -> Self where S: Template, T: Template, { Self { separator, contents, } } } impl<S, T> Template for SeparateTemplate<S, T> where S: Template, T: Template, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let record_non_empty = record_non_empty_fn(formatter); let content_recorders = self.contents.iter().filter_map(record_non_empty); format_joined_with( formatter, content_recorders, &self.separator, |formatter, recorder| recorder?.replay(formatter.as_mut()), ) } } /// Wrapper around an error occurred during template evaluation. #[derive(Debug)] pub struct TemplatePropertyError(pub Box<dyn error::Error + Send + Sync>); // Implements conversion from any error type to support `expr?` in function // binding. This type doesn't implement `std::error::Error` instead. // <https://github.com/dtolnay/anyhow/issues/25#issuecomment-544140480> impl<E> From<E> for TemplatePropertyError where E: error::Error + Send + Sync + 'static, { fn from(err: E) -> Self { Self(err.into()) } } /// Lazily evaluated value which can fail to evaluate. pub trait TemplateProperty { type Output; fn extract(&self) -> Result<Self::Output, TemplatePropertyError>; } impl<P: TemplateProperty + ?Sized> TemplateProperty for Box<P> { type Output = <P as TemplateProperty>::Output; fn extract(&self) -> Result<Self::Output, TemplatePropertyError> { <P as TemplateProperty>::extract(self) } } impl<P: TemplateProperty> TemplateProperty for Option<P> { type Output = Option<P::Output>; fn extract(&self) -> Result<Self::Output, TemplatePropertyError> { self.as_ref().map(|property| property.extract()).transpose() } } // Implement TemplateProperty for tuples macro_rules! tuple_impls { ($( ( $($n:tt $T:ident),+ ) )+) => { $( impl<$($T: TemplateProperty,)+> TemplateProperty for ($($T,)+) { type Output = ($($T::Output,)+); fn extract(&self) -> Result<Self::Output, TemplatePropertyError> { Ok(($(self.$n.extract()?,)+)) } } )+ } } tuple_impls! { (0 T0) (0 T0, 1 T1) (0 T0, 1 T1, 2 T2) (0 T0, 1 T1, 2 T2, 3 T3) } /// Type-erased [`TemplateProperty`]. pub type BoxedTemplateProperty<'a, O> = Box<dyn TemplateProperty<Output = O> + 'a>; pub type BoxedSerializeProperty<'a> = BoxedTemplateProperty<'a, Box<dyn erased_serde::Serialize + 'a>>; /// [`TemplateProperty`] adapters that are useful when implementing methods. pub trait TemplatePropertyExt: TemplateProperty { /// Translates to a property that will apply fallible `function` to an /// extracted value. fn and_then<O, F>(self, function: F) -> TemplateFunction<Self, F> where Self: Sized, F: Fn(Self::Output) -> Result<O, TemplatePropertyError>, { TemplateFunction::new(self, function) } /// Translates to a property that will apply `function` to an extracted /// value, leaving `Err` untouched. fn map<O, F>(self, function: F) -> impl TemplateProperty<Output = O> where Self: Sized, F: Fn(Self::Output) -> O, { TemplateFunction::new(self, move |value| Ok(function(value))) } /// Translates to a property that will unwrap an extracted `Option` value /// of the specified `type_name`, mapping `None` to `Err`. fn try_unwrap<O>(self, type_name: &str) -> impl TemplateProperty<Output = O> where Self: TemplateProperty<Output = Option<O>> + Sized, { self.and_then(move |opt| { opt.ok_or_else(|| TemplatePropertyError(format!("No {type_name} available").into())) }) } /// Converts this property into boxed serialize property. fn into_serialize<'a>(self) -> BoxedSerializeProperty<'a> where Self: Sized + 'a, Self::Output: serde::Serialize, { Box::new(self.map(|value| Box::new(value) as Box<dyn erased_serde::Serialize>)) } /// Converts this property into `Template`. fn into_template<'a>(self) -> Box<dyn Template + 'a> where Self: Sized + 'a, Self::Output: Template, { Box::new(FormattablePropertyTemplate::new(self)) } /// Converts this property into boxed trait object. fn into_dyn<'a>(self) -> BoxedTemplateProperty<'a, Self::Output> where Self: Sized + 'a, {