ok/jj
1
0
Fork 0
forked from mirrors/jj

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.
This commit is contained in:
Yuya Nishihara 2023-01-29 17:46:43 +09:00
parent 67eac2a455
commit f1e6146d6d
2 changed files with 47 additions and 94 deletions

View file

@ -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<Rule>) -> String {
result
}
struct StringFirstLine;
impl TemplateProperty<String> for StringFirstLine {
type Output = String;
fn extract(&self, context: &String) -> Self::Output {
context.lines().next().unwrap().to_string()
}
}
struct SignatureName;
impl TemplateProperty<Signature> for SignatureName {
type Output = String;
fn extract(&self, context: &Signature) -> Self::Output {
context.name.clone()
}
}
struct SignatureEmail;
impl TemplateProperty<Signature> for SignatureEmail {
type Output = String;
fn extract(&self, context: &Signature) -> Self::Output {
context.email.clone()
}
}
struct RelativeTimestampString;
impl TemplateProperty<Timestamp> 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<dyn TemplateProperty<I, Output = String> + 'a>),
Boolean(Box<dyn TemplateProperty<I, Output = bool> + 'a>),
@ -181,9 +140,14 @@ fn parse_method_chain<'a, I: 'a>(
}
fn parse_string_method<'a>(name: Pair<Rule>, _args: Pairs<Rule>) -> Property<'a, String> {
fn wrap_fn<'a, O>(
f: impl Fn(&String) -> O + 'a,
) -> Box<dyn TemplateProperty<String, Output = O> + '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<Rule>,
_args: Pairs<Rule>,
) -> Property<'a, CommitOrChangeId<'a>> {
fn wrap_fn<'a, O>(
f: impl Fn(&CommitOrChangeId<'a>) -> O + 'a,
) -> Box<dyn TemplateProperty<CommitOrChangeId<'a>, 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<Rule>, _args: Pairs<Rule>) -> Property<'a, Signature> {
fn wrap_fn<'a, O>(
f: impl Fn(&Signature) -> O + 'a,
) -> Box<dyn TemplateProperty<Signature, Output = O> + '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<Rule>, _args: Pairs<Rule>) -> Property<'a, Timestamp> {
fn wrap_fn<'a, O>(
f: impl Fn(&Timestamp) -> O + 'a,
) -> Box<dyn TemplateProperty<Timestamp, Output = O> + '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}"),
}
}

View file

@ -168,6 +168,17 @@ impl<C, O: Clone> TemplateProperty<C> for Literal<O> {
}
}
/// Adapter to turn closure into property.
pub struct TemplatePropertyFn<F>(pub F);
impl<C, O, F: Fn(&C) -> O> TemplateProperty<C> for TemplatePropertyFn<F> {
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<P> {
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<CommitOrChangeId<'_>> for HighlightPrefix {
type Output = IdWithHighlightedPrefix;
fn extract(&self, context: &CommitOrChangeId) -> Self::Output {
context.shortest_styled_prefix()
}
}
pub struct CommitOrChangeIdShort;
impl TemplateProperty<CommitOrChangeId<'_>> for CommitOrChangeIdShort {
type Output = String;
fn extract(&self, context: &CommitOrChangeId) -> Self::Output {
context.short()
}
}
pub struct CommitOrChangeIdShortestPrefixAndBrackets;
impl TemplateProperty<CommitOrChangeId<'_>> 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<Commit> for ChangeIdProperty<'a> {
}
}
pub struct SignatureTimestamp;
impl TemplateProperty<Signature> for SignatureTimestamp {
type Output = Timestamp;
fn extract(&self, context: &Signature) -> Self::Output {
context.timestamp.clone()
}
}
pub struct EmptyProperty<'a> {
pub repo: RepoRef<'a>,
}