// 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 Template for &T { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { ::format(self, formatter) } } impl Template for Box { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { ::format(self, formatter) } } // All optional printable types should be printable, and it's unlikely to // implement different formatting per type. impl Template for Option { 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); /// Captures from a regex match, accessible by index or name. #[derive(Clone, Debug)] pub struct RegexCaptures { /// String that matches were found in. haystack: Vec, /// List of byte ranges in `haystack` for capture groups by index (with 0 /// being the full match). capture_ranges: Vec>, /// Mapping from capture group names to their index. names: HashMap, } impl RegexCaptures { pub fn new( haystack: Vec, capture_ranges: Vec>, names: HashMap, ) -> 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 { self.capture_ranges .get(index) .map(|range| self.haystack[range.start..range.end].into()) } pub fn name(&self, name: &str) -> Option { 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 { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { format_joined(formatter, self, " ") } } impl Template for Vec { 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 { content: T, labels: L, } impl LabelTemplate { pub fn new(content: T, labels: L) -> Self where T: Template, L: TemplateProperty>, { Self { content, labels } } } impl Template for LabelTemplate where T: Template, L: TemplateProperty>, { 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(pub T); impl Template for RawEscapeSequenceTemplate { 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 { url: U, text: T, fallback: Option, } impl HyperlinkTemplate { pub fn new(url: U, text: T, fallback: Option) -> Self { Self { url, text, fallback, } } } impl Template for HyperlinkTemplate where U: TemplateProperty, 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(pub Vec); impl Template for CoalesceTemplate { 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(pub Vec); impl Template for ConcatTemplate { 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 { content: T, reformat: F, } impl ReformatTemplate { pub fn new(content: T, reformat: F) -> Self where T: Template, F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>, { Self { content, reformat } } } impl Template for ReformatTemplate 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 { separator: S, contents: Vec, } impl JoinTemplate { pub fn new(separator: S, contents: Vec) -> Self where S: Template, T: Template, { Self { separator, contents, } } } impl Template for JoinTemplate 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 { separator: S, contents: Vec, } impl SeparateTemplate { pub fn new(separator: S, contents: Vec) -> Self where S: Template, T: Template, { Self { separator, contents, } } } impl Template for SeparateTemplate 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); // Implements conversion from any error type to support `expr?` in function // binding. This type doesn't implement `std::error::Error` instead. // impl From 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; } impl TemplateProperty for Box

{ type Output =

::Output; fn extract(&self) -> Result {

::extract(self) } } impl TemplateProperty for Option

{ type Output = Option; fn extract(&self) -> Result { 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 { 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 + 'a>; pub type BoxedSerializeProperty<'a> = BoxedTemplateProperty<'a, Box>; /// [`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(self, function: F) -> TemplateFunction where Self: Sized, F: Fn(Self::Output) -> Result, { TemplateFunction::new(self, function) } /// Translates to a property that will apply `function` to an extracted /// value, leaving `Err` untouched. fn map(self, function: F) -> impl TemplateProperty 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(self, type_name: &str) -> impl TemplateProperty where Self: TemplateProperty> + 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)) } /// Converts this property into `Template`. fn into_template<'a>(self) -> Box 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, { Box::new(self) } /// Converts this property into wrapped (or tagged) type. /// /// Use `W::wrap_property()` if the self type is known to be boxed. fn into_dyn_wrapped<'a, W>(self) -> W where Self: Sized + 'a, W: WrapTemplateProperty<'a, Self::Output>, { W::wrap_property(self.into_dyn()) } } impl TemplatePropertyExt for P {} /// Wraps template property of type `O` in tagged type. /// /// This is basically [`From>`], but is restricted /// to property types. #[diagnostic::on_unimplemented( message = "the template property of type `{O}` cannot be wrapped in `{Self}`" )] pub trait WrapTemplateProperty<'a, O>: Sized { fn wrap_property(property: BoxedTemplateProperty<'a, O>) -> Self; } /// Abstraction trait for a type-erased TemplateProperty pub trait AnyTemplateProperty<'a> { fn try_into_serialize(self: Box) -> Option>; fn try_into_template(self: Box) -> Option>; /// If this is a list whose elements convert to `Template`, concatenate with /// the given separator. fn try_join( self: Box, separator: Box, ) -> Option>; } pub type BoxedAnyProperty<'a> = Box + 'a>; /// Adapter that wraps literal value in `TemplateProperty`. pub struct Literal(pub O); impl Template for Literal { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { self.0.format(formatter) } } impl TemplateProperty for Literal { type Output = O; fn extract(&self) -> Result { Ok(self.0.clone()) } } /// Adapter to extract template value from property for displaying. pub struct FormattablePropertyTemplate

{ property: P, } impl

FormattablePropertyTemplate

{ pub fn new(property: P) -> Self where P: TemplateProperty, P::Output: Template, { Self { property } } } impl

Template for FormattablePropertyTemplate

where P: TemplateProperty, P::Output: Template, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { match self.property.extract() { Ok(template) => template.format(formatter), Err(err) => formatter.handle_error(err), } } } /// Adapter to turn template back to byte string property. pub struct PlainTextFormattedProperty { template: T, } impl PlainTextFormattedProperty { pub fn new(template: T) -> Self { Self { template } } } impl TemplateProperty for PlainTextFormattedProperty { type Output = BString; fn extract(&self) -> Result { let mut output = vec![]; let mut formatter = PlainTextFormatter::new(&mut output); let mut wrapper = TemplateFormatter::new(&mut formatter, propagate_property_error); self.template.format(&mut wrapper)?; Ok(BString::new(output)) } } /// Renders template property of list type with the given separator. /// /// Each list item will be formatted by the given `format_item()` function. pub struct ListPropertyTemplate { property: P, separator: S, format_item: F, } impl ListPropertyTemplate { pub fn new(property: P, separator: S, format_item: F) -> Self where P: TemplateProperty, P::Output: IntoIterator, S: Template, F: Fn(&mut TemplateFormatter, O) -> io::Result<()>, { Self { property, separator, format_item, } } } impl Template for ListPropertyTemplate where P: TemplateProperty, P::Output: IntoIterator, S: Template, F: Fn(&mut TemplateFormatter, O) -> io::Result<()>, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let contents = match self.property.extract() { Ok(contents) => contents, Err(err) => return formatter.handle_error(err), }; format_joined_with(formatter, contents, &self.separator, &self.format_item) } } /// Result of a `map()` operation. /// /// The operations supported on MappedProperty are dependent on the "true" type /// of `mapped`. pub struct ListMapProperty<'a, P, O> { property: P, placeholder: PropertyPlaceholder, mapped: BoxedAnyProperty<'a>, } impl<'a, P, O> ListMapProperty<'a, P, O> { pub fn new( property: P, placeholder: PropertyPlaceholder, mapped: BoxedAnyProperty<'a>, ) -> Self { Self { property, placeholder, mapped, } } } impl<'a, P, O> AnyTemplateProperty<'a> for ListMapProperty<'a, P, O> where P: TemplateProperty + 'a, P::Output: IntoIterator, O: Clone + 'a, { fn try_into_serialize(self: Box) -> Option> { let placeholder = self.placeholder; let mapped = self.mapped.try_into_serialize()?; Some( self.property .and_then(move |property| { property .into_iter() .map(|i| placeholder.with_value(i, || mapped.extract())) .collect::, _>>() }) .into_serialize(), ) } fn try_into_template(self: Box) -> Option> { self.try_join(Box::new(Literal(" "))) } fn try_join( self: Box, separator: Box, ) -> Option> { let placeholder = self.placeholder; let mapped = self.mapped.try_into_template()?; Some(Box::new(ListPropertyTemplate::new( self.property, separator, move |formatter, value| placeholder.with_value(value, || mapped.format(formatter)), ))) } } /// AnyTemplateProperty which selects an output based on a boolean condition. pub struct ConditionalProperty<'a, P> { pub condition: P, pub on_true: BoxedAnyProperty<'a>, pub on_false: Option>, } impl<'a, P> ConditionalProperty<'a, P> { pub fn new( condition: P, on_true: BoxedAnyProperty<'a>, on_false: Option>, ) -> Self where P: TemplateProperty + 'a, { Self { condition, on_true, on_false, } } } impl<'a, P> AnyTemplateProperty<'a> for ConditionalProperty<'a, P> where P: TemplateProperty + 'a, { fn try_into_serialize(self: Box) -> Option> { Some( ( self.condition, self.on_true.try_into_serialize()?, self.on_false?.try_into_serialize()?, ) .map( move |(condition, on_true, on_false)| { if condition { on_true } else { on_false } }, ) .into_dyn(), ) } fn try_into_template(self: Box) -> Option> { Some(Box::new(ConditionalTemplate::new( self.condition, self.on_true.try_into_template()?, // NOTE: We need to propagate out the inner `None` (or else we would // allow a non-template `else` property). match self.on_false { Some(on_false) => on_false.try_into_template()?, None => Box::new(Literal("")), }, ))) } fn try_join( self: Box, _separator: Box, ) -> Option> { // NOTE: This is implementable, but currently cannot be called. None } } /// Template which selects an output based on a boolean condition. pub struct ConditionalTemplate { pub condition: P, pub true_template: T, pub false_template: U, } impl ConditionalTemplate { pub fn new(condition: P, true_template: T, false_template: U) -> Self where P: TemplateProperty, T: Template, U: Template, { Self { condition, true_template, false_template, } } } impl Template for ConditionalTemplate where P: TemplateProperty, T: Template, U: Template, { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let condition = match self.condition.extract() { Ok(condition) => condition, Err(err) => return formatter.handle_error(err), }; match condition { true => self.true_template.format(formatter), false => self.false_template.format(formatter), } } } /// Attempts to render or extract contents in order, returns the first /// successful output. pub struct TryList(Vec); impl TryList { pub fn new(contents: Vec) -> Self { assert!(!contents.is_empty()); Self(contents) } fn try_into_inner(self, mut f: impl FnMut(T) -> Option) -> Option> { self.0.into_iter().map(&mut f).collect() } } /// Converts type-erased properties if all items support the operation. impl<'a> AnyTemplateProperty<'a> for TryList> { fn try_into_serialize(self: Box) -> Option> { let properties = self.try_into_inner(|p| p.try_into_serialize())?; Some(Box::new(TryList(properties))) } fn try_into_template(self: Box) -> Option> { let templates = self.try_into_inner(|p| p.try_into_template())?; Some(Box::new(TryList(templates))) } fn try_join( self: Box, _separator: Box, ) -> Option> { // NOTE: This is implementable, but currently cannot be called. None } } impl Template for TryList { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { let (last, contents) = self.0.split_last().unwrap(); if let Some(recorder) = contents.iter().find_map(|content| { let mut recorder = FormatRecorder::new(formatter.maybe_color()); let mut wrapper = TemplateFormatter::new(&mut recorder, propagate_property_error); content.format(&mut wrapper).is_ok().then_some(recorder) }) { recorder.replay(formatter.as_mut()) } else { last.format(formatter) // render the last error normally } } } impl TemplateProperty for TryList { type Output = T::Output; fn extract(&self) -> Result { let (last, contents) = self.0.split_last().unwrap(); if let Some(value) = contents.iter().find_map(|p| p.extract().ok()) { Ok(value) } else { last.extract() // propagate the last error } } } /// Adapter to apply fallible `function` to the `property`. /// /// This is usually created by `TemplatePropertyExt::and_then()`/`map()`. pub struct TemplateFunction { pub property: P, pub function: F, } impl TemplateFunction { pub fn new(property: P, function: F) -> Self where P: TemplateProperty, F: Fn(P::Output) -> Result, { Self { property, function } } } impl TemplateProperty for TemplateFunction where P: TemplateProperty, F: Fn(P::Output) -> Result, { type Output = O; fn extract(&self) -> Result { (self.function)(self.property.extract()?) } } /// Property which will be compiled into template once, and substituted later. #[derive(Clone, Debug)] pub struct PropertyPlaceholder { value: Rc>>, } impl PropertyPlaceholder { pub fn new() -> Self { Self { value: Rc::new(RefCell::new(None)), } } pub fn set(&self, value: O) { *self.value.borrow_mut() = Some(value); } pub fn take(&self) -> Option { self.value.borrow_mut().take() } pub fn with_value(&self, value: O, f: impl FnOnce() -> R) -> R { self.set(value); let result = f(); self.take(); result } } impl Default for PropertyPlaceholder { fn default() -> Self { Self::new() } } impl TemplateProperty for PropertyPlaceholder { type Output = O; fn extract(&self) -> Result { if let Some(value) = self.value.borrow().as_ref() { Ok(value.clone()) } else { Err(TemplatePropertyError("Placeholder value is not set".into())) } } } /// Adapter that renders compiled `template` with the `placeholder` value set. pub struct TemplateRenderer<'a, C> { template: Box, placeholder: PropertyPlaceholder, labels: Vec, } impl<'a, C: Clone> TemplateRenderer<'a, C> { pub fn new(template: Box, placeholder: PropertyPlaceholder) -> Self { Self { template, placeholder, labels: Vec::new(), } } /// Returns renderer that will format template with the given `labels`. /// /// This is equivalent to wrapping the content template with `label()` /// function. For example, /// `content.labeled(["foo", "bar"]).labeled(["baz"])` can be expressed as /// `label("baz", label("foo bar", content))` in template. pub fn labeled>(mut self, labels: impl IntoIterator) -> Self { self.labels.splice(0..0, labels.into_iter().map(Into::into)); self } pub fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { let mut wrapper = TemplateFormatter::new(formatter, format_property_error_inline); self.placeholder.with_value(context.clone(), || { format_labeled(&mut wrapper, &self.template, &self.labels) }) } /// Renders template into buffer ignoring any color labels. /// /// The output is usually UTF-8, but it can contain arbitrary bytes such as /// file content. pub fn format_plain_text(&self, context: &C) -> Vec { let mut output = Vec::new(); self.format(context, &mut PlainTextFormatter::new(&mut output)) .expect("write() to vec backed formatter should never fail"); output } } /// Wrapper to pass around `Formatter` and error handler. pub struct TemplateFormatter<'a> { formatter: &'a mut dyn Formatter, error_handler: PropertyErrorHandler, } impl<'a> TemplateFormatter<'a> { fn new(formatter: &'a mut dyn Formatter, error_handler: PropertyErrorHandler) -> Self { Self { formatter, error_handler, } } /// Returns function that wraps another `Formatter` with the current error /// handling strategy. /// /// This does not borrow `self` so the underlying formatter can be mutably /// borrowed. pub fn rewrap_fn(&self) -> impl Fn(&mut dyn Formatter) -> TemplateFormatter<'_> + use<> { let error_handler = self.error_handler; move |formatter| TemplateFormatter::new(formatter, error_handler) } pub fn raw(&mut self) -> io::Result> { self.formatter.raw() } pub fn labeled(&mut self, label: &str) -> LabeledScope<&mut (dyn Formatter + 'a)> { self.formatter.labeled(label) } pub fn push_label(&mut self, label: &str) { self.formatter.push_label(label); } pub fn pop_label(&mut self) { self.formatter.pop_label(); } pub fn maybe_color(&self) -> bool { self.formatter.maybe_color() } pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { self.formatter.write_fmt(args) } /// Handles the given template property evaluation error. /// /// This usually prints the given error inline, and returns `Ok`. It's up to /// caller to decide whether or not to continue template processing on `Ok`. /// For example, `if(cond, ..)` expression will terminate if the `cond` /// failed to evaluate, whereas `concat(x, y, ..)` will continue processing. /// /// If `Err` is returned, the error should be propagated. pub fn handle_error(&mut self, err: TemplatePropertyError) -> io::Result<()> { (self.error_handler)(self.formatter, err) } } impl<'a> AsMut for TemplateFormatter<'a> { fn as_mut(&mut self) -> &mut (dyn Formatter + 'a) { self.formatter } } pub fn format_joined( formatter: &mut TemplateFormatter, contents: I, separator: S, ) -> io::Result<()> where I: IntoIterator, I::Item: Template, S: Template, { format_joined_with(formatter, contents, separator, |formatter, item| { item.format(formatter) }) } fn format_joined_with( formatter: &mut TemplateFormatter, contents: I, separator: S, mut format_item: F, ) -> io::Result<()> where I: IntoIterator, S: Template, F: FnMut(&mut TemplateFormatter, I::Item) -> io::Result<()>, { let mut contents_iter = contents.into_iter().fuse(); if let Some(item) = contents_iter.next() { format_item(formatter, item)?; } for item in contents_iter { separator.format(formatter)?; format_item(formatter, item)?; } Ok(()) } fn format_labeled( formatter: &mut TemplateFormatter, content: &T, labels: &[String], ) -> io::Result<()> { for label in labels { formatter.push_label(label); } content.format(formatter)?; for _label in labels { formatter.pop_label(); } Ok(()) } type PropertyErrorHandler = fn(&mut dyn Formatter, TemplatePropertyError) -> io::Result<()>; /// Prints property evaluation error as inline template output. fn format_property_error_inline( formatter: &mut dyn Formatter, err: TemplatePropertyError, ) -> io::Result<()> { let TemplatePropertyError(err) = &err; let mut formatter = formatter.labeled("error"); write!(formatter, "<")?; write!(formatter.labeled("heading"), "Error: ")?; write!(formatter, "{err}")?; for err in iter::successors(err.source(), |err| err.source()) { write!(formatter, ": {err}")?; } write!(formatter, ">")?; Ok(()) } fn propagate_property_error( _formatter: &mut dyn Formatter, err: TemplatePropertyError, ) -> io::Result<()> { Err(io::Error::other(err.0)) } /// Creates function that renders a template to buffer and returns the buffer /// only if it isn't empty. /// /// This inherits the error handling strategy from the given `formatter`. fn record_non_empty_fn( formatter: &TemplateFormatter, // TODO: T doesn't have to be captured, but "currently, all type parameters // are required to be mentioned in the precise captures list" as of rustc // 1.85.0. ) -> impl Fn(&T) -> Option> + use { let rewrap = formatter.rewrap_fn(); let maybe_color = formatter.maybe_color(); move |template| { let mut recorder = FormatRecorder::new(maybe_color); match template.format(&mut rewrap(&mut recorder)) { Ok(()) if recorder.data().is_empty() => None, // omit empty content Ok(()) => Some(Ok(recorder)), Err(e) => Some(Err(e)), } } }