// 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::collections::HashMap;use std::error;use std::fmt;use std::io;use std::io::Write;use std::iter;use std::ops::Range;use std::path::PathBuf;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::file_util;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 PathBuf { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { // Render native path bytes directly. Serialization still follows // PathBuf's serde behavior, which can reject non-UTF-8 paths. let bytes = file_util::path_to_bytes(self).map_err(io::Error::other)?; formatter.as_mut().write_all(bytes) }} 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>); /// Captures from a regex match, accessible by index or name.#[derive(Clone, Debug)]pub struct RegexCaptures { /// String that matches were found in. haystack: Vec<u8>, /// List of byte ranges in `haystack` for capture groups by index (with 0 /// being the full match). capture_ranges: Vec<Range<usize>>, /// Mapping from capture group names to their index. names: HashMap<String, usize>,} impl RegexCaptures { pub fn new( haystack: Vec<u8>, capture_ranges: Vec<Range<usize>>, names: HashMap<String, usize>, ) -> Self { Self { haystack, capture_ranges, names, } } #[expect(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.capture_ranges.len() } pub fn get(&self, index: usize) -> Option<BString> { self.capture_ranges .get(index) .map(|range| self.haystack[range.start..range.end].into()) } pub fn name(&self, name: &str) -> Option<BString> { self.names.get(name).and_then(|&i| self.get(i)) }} 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<BString> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, self, " ") }} 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`].Lines 1–500 of 1170. 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::collections::HashMap; use std::error; use std::fmt; use std::io; use std::io::Write; use std::iter; use std::ops::Range; use std::path::PathBuf; 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::file_util; 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 PathBuf { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { // Render native path bytes directly. Serialization still follows // PathBuf's serde behavior, which can reject non-UTF-8 paths. let bytes = file_util::path_to_bytes(self).map_err(io::Error::other)?; formatter.as_mut().write_all(bytes) } } 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>); /// Captures from a regex match, accessible by index or name. #[derive(Clone, Debug)] pub struct RegexCaptures { /// String that matches were found in. haystack: Vec<u8>, /// List of byte ranges in `haystack` for capture groups by index (with 0 /// being the full match). capture_ranges: Vec<Range<usize>>, /// Mapping from capture group names to their index. names: HashMap<String, usize>, } impl RegexCaptures { pub fn new( haystack: Vec<u8>, capture_ranges: Vec<Range<usize>>, names: HashMap<String, usize>, ) -> Self { Self { haystack, capture_ranges, names, } } #[expect(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.capture_ranges.len() } pub fn get(&self, index: usize) -> Option<BString> { self.capture_ranges .get(index) .map(|range| self.haystack[range.start..range.end].into()) } pub fn name(&self, name: &str) -> Option<BString> { self.names.get(name).and_then(|&i| self.get(i)) } } 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<BString> { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, self, " ") } } 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`].