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