Skip to main content

jj_cli/
templater.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Tools for lazily evaluating templates that produce text in a fallible
16//! manner.
17
18use std::cell::RefCell;
19use std::collections::HashMap;
20use std::error;
21use std::fmt;
22use std::io;
23use std::io::Write;
24use std::iter;
25use std::ops::Range;
26use std::path::PathBuf;
27use std::rc::Rc;
28
29use bstr::BStr;
30use bstr::BString;
31use jj_lib::backend::Signature;
32use jj_lib::backend::Timestamp;
33use jj_lib::config::ConfigValue;
34use jj_lib::file_util;
35use jj_lib::op_store::TimestampRange;
36
37use crate::formatter::FormatRecorder;
38use crate::formatter::Formatter;
39use crate::formatter::FormatterExt as _;
40use crate::formatter::LabeledScope;
41use crate::formatter::PlainTextFormatter;
42use crate::text_util;
43use crate::time_util;
44
45/// Represents a printable type or a compiled template containing a placeholder
46/// value.
47///
48/// This is analogous to [`std::fmt::Display`], but with customized error
49/// handling.
50pub trait Template {
51    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()>;
52}
53
54impl<T: Template + ?Sized> Template for &T {
55    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
56        <T as Template>::format(self, formatter)
57    }
58}
59
60impl<T: Template + ?Sized> Template for Box<T> {
61    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
62        <T as Template>::format(self, formatter)
63    }
64}
65
66// All optional printable types should be printable, and it's unlikely to
67// implement different formatting per type.
68impl<T: Template> Template for Option<T> {
69    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
70        self.as_ref().map_or(Ok(()), |t| t.format(formatter))
71    }
72}
73
74impl Template for BString {
75    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
76        formatter.as_mut().write_all(self)
77    }
78}
79
80impl Template for &BStr {
81    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
82        formatter.as_mut().write_all(self)
83    }
84}
85
86impl Template for PathBuf {
87    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
88        // Render native path bytes directly. Serialization still follows
89        // PathBuf's serde behavior, which can reject non-UTF-8 paths.
90        let bytes = file_util::path_to_bytes(self).map_err(io::Error::other)?;
91        formatter.as_mut().write_all(bytes)
92    }
93}
94
95impl Template for ConfigValue {
96    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
97        write!(formatter, "{self}")
98    }
99}
100
101impl Template for Signature {
102    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
103        write!(formatter.labeled("name"), "{}", self.name)?;
104        if !self.name.is_empty() && !self.email.is_empty() {
105            write!(formatter, " ")?;
106        }
107        if !self.email.is_empty() {
108            write!(formatter, "<")?;
109            let email = Email(self.email.clone());
110            email.format(formatter)?;
111            write!(formatter, ">")?;
112        }
113        Ok(())
114    }
115}
116
117#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
118#[serde(transparent)]
119pub struct Email(pub String);
120
121impl Template for Email {
122    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
123        let (local, domain) = text_util::split_email(&self.0);
124        write!(formatter.labeled("local"), "{local}")?;
125        if let Some(domain) = domain {
126            write!(formatter, "@")?;
127            write!(formatter.labeled("domain"), "{domain}")?;
128        }
129        Ok(())
130    }
131}
132
133// In template language, an integer value is represented as i64. However, we use
134// usize here because it's more convenient to guarantee that the lower value is
135// bounded to 0.
136pub type SizeHint = (usize, Option<usize>);
137
138/// Captures from a regex match, accessible by index or name.
139#[derive(Clone, Debug)]
140pub struct RegexCaptures {
141    /// String that matches were found in.
142    haystack: Vec<u8>,
143    /// List of byte ranges in `haystack` for capture groups by index (with 0
144    /// being the full match).
145    capture_ranges: Vec<Range<usize>>,
146    /// Mapping from capture group names to their index.
147    names: HashMap<String, usize>,
148}
149
150impl RegexCaptures {
151    pub fn new(
152        haystack: Vec<u8>,
153        capture_ranges: Vec<Range<usize>>,
154        names: HashMap<String, usize>,
155    ) -> Self {
156        Self {
157            haystack,
158            capture_ranges,
159            names,
160        }
161    }
162
163    #[expect(clippy::len_without_is_empty)]
164    pub fn len(&self) -> usize {
165        self.capture_ranges.len()
166    }
167
168    pub fn get(&self, index: usize) -> Option<BString> {
169        self.capture_ranges
170            .get(index)
171            .map(|range| self.haystack[range.start..range.end].into())
172    }
173
174    pub fn name(&self, name: &str) -> Option<BString> {
175        self.names.get(name).and_then(|&i| self.get(i))
176    }
177}
178
179impl Template for String {
180    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
181        write!(formatter, "{self}")
182    }
183}
184
185impl Template for &str {
186    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
187        write!(formatter, "{self}")
188    }
189}
190
191impl Template for Timestamp {
192    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
193        match time_util::format_absolute_timestamp(self) {
194            Ok(formatted) => write!(formatter, "{formatted}"),
195            Err(err) => formatter.handle_error(err.into()),
196        }
197    }
198}
199
200impl Template for TimestampRange {
201    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
202        self.start.format(formatter)?;
203        write!(formatter, " - ")?;
204        self.end.format(formatter)?;
205        Ok(())
206    }
207}
208
209impl Template for Vec<BString> {
210    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
211        format_joined(formatter, self, " ")
212    }
213}
214
215impl Template for Vec<String> {
216    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
217        format_joined(formatter, self, " ")
218    }
219}
220
221impl Template for bool {
222    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
223        let repr = if *self { "true" } else { "false" };
224        write!(formatter, "{repr}")
225    }
226}
227
228impl Template for i64 {
229    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
230        write!(formatter, "{self}")
231    }
232}
233
234pub struct LabelTemplate<T, L> {
235    content: T,
236    labels: L,
237}
238
239impl<T, L> LabelTemplate<T, L> {
240    pub fn new(content: T, labels: L) -> Self
241    where
242        T: Template,
243        L: TemplateProperty<Output = Vec<String>>,
244    {
245        Self { content, labels }
246    }
247}
248
249impl<T, L> Template for LabelTemplate<T, L>
250where
251    T: Template,
252    L: TemplateProperty<Output = Vec<String>>,
253{
254    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
255        match self.labels.extract() {
256            Ok(labels) => format_labeled(formatter, &self.content, &labels),
257            Err(err) => formatter.handle_error(err),
258        }
259    }
260}
261
262pub struct RawEscapeSequenceTemplate<T>(pub T);
263
264impl<T: Template> Template for RawEscapeSequenceTemplate<T> {
265    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
266        let rewrap = formatter.rewrap_fn();
267        let mut raw_formatter = PlainTextFormatter::new(formatter.raw()?);
268        self.0.format(&mut rewrap(&mut raw_formatter))
269    }
270}
271
272/// Renders a hyperlink using OSC 8 escape sequences when supported, or a
273/// fallback otherwise.
274pub struct HyperlinkTemplate<U, T, F> {
275    url: U,
276    text: T,
277    fallback: Option<F>,
278}
279
280impl<U, T, F> HyperlinkTemplate<U, T, F> {
281    pub fn new(url: U, text: T, fallback: Option<F>) -> Self {
282        Self {
283            url,
284            text,
285            fallback,
286        }
287    }
288}
289
290impl<U, T, F> Template for HyperlinkTemplate<U, T, F>
291where
292    U: TemplateProperty<Output = String>,
293    T: Template,
294    F: Template,
295{
296    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
297        // Extract URL string
298        let url_str = match self.url.extract() {
299            Ok(url) => url,
300            Err(err) => return formatter.handle_error(err),
301        };
302
303        if !formatter.maybe_color() {
304            if let Some(fallback) = &self.fallback {
305                return fallback.format(formatter);
306            }
307            return self.text.format(formatter);
308        }
309
310        // Write OSC 8 hyperlink via raw()
311        write!(formatter.raw()?, "\x1b]8;;{url_str}\x1b\\")?;
312        self.text.format(formatter)?;
313        write!(formatter.raw()?, "\x1b]8;;\x1b\\")
314    }
315}
316
317/// Renders contents in order, and returns the first non-empty output.
318pub struct CoalesceTemplate<T>(pub Vec<T>);
319
320impl<T: Template> Template for CoalesceTemplate<T> {
321    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
322        let Some((last, contents)) = self.0.split_last() else {
323            return Ok(());
324        };
325        let record_non_empty = record_non_empty_fn(formatter);
326        if let Some(recorder) = contents.iter().find_map(record_non_empty) {
327            recorder?.replay(formatter.as_mut())
328        } else {
329            last.format(formatter) // no need to capture the last content
330        }
331    }
332}
333
334pub struct ConcatTemplate<T>(pub Vec<T>);
335
336impl<T: Template> Template for ConcatTemplate<T> {
337    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
338        for template in &self.0 {
339            template.format(formatter)?;
340        }
341        Ok(())
342    }
343}
344
345/// Renders the content to buffer, and transforms it without losing labels.
346pub struct ReformatTemplate<T, F> {
347    content: T,
348    reformat: F,
349}
350
351impl<T, F> ReformatTemplate<T, F> {
352    pub fn new(content: T, reformat: F) -> Self
353    where
354        T: Template,
355        F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>,
356    {
357        Self { content, reformat }
358    }
359}
360
361impl<T, F> Template for ReformatTemplate<T, F>
362where
363    T: Template,
364    F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>,
365{
366    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
367        let rewrap = formatter.rewrap_fn();
368        let mut recorder = FormatRecorder::new(formatter.maybe_color());
369        self.content.format(&mut rewrap(&mut recorder))?;
370        (self.reformat)(formatter, &recorder)
371    }
372}
373
374/// Like `ConcatTemplate`, but inserts a separator between contents.
375pub struct JoinTemplate<S, T> {
376    separator: S,
377    contents: Vec<T>,
378}
379
380impl<S, T> JoinTemplate<S, T> {
381    pub fn new(separator: S, contents: Vec<T>) -> Self
382    where
383        S: Template,
384        T: Template,
385    {
386        Self {
387            separator,
388            contents,
389        }
390    }
391}
392
393impl<S, T> Template for JoinTemplate<S, T>
394where
395    S: Template,
396    T: Template,
397{
398    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
399        format_joined(formatter, &self.contents, &self.separator)
400    }
401}
402
403/// Like `JoinTemplate`, but ignores empty contents.
404pub struct SeparateTemplate<S, T> {
405    separator: S,
406    contents: Vec<T>,
407}
408
409impl<S, T> SeparateTemplate<S, T> {
410    pub fn new(separator: S, contents: Vec<T>) -> Self
411    where
412        S: Template,
413        T: Template,
414    {
415        Self {
416            separator,
417            contents,
418        }
419    }
420}
421
422impl<S, T> Template for SeparateTemplate<S, T>
423where
424    S: Template,
425    T: Template,
426{
427    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
428        let record_non_empty = record_non_empty_fn(formatter);
429        let content_recorders = self.contents.iter().filter_map(record_non_empty);
430        format_joined_with(
431            formatter,
432            content_recorders,
433            &self.separator,
434            |formatter, recorder| recorder?.replay(formatter.as_mut()),
435        )
436    }
437}
438
439/// Wrapper around an error occurred during template evaluation.
440#[derive(Debug)]
441pub struct TemplatePropertyError(pub Box<dyn error::Error + Send + Sync>);
442
443// Implements conversion from any error type to support `expr?` in function
444// binding. This type doesn't implement `std::error::Error` instead.
445// <https://github.com/dtolnay/anyhow/issues/25#issuecomment-544140480>
446impl<E> From<E> for TemplatePropertyError
447where
448    E: error::Error + Send + Sync + 'static,
449{
450    fn from(err: E) -> Self {
451        Self(err.into())
452    }
453}
454
455/// Lazily evaluated value which can fail to evaluate.
456pub trait TemplateProperty {
457    type Output;
458
459    fn extract(&self) -> Result<Self::Output, TemplatePropertyError>;
460}
461
462impl<P: TemplateProperty + ?Sized> TemplateProperty for Box<P> {
463    type Output = <P as TemplateProperty>::Output;
464
465    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
466        <P as TemplateProperty>::extract(self)
467    }
468}
469
470impl<P: TemplateProperty> TemplateProperty for Option<P> {
471    type Output = Option<P::Output>;
472
473    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
474        self.as_ref().map(|property| property.extract()).transpose()
475    }
476}
477
478// Implement TemplateProperty for tuples
479macro_rules! tuple_impls {
480    ($( ( $($n:tt $T:ident),+ ) )+) => {
481        $(
482            impl<$($T: TemplateProperty,)+> TemplateProperty for ($($T,)+) {
483                type Output = ($($T::Output,)+);
484
485                fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
486                    Ok(($(self.$n.extract()?,)+))
487                }
488            }
489        )+
490    }
491}
492
493tuple_impls! {
494    (0 T0)
495    (0 T0, 1 T1)
496    (0 T0, 1 T1, 2 T2)
497    (0 T0, 1 T1, 2 T2, 3 T3)
498}
499
500/// Type-erased [`TemplateProperty`].
501pub type BoxedTemplateProperty<'a, O> = Box<dyn TemplateProperty<Output = O> + 'a>;
502pub type BoxedSerializeProperty<'a> =
503    BoxedTemplateProperty<'a, Box<dyn erased_serde::Serialize + 'a>>;
504
505/// [`TemplateProperty`] adapters that are useful when implementing methods.
506pub trait TemplatePropertyExt: TemplateProperty {
507    /// Translates to a property that will apply fallible `function` to an
508    /// extracted value.
509    fn and_then<O, F>(self, function: F) -> TemplateFunction<Self, F>
510    where
511        Self: Sized,
512        F: Fn(Self::Output) -> Result<O, TemplatePropertyError>,
513    {
514        TemplateFunction::new(self, function)
515    }
516
517    /// Translates to a property that will apply `function` to an extracted
518    /// value, leaving `Err` untouched.
519    fn map<O, F>(self, function: F) -> impl TemplateProperty<Output = O>
520    where
521        Self: Sized,
522        F: Fn(Self::Output) -> O,
523    {
524        TemplateFunction::new(self, move |value| Ok(function(value)))
525    }
526
527    /// Translates to a property that will unwrap an extracted `Option` value
528    /// of the specified `type_name`, mapping `None` to `Err`.
529    fn try_unwrap<O>(self, type_name: &str) -> impl TemplateProperty<Output = O>
530    where
531        Self: TemplateProperty<Output = Option<O>> + Sized,
532    {
533        self.and_then(move |opt| {
534            opt.ok_or_else(|| TemplatePropertyError(format!("No {type_name} available").into()))
535        })
536    }
537
538    /// Converts this property into boxed serialize property.
539    fn into_serialize<'a>(self) -> BoxedSerializeProperty<'a>
540    where
541        Self: Sized + 'a,
542        Self::Output: serde::Serialize,
543    {
544        Box::new(self.map(|value| Box::new(value) as Box<dyn erased_serde::Serialize>))
545    }
546
547    /// Converts this property into `Template`.
548    fn into_template<'a>(self) -> Box<dyn Template + 'a>
549    where
550        Self: Sized + 'a,
551        Self::Output: Template,
552    {
553        Box::new(FormattablePropertyTemplate::new(self))
554    }
555
556    /// Converts this property into boxed trait object.
557    fn into_dyn<'a>(self) -> BoxedTemplateProperty<'a, Self::Output>
558    where
559        Self: Sized + 'a,
560    {
561        Box::new(self)
562    }
563
564    /// Converts this property into wrapped (or tagged) type.
565    ///
566    /// Use `W::wrap_property()` if the self type is known to be boxed.
567    fn into_dyn_wrapped<'a, W>(self) -> W
568    where
569        Self: Sized + 'a,
570        W: WrapTemplateProperty<'a, Self::Output>,
571    {
572        W::wrap_property(self.into_dyn())
573    }
574}
575
576impl<P: TemplateProperty + ?Sized> TemplatePropertyExt for P {}
577
578/// Wraps template property of type `O` in tagged type.
579///
580/// This is basically [`From<BoxedTemplateProperty<'a, O>>`], but is restricted
581/// to property types.
582#[diagnostic::on_unimplemented(
583    message = "the template property of type `{O}` cannot be wrapped in `{Self}`"
584)]
585pub trait WrapTemplateProperty<'a, O>: Sized {
586    fn wrap_property(property: BoxedTemplateProperty<'a, O>) -> Self;
587}
588
589/// Abstraction trait for a type-erased TemplateProperty
590pub trait AnyTemplateProperty<'a> {
591    fn try_into_serialize(self: Box<Self>) -> Option<BoxedSerializeProperty<'a>>;
592
593    fn try_into_template(self: Box<Self>) -> Option<Box<dyn Template + 'a>>;
594
595    /// If this is a list whose elements convert to `Template`, concatenate with
596    /// the given separator.
597    fn try_join(
598        self: Box<Self>,
599        separator: Box<dyn Template + 'a>,
600    ) -> Option<Box<dyn Template + 'a>>;
601}
602pub type BoxedAnyProperty<'a> = Box<dyn AnyTemplateProperty<'a> + 'a>;
603
604/// Adapter that wraps literal value in `TemplateProperty`.
605pub struct Literal<O>(pub O);
606
607impl<O: Template> Template for Literal<O> {
608    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
609        self.0.format(formatter)
610    }
611}
612
613impl<O: Clone> TemplateProperty for Literal<O> {
614    type Output = O;
615
616    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
617        Ok(self.0.clone())
618    }
619}
620
621/// Adapter to extract template value from property for displaying.
622pub struct FormattablePropertyTemplate<P> {
623    property: P,
624}
625
626impl<P> FormattablePropertyTemplate<P> {
627    pub fn new(property: P) -> Self
628    where
629        P: TemplateProperty,
630        P::Output: Template,
631    {
632        Self { property }
633    }
634}
635
636impl<P> Template for FormattablePropertyTemplate<P>
637where
638    P: TemplateProperty,
639    P::Output: Template,
640{
641    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
642        match self.property.extract() {
643            Ok(template) => template.format(formatter),
644            Err(err) => formatter.handle_error(err),
645        }
646    }
647}
648
649/// Adapter to turn template back to byte string property.
650pub struct PlainTextFormattedProperty<T> {
651    template: T,
652}
653
654impl<T> PlainTextFormattedProperty<T> {
655    pub fn new(template: T) -> Self {
656        Self { template }
657    }
658}
659
660impl<T: Template> TemplateProperty for PlainTextFormattedProperty<T> {
661    type Output = BString;
662
663    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
664        let mut output = vec![];
665        let mut formatter = PlainTextFormatter::new(&mut output);
666        let mut wrapper = TemplateFormatter::new(&mut formatter, propagate_property_error);
667        self.template.format(&mut wrapper)?;
668        Ok(BString::new(output))
669    }
670}
671
672/// Renders template property of list type with the given separator.
673///
674/// Each list item will be formatted by the given `format_item()` function.
675pub struct ListPropertyTemplate<P, S, F> {
676    property: P,
677    separator: S,
678    format_item: F,
679}
680
681impl<P, S, F> ListPropertyTemplate<P, S, F> {
682    pub fn new<O>(property: P, separator: S, format_item: F) -> Self
683    where
684        P: TemplateProperty,
685        P::Output: IntoIterator<Item = O>,
686        S: Template,
687        F: Fn(&mut TemplateFormatter, O) -> io::Result<()>,
688    {
689        Self {
690            property,
691            separator,
692            format_item,
693        }
694    }
695}
696
697impl<O, P, S, F> Template for ListPropertyTemplate<P, S, F>
698where
699    P: TemplateProperty,
700    P::Output: IntoIterator<Item = O>,
701    S: Template,
702    F: Fn(&mut TemplateFormatter, O) -> io::Result<()>,
703{
704    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
705        let contents = match self.property.extract() {
706            Ok(contents) => contents,
707            Err(err) => return formatter.handle_error(err),
708        };
709        format_joined_with(formatter, contents, &self.separator, &self.format_item)
710    }
711}
712
713/// Result of a `map()` operation.
714///
715/// The operations supported on MappedProperty are dependent on the "true" type
716/// of `mapped`.
717pub struct ListMapProperty<'a, P, O> {
718    property: P,
719    placeholder: PropertyPlaceholder<O>,
720    mapped: BoxedAnyProperty<'a>,
721}
722
723impl<'a, P, O> ListMapProperty<'a, P, O> {
724    pub fn new(
725        property: P,
726        placeholder: PropertyPlaceholder<O>,
727        mapped: BoxedAnyProperty<'a>,
728    ) -> Self {
729        Self {
730            property,
731            placeholder,
732            mapped,
733        }
734    }
735}
736
737impl<'a, P, O> AnyTemplateProperty<'a> for ListMapProperty<'a, P, O>
738where
739    P: TemplateProperty + 'a,
740    P::Output: IntoIterator<Item = O>,
741    O: Clone + 'a,
742{
743    fn try_into_serialize(self: Box<Self>) -> Option<BoxedSerializeProperty<'a>> {
744        let placeholder = self.placeholder;
745        let mapped = self.mapped.try_into_serialize()?;
746        Some(
747            self.property
748                .and_then(move |property| {
749                    property
750                        .into_iter()
751                        .map(|i| placeholder.with_value(i, || mapped.extract()))
752                        .collect::<Result<Vec<_>, _>>()
753                })
754                .into_serialize(),
755        )
756    }
757
758    fn try_into_template(self: Box<Self>) -> Option<Box<dyn Template + 'a>> {
759        self.try_join(Box::new(Literal(" ")))
760    }
761
762    fn try_join(
763        self: Box<Self>,
764        separator: Box<dyn Template + 'a>,
765    ) -> Option<Box<dyn Template + 'a>> {
766        let placeholder = self.placeholder;
767        let mapped = self.mapped.try_into_template()?;
768        Some(Box::new(ListPropertyTemplate::new(
769            self.property,
770            separator,
771            move |formatter, value| placeholder.with_value(value, || mapped.format(formatter)),
772        )))
773    }
774}
775
776/// AnyTemplateProperty which selects an output based on a boolean condition.
777pub struct ConditionalProperty<'a, P> {
778    pub condition: P,
779    pub on_true: BoxedAnyProperty<'a>,
780    pub on_false: Option<BoxedAnyProperty<'a>>,
781}
782
783impl<'a, P> ConditionalProperty<'a, P> {
784    pub fn new(
785        condition: P,
786        on_true: BoxedAnyProperty<'a>,
787        on_false: Option<BoxedAnyProperty<'a>>,
788    ) -> Self
789    where
790        P: TemplateProperty<Output = bool> + 'a,
791    {
792        Self {
793            condition,
794            on_true,
795            on_false,
796        }
797    }
798}
799
800impl<'a, P> AnyTemplateProperty<'a> for ConditionalProperty<'a, P>
801where
802    P: TemplateProperty<Output = bool> + 'a,
803{
804    fn try_into_serialize(self: Box<Self>) -> Option<BoxedSerializeProperty<'a>> {
805        Some(
806            (
807                self.condition,
808                self.on_true.try_into_serialize()?,
809                self.on_false?.try_into_serialize()?,
810            )
811                .map(
812                    move |(condition, on_true, on_false)| {
813                        if condition { on_true } else { on_false }
814                    },
815                )
816                .into_dyn(),
817        )
818    }
819
820    fn try_into_template(self: Box<Self>) -> Option<Box<dyn Template + 'a>> {
821        Some(Box::new(ConditionalTemplate::new(
822            self.condition,
823            self.on_true.try_into_template()?,
824            // NOTE: We need to propagate out the inner `None` (or else we would
825            // allow a non-template `else` property).
826            match self.on_false {
827                Some(on_false) => on_false.try_into_template()?,
828                None => Box::new(Literal("")),
829            },
830        )))
831    }
832
833    fn try_join(
834        self: Box<Self>,
835        _separator: Box<dyn Template + 'a>,
836    ) -> Option<Box<dyn Template + 'a>> {
837        // NOTE: This is implementable, but currently cannot be called.
838        None
839    }
840}
841
842/// Template which selects an output based on a boolean condition.
843pub struct ConditionalTemplate<P, T, U> {
844    pub condition: P,
845    pub true_template: T,
846    pub false_template: U,
847}
848
849impl<P, T, U> ConditionalTemplate<P, T, U> {
850    pub fn new(condition: P, true_template: T, false_template: U) -> Self
851    where
852        P: TemplateProperty<Output = bool>,
853        T: Template,
854        U: Template,
855    {
856        Self {
857            condition,
858            true_template,
859            false_template,
860        }
861    }
862}
863
864impl<P, T, U> Template for ConditionalTemplate<P, T, U>
865where
866    P: TemplateProperty<Output = bool>,
867    T: Template,
868    U: Template,
869{
870    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
871        let condition = match self.condition.extract() {
872            Ok(condition) => condition,
873            Err(err) => return formatter.handle_error(err),
874        };
875        match condition {
876            true => self.true_template.format(formatter),
877            false => self.false_template.format(formatter),
878        }
879    }
880}
881
882/// Attempts to render or extract contents in order, returns the first
883/// successful output.
884pub struct TryList<T>(Vec<T>);
885
886impl<T> TryList<T> {
887    pub fn new(contents: Vec<T>) -> Self {
888        assert!(!contents.is_empty());
889        Self(contents)
890    }
891
892    fn try_into_inner<U>(self, mut f: impl FnMut(T) -> Option<U>) -> Option<Vec<U>> {
893        self.0.into_iter().map(&mut f).collect()
894    }
895}
896
897/// Converts type-erased properties if all items support the operation.
898impl<'a> AnyTemplateProperty<'a> for TryList<BoxedAnyProperty<'a>> {
899    fn try_into_serialize(self: Box<Self>) -> Option<BoxedSerializeProperty<'a>> {
900        let properties = self.try_into_inner(|p| p.try_into_serialize())?;
901        Some(Box::new(TryList(properties)))
902    }
903
904    fn try_into_template(self: Box<Self>) -> Option<Box<dyn Template + 'a>> {
905        let templates = self.try_into_inner(|p| p.try_into_template())?;
906        Some(Box::new(TryList(templates)))
907    }
908
909    fn try_join(
910        self: Box<Self>,
911        _separator: Box<dyn Template + 'a>,
912    ) -> Option<Box<dyn Template + 'a>> {
913        // NOTE: This is implementable, but currently cannot be called.
914        None
915    }
916}
917
918impl<T: Template> Template for TryList<T> {
919    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
920        let (last, contents) = self.0.split_last().unwrap();
921        if let Some(recorder) = contents.iter().find_map(|content| {
922            let mut recorder = FormatRecorder::new(formatter.maybe_color());
923            let mut wrapper = TemplateFormatter::new(&mut recorder, propagate_property_error);
924            content.format(&mut wrapper).is_ok().then_some(recorder)
925        }) {
926            recorder.replay(formatter.as_mut())
927        } else {
928            last.format(formatter) // render the last error normally
929        }
930    }
931}
932
933impl<T: TemplateProperty> TemplateProperty for TryList<T> {
934    type Output = T::Output;
935
936    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
937        let (last, contents) = self.0.split_last().unwrap();
938        if let Some(value) = contents.iter().find_map(|p| p.extract().ok()) {
939            Ok(value)
940        } else {
941            last.extract() // propagate the last error
942        }
943    }
944}
945
946/// Adapter to apply fallible `function` to the `property`.
947///
948/// This is usually created by `TemplatePropertyExt::and_then()`/`map()`.
949pub struct TemplateFunction<P, F> {
950    pub property: P,
951    pub function: F,
952}
953
954impl<P, F> TemplateFunction<P, F> {
955    pub fn new<O>(property: P, function: F) -> Self
956    where
957        P: TemplateProperty,
958        F: Fn(P::Output) -> Result<O, TemplatePropertyError>,
959    {
960        Self { property, function }
961    }
962}
963
964impl<O, P, F> TemplateProperty for TemplateFunction<P, F>
965where
966    P: TemplateProperty,
967    F: Fn(P::Output) -> Result<O, TemplatePropertyError>,
968{
969    type Output = O;
970
971    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
972        (self.function)(self.property.extract()?)
973    }
974}
975
976/// Property which will be compiled into template once, and substituted later.
977#[derive(Clone, Debug)]
978pub struct PropertyPlaceholder<O> {
979    value: Rc<RefCell<Option<O>>>,
980}
981
982impl<O> PropertyPlaceholder<O> {
983    pub fn new() -> Self {
984        Self {
985            value: Rc::new(RefCell::new(None)),
986        }
987    }
988
989    pub fn set(&self, value: O) {
990        *self.value.borrow_mut() = Some(value);
991    }
992
993    pub fn take(&self) -> Option<O> {
994        self.value.borrow_mut().take()
995    }
996
997    pub fn with_value<R>(&self, value: O, f: impl FnOnce() -> R) -> R {
998        self.set(value);
999        let result = f();
1000        self.take();
1001        result
1002    }
1003}
1004
1005impl<O> Default for PropertyPlaceholder<O> {
1006    fn default() -> Self {
1007        Self::new()
1008    }
1009}
1010
1011impl<O: Clone> TemplateProperty for PropertyPlaceholder<O> {
1012    type Output = O;
1013
1014    fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {
1015        if let Some(value) = self.value.borrow().as_ref() {
1016            Ok(value.clone())
1017        } else {
1018            Err(TemplatePropertyError("Placeholder value is not set".into()))
1019        }
1020    }
1021}
1022
1023/// Adapter that renders compiled `template` with the `placeholder` value set.
1024pub struct TemplateRenderer<'a, C> {
1025    template: Box<dyn Template + 'a>,
1026    placeholder: PropertyPlaceholder<C>,
1027    labels: Vec<String>,
1028}
1029
1030impl<'a, C: Clone> TemplateRenderer<'a, C> {
1031    pub fn new(template: Box<dyn Template + 'a>, placeholder: PropertyPlaceholder<C>) -> Self {
1032        Self {
1033            template,
1034            placeholder,
1035            labels: Vec::new(),
1036        }
1037    }
1038
1039    /// Returns renderer that will format template with the given `labels`.
1040    ///
1041    /// This is equivalent to wrapping the content template with `label()`
1042    /// function. For example,
1043    /// `content.labeled(["foo", "bar"]).labeled(["baz"])` can be expressed as
1044    /// `label("baz", label("foo bar", content))` in template.
1045    pub fn labeled<S: Into<String>>(mut self, labels: impl IntoIterator<Item = S>) -> Self {
1046        self.labels.splice(0..0, labels.into_iter().map(Into::into));
1047        self
1048    }
1049
1050    pub fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> {
1051        let mut wrapper = TemplateFormatter::new(formatter, format_property_error_inline);
1052        self.placeholder.with_value(context.clone(), || {
1053            format_labeled(&mut wrapper, &self.template, &self.labels)
1054        })
1055    }
1056
1057    /// Renders template into buffer ignoring any color labels.
1058    ///
1059    /// The output is usually UTF-8, but it can contain arbitrary bytes such as
1060    /// file content.
1061    pub fn format_plain_text(&self, context: &C) -> Vec<u8> {
1062        let mut output = Vec::new();
1063        self.format(context, &mut PlainTextFormatter::new(&mut output))
1064            .expect("write() to vec backed formatter should never fail");
1065        output
1066    }
1067}
1068
1069/// Wrapper to pass around `Formatter` and error handler.
1070pub struct TemplateFormatter<'a> {
1071    formatter: &'a mut dyn Formatter,
1072    error_handler: PropertyErrorHandler,
1073}
1074
1075impl<'a> TemplateFormatter<'a> {
1076    fn new(formatter: &'a mut dyn Formatter, error_handler: PropertyErrorHandler) -> Self {
1077        Self {
1078            formatter,
1079            error_handler,
1080        }
1081    }
1082
1083    /// Returns function that wraps another `Formatter` with the current error
1084    /// handling strategy.
1085    ///
1086    /// This does not borrow `self` so the underlying formatter can be mutably
1087    /// borrowed.
1088    pub fn rewrap_fn(&self) -> impl Fn(&mut dyn Formatter) -> TemplateFormatter<'_> + use<> {
1089        let error_handler = self.error_handler;
1090        move |formatter| TemplateFormatter::new(formatter, error_handler)
1091    }
1092
1093    pub fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> {
1094        self.formatter.raw()
1095    }
1096
1097    pub fn labeled(&mut self, label: &str) -> LabeledScope<&mut (dyn Formatter + 'a)> {
1098        self.formatter.labeled(label)
1099    }
1100
1101    pub fn push_label(&mut self, label: &str) {
1102        self.formatter.push_label(label);
1103    }
1104
1105    pub fn pop_label(&mut self) {
1106        self.formatter.pop_label();
1107    }
1108
1109    pub fn maybe_color(&self) -> bool {
1110        self.formatter.maybe_color()
1111    }
1112
1113    pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
1114        self.formatter.write_fmt(args)
1115    }
1116
1117    /// Handles the given template property evaluation error.
1118    ///
1119    /// This usually prints the given error inline, and returns `Ok`. It's up to
1120    /// caller to decide whether or not to continue template processing on `Ok`.
1121    /// For example, `if(cond, ..)` expression will terminate if the `cond`
1122    /// failed to evaluate, whereas `concat(x, y, ..)` will continue processing.
1123    ///
1124    /// If `Err` is returned, the error should be propagated.
1125    pub fn handle_error(&mut self, err: TemplatePropertyError) -> io::Result<()> {
1126        (self.error_handler)(self.formatter, err)
1127    }
1128}
1129
1130impl<'a> AsMut<dyn Formatter + 'a> for TemplateFormatter<'a> {
1131    fn as_mut(&mut self) -> &mut (dyn Formatter + 'a) {
1132        self.formatter
1133    }
1134}
1135
1136pub fn format_joined<I, S>(
1137    formatter: &mut TemplateFormatter,
1138    contents: I,
1139    separator: S,
1140) -> io::Result<()>
1141where
1142    I: IntoIterator,
1143    I::Item: Template,
1144    S: Template,
1145{
1146    format_joined_with(formatter, contents, separator, |formatter, item| {
1147        item.format(formatter)
1148    })
1149}
1150
1151fn format_joined_with<I, S, F>(
1152    formatter: &mut TemplateFormatter,
1153    contents: I,
1154    separator: S,
1155    mut format_item: F,
1156) -> io::Result<()>
1157where
1158    I: IntoIterator,
1159    S: Template,
1160    F: FnMut(&mut TemplateFormatter, I::Item) -> io::Result<()>,
1161{
1162    let mut contents_iter = contents.into_iter().fuse();
1163    if let Some(item) = contents_iter.next() {
1164        format_item(formatter, item)?;
1165    }
1166    for item in contents_iter {
1167        separator.format(formatter)?;
1168        format_item(formatter, item)?;
1169    }
1170    Ok(())
1171}
1172
1173fn format_labeled<T: Template + ?Sized>(
1174    formatter: &mut TemplateFormatter,
1175    content: &T,
1176    labels: &[String],
1177) -> io::Result<()> {
1178    for label in labels {
1179        formatter.push_label(label);
1180    }
1181    content.format(formatter)?;
1182    for _label in labels {
1183        formatter.pop_label();
1184    }
1185    Ok(())
1186}
1187
1188type PropertyErrorHandler = fn(&mut dyn Formatter, TemplatePropertyError) -> io::Result<()>;
1189
1190/// Prints property evaluation error as inline template output.
1191fn format_property_error_inline(
1192    formatter: &mut dyn Formatter,
1193    err: TemplatePropertyError,
1194) -> io::Result<()> {
1195    let TemplatePropertyError(err) = &err;
1196    let mut formatter = formatter.labeled("error");
1197    write!(formatter, "<")?;
1198    write!(formatter.labeled("heading"), "Error: ")?;
1199    write!(formatter, "{err}")?;
1200    for err in iter::successors(err.source(), |err| err.source()) {
1201        write!(formatter, ": {err}")?;
1202    }
1203    write!(formatter, ">")?;
1204    Ok(())
1205}
1206
1207fn propagate_property_error(
1208    _formatter: &mut dyn Formatter,
1209    err: TemplatePropertyError,
1210) -> io::Result<()> {
1211    Err(io::Error::other(err.0))
1212}
1213
1214/// Creates function that renders a template to buffer and returns the buffer
1215/// only if it isn't empty.
1216///
1217/// This inherits the error handling strategy from the given `formatter`.
1218fn record_non_empty_fn<T: Template + ?Sized>(
1219    formatter: &TemplateFormatter,
1220    // TODO: T doesn't have to be captured, but "currently, all type parameters
1221    // are required to be mentioned in the precise captures list" as of rustc
1222    // 1.85.0.
1223) -> impl Fn(&T) -> Option<io::Result<FormatRecorder>> + use<T> {
1224    let rewrap = formatter.rewrap_fn();
1225    let maybe_color = formatter.maybe_color();
1226    move |template| {
1227        let mut recorder = FormatRecorder::new(maybe_color);
1228        match template.format(&mut rewrap(&mut recorder)) {
1229            Ok(()) if recorder.data().is_empty() => None, // omit empty content
1230            Ok(()) => Some(Ok(recorder)),
1231            Err(e) => Some(Err(e)),
1232        }
1233    }
1234}