// Copyright 2020 Google LLC // // 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::borrow::BorrowMut; use std::ops::Add; use jujube_lib::commit::Commit; use jujube_lib::repo::RepoRef; use jujube_lib::store::{CommitId, Signature}; use crate::styler::Styler; pub trait Template { fn format(&self, context: &C, styler: &mut dyn Styler); } // TODO: Extract a trait for this type? pub struct TemplateFormatter<'s, 't: 's, C> { template: Box + 't>, styler: &'s mut dyn Styler, } impl<'s, 't: 's, C> TemplateFormatter<'s, 't, C> { pub fn new(template: Box + 't>, styler: &'s mut dyn Styler) -> Self { TemplateFormatter { template, styler } } pub fn format<'c, 'a: 'c>(&'a mut self, context: &'c C) { self.template.format(context, self.styler.borrow_mut()); } } pub struct LiteralTemplate(pub String); impl Template for LiteralTemplate { fn format(&self, _context: &C, styler: &mut dyn Styler) { styler.write_str(&self.0) } } // TODO: figure out why this lifetime is needed pub struct LabelTemplate<'a, C> { content: Box + 'a>, labels: Vec, } impl<'a, C> LabelTemplate<'a, C> { pub fn new(content: Box + 'a>, labels: String) -> Self { let labels: Vec = labels .split_whitespace() .map(|label| label.to_string()) .collect(); LabelTemplate { content, labels } } } impl<'a, C> Template for LabelTemplate<'a, C> { fn format(&self, context: &C, styler: &mut dyn Styler) { for label in &self.labels { styler.add_label(label.clone()); } self.content.format(context, styler); for _label in &self.labels { styler.remove_label(); } } } // TODO: figure out why this lifetime is needed pub struct DynamicLabelTemplate<'a, C> { content: Box + 'a>, label_property: Box String + 'a>, } impl<'a, C> DynamicLabelTemplate<'a, C> { pub fn new( content: Box + 'a>, label_property: Box String + 'a>, ) -> Self { DynamicLabelTemplate { content, label_property, } } } impl<'a, C> Template for DynamicLabelTemplate<'a, C> { fn format(&self, context: &C, styler: &mut dyn Styler) { let labels = self.label_property.as_ref()(context); let labels: Vec = labels .split_whitespace() .map(|label| label.to_string()) .collect(); for label in &labels { styler.add_label(label.clone()); } self.content.format(context, styler); for _label in &labels { styler.remove_label(); } } } // TODO: figure out why this lifetime is needed pub struct ListTemplate<'a, C>(pub Vec + 'a>>); impl<'a, C> Template for ListTemplate<'a, C> { fn format(&self, context: &C, styler: &mut dyn Styler) { for template in &self.0 { template.format(context, styler) } } } pub trait TemplateProperty { fn extract(&self, context: &C) -> O; } pub struct ConstantTemplateProperty { pub output: O, } impl TemplateProperty for ConstantTemplateProperty { fn extract(&self, _context: &C) -> O { self.output.clone() } } // TODO: figure out why this lifetime is needed pub struct StringPropertyTemplate<'a, C> { pub property: Box + 'a>, } impl<'a, C> Template for StringPropertyTemplate<'a, C> { fn format(&self, context: &C, styler: &mut dyn Styler) { let text = self.property.extract(context); styler.write_str(&text); } } pub struct ChangeIdProperty; impl<'r> TemplateProperty for ChangeIdProperty { fn extract(&self, context: &Commit) -> String { context.change_id().hex() } } pub struct DescriptionProperty; impl<'r> TemplateProperty for DescriptionProperty { fn extract(&self, context: &Commit) -> String { let description = context.description().to_owned(); if description.ends_with('\n') { description } else { description.add("\n") } } } pub struct AuthorProperty; impl<'r> TemplateProperty for AuthorProperty { fn extract(&self, context: &Commit) -> Signature { context.author().clone() } } pub struct CommitterProperty; impl<'r> TemplateProperty for CommitterProperty { fn extract(&self, context: &Commit) -> Signature { context.committer().clone() } } pub struct OpenProperty; impl<'r> TemplateProperty for OpenProperty { fn extract(&self, context: &Commit) -> bool { context.is_open() } } pub struct PrunedProperty; impl TemplateProperty for PrunedProperty { fn extract(&self, context: &Commit) -> bool { context.is_pruned() } } pub struct CurrentCheckoutProperty<'a, 'r> { pub repo: RepoRef<'a, 'r>, } impl TemplateProperty for CurrentCheckoutProperty<'_, '_> { fn extract(&self, context: &Commit) -> bool { context.id() == self.repo.view().checkout() } } pub struct GitRefsProperty<'a, 'r> { pub repo: RepoRef<'a, 'r>, } impl TemplateProperty for GitRefsProperty<'_, '_> { fn extract(&self, context: &Commit) -> String { let refs: Vec<_> = self .repo .view() .git_refs() .iter() .filter(|(_name, id)| *id == context.id()) .map(|(name, _id)| name.clone()) .collect(); refs.join(" ") } } pub struct ObsoleteProperty<'a, 'r> { pub repo: RepoRef<'a, 'r>, } impl TemplateProperty for ObsoleteProperty<'_, '_> { fn extract(&self, context: &Commit) -> bool { self.repo.evolution().is_obsolete(context.id()) } } pub struct OrphanProperty<'a, 'r> { pub repo: RepoRef<'a, 'r>, } impl TemplateProperty for OrphanProperty<'_, '_> { fn extract(&self, context: &Commit) -> bool { self.repo.evolution().is_orphan(context.id()) } } pub struct DivergentProperty<'a, 'r> { pub repo: RepoRef<'a, 'r>, } impl TemplateProperty for DivergentProperty<'_, '_> { fn extract(&self, context: &Commit) -> bool { self.repo.evolution().is_divergent(context.change_id()) } } pub struct ConflictProperty; impl TemplateProperty for ConflictProperty { fn extract(&self, context: &Commit) -> bool { context.tree().has_conflict() } } pub struct ConditionalTemplate<'a, C> { pub condition: Box + 'a>, pub true_template: Box + 'a>, pub false_template: Option + 'a>>, } // TODO: figure out why this lifetime is needed impl<'a, C> ConditionalTemplate<'a, C> { pub fn new( condition: Box + 'a>, true_template: Box + 'a>, false_template: Option + 'a>>, ) -> Self { ConditionalTemplate { condition, true_template, false_template, } } } impl<'a, C> Template for ConditionalTemplate<'a, C> { fn format(&self, context: &C, styler: &mut dyn Styler) { if self.condition.extract(context) { self.true_template.format(context, styler); } else if let Some(false_template) = &self.false_template { false_template.format(context, styler); } } } // TODO: If needed, add a ContextualTemplateFunction where the function also // gets the context pub struct TemplateFunction<'a, C, I, O> { pub property: Box + 'a>, pub function: Box O + 'a>, } // TODO: figure out why this lifetime is needed impl<'a, C, I, O> TemplateFunction<'a, C, I, O> { pub fn new( template: Box + 'a>, function: Box O + 'a>, ) -> Self { TemplateFunction { property: template, function, } } } impl<'a, C, I, O> TemplateProperty for TemplateFunction<'a, C, I, O> { fn extract(&self, context: &C) -> O { (self.function)(self.property.extract(context)) } } pub struct CommitIdKeyword; impl CommitIdKeyword { pub fn default_format(commit_id: CommitId) -> String { commit_id.hex() } pub fn shortest_format(commit_id: CommitId) -> String { // TODO: make this actually be the shortest unambiguous prefix commit_id.hex()[..12].to_string() } } impl<'r> TemplateProperty for CommitIdKeyword { fn extract(&self, context: &Commit) -> CommitId { context.id().clone() } }