From f1e6146d6d5b17992d7b60a81e71d8676984c6ac Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 29 Jan 2023 17:46:43 +0900 Subject: [PATCH] templater: add newtype to implement property over closure Many template keywords and methods are one liners, and I think that's actually good because writing tests for templater would be more involved than for pure functions. This patch introduces a wrapper for such one-line functions, and migrates method parser to that. Some of the commit keyword structs can also be ported to this wrapper. --- src/template_parser.rs | 89 +++++++++++++++++------------------------- src/templater.rs | 52 ++++++------------------ 2 files changed, 47 insertions(+), 94 deletions(-) diff --git a/src/template_parser.rs b/src/template_parser.rs index 41a71112a..693432e72 100644 --- a/src/template_parser.rs +++ b/src/template_parser.rs @@ -24,12 +24,11 @@ use pest_derive::Parser; use crate::formatter::PlainTextFormatter; use crate::templater::{ AuthorProperty, BranchProperty, ChangeIdProperty, CommitIdProperty, CommitOrChangeId, - CommitOrChangeIdShort, CommitOrChangeIdShortestPrefixAndBrackets, CommitterProperty, - ConditionalTemplate, ConflictProperty, DescriptionProperty, DivergentProperty, - DynamicLabelTemplate, EmptyProperty, FormattablePropertyTemplate, GitHeadProperty, - GitRefsProperty, HighlightPrefix, IdWithHighlightedPrefix, IsWorkingCopyProperty, - LabelTemplate, ListTemplate, Literal, SignatureTimestamp, TagProperty, Template, - TemplateFunction, TemplateProperty, WorkingCopiesProperty, + CommitterProperty, ConditionalTemplate, ConflictProperty, DescriptionProperty, + DivergentProperty, DynamicLabelTemplate, EmptyProperty, FormattablePropertyTemplate, + GitHeadProperty, GitRefsProperty, IdWithHighlightedPrefix, IsWorkingCopyProperty, + LabelTemplate, ListTemplate, Literal, TagProperty, Template, TemplateFunction, + TemplateProperty, TemplatePropertyFn, WorkingCopiesProperty, }; use crate::time_util; @@ -57,46 +56,6 @@ fn parse_string_literal(pair: Pair) -> String { result } -struct StringFirstLine; - -impl TemplateProperty for StringFirstLine { - type Output = String; - - fn extract(&self, context: &String) -> Self::Output { - context.lines().next().unwrap().to_string() - } -} - -struct SignatureName; - -impl TemplateProperty for SignatureName { - type Output = String; - - fn extract(&self, context: &Signature) -> Self::Output { - context.name.clone() - } -} - -struct SignatureEmail; - -impl TemplateProperty for SignatureEmail { - type Output = String; - - fn extract(&self, context: &Signature) -> Self::Output { - context.email.clone() - } -} - -struct RelativeTimestampString; - -impl TemplateProperty for RelativeTimestampString { - type Output = String; - - fn extract(&self, context: &Timestamp) -> Self::Output { - time_util::format_timestamp_relative_to_now(context) - } -} - enum Property<'a, I> { String(Box + 'a>), Boolean(Box + 'a>), @@ -181,9 +140,14 @@ fn parse_method_chain<'a, I: 'a>( } fn parse_string_method<'a>(name: Pair, _args: Pairs) -> Property<'a, String> { + fn wrap_fn<'a, O>( + f: impl Fn(&String) -> O + 'a, + ) -> Box + 'a> { + Box::new(TemplatePropertyFn(f)) + } // TODO: validate arguments match name.as_str() { - "first_line" => Property::String(Box::new(StringFirstLine)), + "first_line" => Property::String(wrap_fn(|s| s.lines().next().unwrap().to_string())), name => panic!("no such string method: {name}"), } } @@ -197,31 +161,48 @@ fn parse_commit_or_change_id_method<'a>( name: Pair, _args: Pairs, ) -> Property<'a, CommitOrChangeId<'a>> { + fn wrap_fn<'a, O>( + f: impl Fn(&CommitOrChangeId<'a>) -> O + 'a, + ) -> Box, Output = O> + 'a> { + Box::new(TemplatePropertyFn(f)) + } // TODO: validate arguments match name.as_str() { - "short" => Property::String(Box::new(CommitOrChangeIdShort)), + "short" => Property::String(wrap_fn(|id| id.short())), "shortest_prefix_and_brackets" => { - Property::String(Box::new(CommitOrChangeIdShortestPrefixAndBrackets)) + Property::String(wrap_fn(|id| id.shortest_prefix_and_brackets())) + } + "shortest_styled_prefix" => { + Property::IdWithHighlightedPrefix(wrap_fn(|id| id.shortest_styled_prefix())) } - "shortest_styled_prefix" => Property::IdWithHighlightedPrefix(Box::new(HighlightPrefix)), name => panic!("no such commit ID method: {name}"), } } fn parse_signature_method<'a>(name: Pair, _args: Pairs) -> Property<'a, Signature> { + fn wrap_fn<'a, O>( + f: impl Fn(&Signature) -> O + 'a, + ) -> Box + 'a> { + Box::new(TemplatePropertyFn(f)) + } // TODO: validate arguments match name.as_str() { - "name" => Property::String(Box::new(SignatureName)), - "email" => Property::String(Box::new(SignatureEmail)), - "timestamp" => Property::Timestamp(Box::new(SignatureTimestamp)), + "name" => Property::String(wrap_fn(|signature| signature.name.clone())), + "email" => Property::String(wrap_fn(|signature| signature.email.clone())), + "timestamp" => Property::Timestamp(wrap_fn(|signature| signature.timestamp.clone())), name => panic!("no such commit ID method: {name}"), } } fn parse_timestamp_method<'a>(name: Pair, _args: Pairs) -> Property<'a, Timestamp> { + fn wrap_fn<'a, O>( + f: impl Fn(&Timestamp) -> O + 'a, + ) -> Box + 'a> { + Box::new(TemplatePropertyFn(f)) + } // TODO: validate arguments match name.as_str() { - "ago" => Property::String(Box::new(RelativeTimestampString)), + "ago" => Property::String(wrap_fn(time_util::format_timestamp_relative_to_now)), name => panic!("no such timestamp method: {name}"), } } diff --git a/src/templater.rs b/src/templater.rs index e6d346a36..8b3cac319 100644 --- a/src/templater.rs +++ b/src/templater.rs @@ -168,6 +168,17 @@ impl TemplateProperty for Literal { } } +/// 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, @@ -494,7 +505,7 @@ impl CommitOrChangeId<'_> { hex } - fn shortest_prefix_and_brackets(&self) -> String { + pub fn shortest_prefix_and_brackets(&self) -> String { let hex = self.hex(); let (prefix, rest) = extract_entire_prefix_and_trimmed_tail( &hex, @@ -586,35 +597,6 @@ impl Template<()> for IdWithHighlightedPrefix { } } -pub struct HighlightPrefix; -impl TemplateProperty> for HighlightPrefix { - type Output = IdWithHighlightedPrefix; - - fn extract(&self, context: &CommitOrChangeId) -> Self::Output { - context.shortest_styled_prefix() - } -} - -pub struct CommitOrChangeIdShort; - -impl TemplateProperty> for CommitOrChangeIdShort { - type Output = String; - - fn extract(&self, context: &CommitOrChangeId) -> Self::Output { - context.short() - } -} - -pub struct CommitOrChangeIdShortestPrefixAndBrackets; - -impl TemplateProperty> for CommitOrChangeIdShortestPrefixAndBrackets { - type Output = String; - - fn extract(&self, context: &CommitOrChangeId) -> Self::Output { - context.shortest_prefix_and_brackets() - } -} - pub struct CommitIdProperty<'a> { pub repo: RepoRef<'a>, } @@ -645,16 +627,6 @@ impl<'a> TemplateProperty for ChangeIdProperty<'a> { } } -pub struct SignatureTimestamp; - -impl TemplateProperty for SignatureTimestamp { - type Output = Timestamp; - - fn extract(&self, context: &Signature) -> Self::Output { - context.timestamp.clone() - } -} - pub struct EmptyProperty<'a> { pub repo: RepoRef<'a>, }