// 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. use std::cell::RefCell; use std::io; use std::rc::Rc; use jj_lib::backend::{Signature, Timestamp}; use crate::formatter::{FormatRecorder, Formatter, PlainTextFormatter}; use crate::time_util; pub trait Template { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()>; } /// Template that supports list-like behavior. pub trait ListTemplate: Template { /// Concatenates items with the given separator. fn join<'a>(self: Box, separator: Box + 'a>) -> Box + 'a> where Self: 'a, C: 'a; /// Upcasts to the template type. fn into_template<'a>(self: Box) -> Box + 'a> where Self: 'a; } pub trait IntoTemplate<'a, C> { fn into_template(self) -> Box + 'a>; } impl + ?Sized> Template for &T { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { >::format(self, context, formatter) } } impl + ?Sized> Template for Box { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { >::format(self, context, formatter) } } impl Template<()> for Signature { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> 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, "<")?; write!(formatter.labeled("email"), "{}", self.email)?; write!(formatter, ">")?; } Ok(()) } } impl Template<()> for String { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { formatter.write_str(self) } } impl Template<()> for &str { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { formatter.write_str(self) } } impl Template<()> for Timestamp { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { formatter.write_str(&time_util::format_absolute_timestamp(self)) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct TimestampRange { // Could be aliased to Range if needed. pub start: Timestamp, pub end: Timestamp, } impl TimestampRange { // TODO: Introduce duration type, and move formatting to it. pub fn duration(&self) -> String { let mut f = timeago::Formatter::new(); f.min_unit(timeago::TimeUnit::Microseconds).ago(""); let duration = time_util::format_duration(&self.start, &self.end, &f); if duration == "now" { "less than a microsecond".to_owned() } else { duration } } } impl Template<()> for TimestampRange { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { self.start.format(&(), formatter)?; write!(formatter, " - ")?; self.end.format(&(), formatter)?; Ok(()) } } impl Template<()> for Vec { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { format_joined(&(), formatter, self, " ") } } impl Template<()> for bool { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { formatter.write_str(if *self { "true" } else { "false" }) } } impl Template<()> for i64 { fn format(&self, _: &(), formatter: &mut dyn Formatter) -> 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>, { LabelTemplate { content, labels } } } impl Template for LabelTemplate where T: Template, L: TemplateProperty>, { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { let labels = self.labels.extract(context); for label in &labels { formatter.push_label(label)?; } self.content.format(context, formatter)?; for _label in &labels { formatter.pop_label()?; } Ok(()) } } pub struct ConcatTemplate(pub Vec); impl> Template for ConcatTemplate { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { for template in &self.0 { template.format(context, 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(&C, &mut dyn Formatter, &FormatRecorder) -> io::Result<()>, { ReformatTemplate { content, reformat } } } impl Template for ReformatTemplate where T: Template, F: Fn(&C, &mut dyn Formatter, &FormatRecorder) -> io::Result<()>, { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { let mut recorder = FormatRecorder::new(); self.content.format(context, &mut recorder)?; (self.reformat)(context, formatter, &recorder) } } /// Like `ConcatTemplate`, but inserts a separator between non-empty templates. pub struct SeparateTemplate { separator: S, contents: Vec, } impl SeparateTemplate { pub fn new(separator: S, contents: Vec) -> Self where S: Template, T: Template, { SeparateTemplate { separator, contents, } } } impl Template for SeparateTemplate where S: Template, T: Template, { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { let mut content_recorders = self .contents .iter() .filter_map(|template| { let mut recorder = FormatRecorder::new(); match template.format(context, &mut recorder) { Ok(()) if recorder.data().is_empty() => None, // omit empty content Ok(()) => Some(Ok(recorder)), Err(e) => Some(Err(e)), } }) .fuse(); if let Some(recorder) = content_recorders.next() { recorder?.replay(formatter)?; } for recorder in content_recorders { self.separator.format(context, formatter)?; recorder?.replay(formatter)?; } Ok(()) } } pub trait TemplateProperty { type Output; fn extract(&self, context: &C) -> Self::Output; } impl + ?Sized> TemplateProperty for Box

{ type Output =

>::Output; fn extract(&self, context: &C) -> Self::Output {

>::extract(self, context) } } impl> TemplateProperty for Option

{ type Output = Option; fn extract(&self, context: &C) -> Self::Output { self.as_ref().map(|property| property.extract(context)) } } // Implement TemplateProperty for tuples macro_rules! tuple_impls { ($( ( $($n:tt $T:ident),+ ) )+) => { $( impl,)+> TemplateProperty for ($($T,)+) { type Output = ($($T::Output,)+); fn extract(&self, context: &C) -> Self::Output { ($(self.$n.extract(context),)+) } } )+ } } tuple_impls! { (0 T0) (0 T0, 1 T1) (0 T0, 1 T1, 2 T2) (0 T0, 1 T1, 2 T2, 3 T3) } /// Adapter to drop template context. pub struct Literal(pub O); impl> Template for Literal { fn format(&self, _context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { self.0.format(&(), formatter) } } impl TemplateProperty for Literal { type Output = O; fn extract(&self, _context: &C) -> O { self.0.clone() } } /// Adapter to turn closure into property. pub struct TemplatePropertyFn(pub F); impl O> TemplateProperty for TemplatePropertyFn { type Output = O; fn extract(&self, context: &C) -> Self::Output { (self.0)(context) } } /// Adapter to extract context-less 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<()>, { FormattablePropertyTemplate { property } } } impl Template for FormattablePropertyTemplate

where P: TemplateProperty, P::Output: Template<()>, { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { let template = self.property.extract(context); template.format(&(), formatter) } } impl<'a, C: 'a, O> IntoTemplate<'a, C> for Box + 'a> where O: Template<()> + 'a, { fn into_template(self) -> Box + 'a> { Box::new(FormattablePropertyTemplate::new(self)) } } /// Adapter to turn template back to string property. pub struct PlainTextFormattedProperty { template: T, } impl PlainTextFormattedProperty { pub fn new(template: T) -> Self { PlainTextFormattedProperty { template } } } impl> TemplateProperty for PlainTextFormattedProperty { type Output = String; fn extract(&self, context: &C) -> Self::Output { let mut output = vec![]; self.template .format(context, &mut PlainTextFormatter::new(&mut output)) .expect("write() to PlainTextFormatter should never fail"); // TODO: Use from_utf8_lossy() if we added template that embeds file content String::from_utf8(output).expect("template output should be utf-8 bytes") } } /// Renders template property of list type with the given separator. /// /// Each list item will be formatted by the given `format_item()` function. /// The separator takes a context of type `C`. 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(&C, &mut dyn Formatter, O) -> io::Result<()>, { ListPropertyTemplate { property, separator, format_item, } } } impl Template for ListPropertyTemplate where P: TemplateProperty, P::Output: IntoIterator, S: Template, F: Fn(&C, &mut dyn Formatter, O) -> io::Result<()>, { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { let contents = self.property.extract(context); format_joined_with( context, formatter, contents, &self.separator, &self.format_item, ) } } impl ListTemplate for ListPropertyTemplate where P: TemplateProperty, P::Output: IntoIterator, S: Template, F: Fn(&C, &mut dyn Formatter, O) -> io::Result<()>, { fn join<'a>(self: Box, separator: Box + 'a>) -> Box + 'a> where Self: 'a, C: 'a, { // Once join()-ed, list-like API should be dropped. This is guaranteed by // the return type. Box::new(ListPropertyTemplate::new( self.property, separator, self.format_item, )) } fn into_template<'a>(self: Box) -> Box + 'a> where Self: 'a, { self } } pub struct ConditionalTemplate { pub condition: P, pub true_template: T, pub false_template: Option, } impl ConditionalTemplate { pub fn new(condition: P, true_template: T, false_template: Option) -> Self where P: TemplateProperty, T: Template, U: Template, { ConditionalTemplate { condition, true_template, false_template, } } } impl Template for ConditionalTemplate where P: TemplateProperty, T: Template, U: Template, { fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { if self.condition.extract(context) { self.true_template.format(context, formatter)?; } else if let Some(false_template) = &self.false_template { false_template.format(context, formatter)?; } Ok(()) } } // TODO: If needed, add a ContextualTemplateFunction where the function also // gets the context 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) -> O, { TemplateFunction { property, function } } } impl TemplateProperty for TemplateFunction where P: TemplateProperty, F: Fn(P::Output) -> O, { type Output = O; fn extract(&self, context: &C) -> Self::Output { (self.function)(self.property.extract(context)) } } /// 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 { PropertyPlaceholder { 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, _: &C) -> Self::Output { self.value .borrow() .as_ref() .expect("placeholder value must be set before evaluating template") .clone() } } pub fn format_joined( context: &C, formatter: &mut dyn Formatter, contents: I, separator: S, ) -> io::Result<()> where I: IntoIterator, I::Item: Template, S: Template, { format_joined_with( context, formatter, contents, separator, |context, formatter, item| item.format(context, formatter), ) } fn format_joined_with( context: &C, formatter: &mut dyn Formatter, contents: I, separator: S, mut format_item: F, ) -> io::Result<()> where I: IntoIterator, S: Template, F: FnMut(&C, &mut dyn Formatter, I::Item) -> io::Result<()>, { let mut contents_iter = contents.into_iter().fuse(); if let Some(item) = contents_iter.next() { format_item(context, formatter, item)?; } for item in contents_iter { separator.format(context, formatter)?; format_item(context, formatter, item)?; } Ok(()) }