From e8fd12aff68e0f812599bef09dfb041d204e523c Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Mon, 6 Mar 2023 22:02:39 +0900 Subject: [PATCH] templater: add list.join(separator) method The implementation is a bit tricky since we have to combine a property (of C -> Vec> type) and a separator of Template type. --- docs/templates.md | 5 ++++- src/template_parser.rs | 25 +++++++++++++++++-------- src/templater.rs | 38 ++++++++++++++++++++++++++++++++++++++ tests/test_templater.rs | 13 +++++++++++++ 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/docs/templates.md b/docs/templates.md index 5976fc90c..05a6eee47 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -86,7 +86,10 @@ No methods are defined. ### List type -No methods are defined. +The following methods are defined. + +* `.join(separator: Template) -> Template`: Concatenate elements with + the given `separator`. ### OperationId type diff --git a/src/template_parser.rs b/src/template_parser.rs index e02a8a9a9..6bb4e0a1e 100644 --- a/src/template_parser.rs +++ b/src/template_parser.rs @@ -25,9 +25,9 @@ use pest_derive::Parser; use thiserror::Error; use crate::templater::{ - ConcatTemplate, ConditionalTemplate, IndentTemplate, IntoTemplate, LabelTemplate, Literal, - PlainTextFormattedProperty, SeparateTemplate, Template, TemplateFunction, TemplateProperty, - TimestampRange, + ConcatTemplate, ConditionalTemplate, FormattablePropertyListTemplate, IndentTemplate, + IntoTemplate, LabelTemplate, Literal, PlainTextFormattedProperty, SeparateTemplate, Template, + TemplateFunction, TemplateProperty, TimestampRange, }; use crate::time_util; @@ -1048,13 +1048,22 @@ fn build_timestamp_range_method<'a, L: TemplateLanguage<'a>>( Ok(property) } -pub fn build_list_method<'a, L: TemplateLanguage<'a>, P>( - _language: &L, - _self_property: impl TemplateProperty> + 'a, +pub fn build_list_method<'a, L: TemplateLanguage<'a>, P: Template<()> + 'a>( + language: &L, + self_property: impl TemplateProperty> + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { - // TODO: .join(separator), .map(), ... - Err(TemplateParseError::no_such_method("List", function)) + let property = match function.name { + "join" => { + let [separator_node] = expect_exact_arguments(function)?; + let separator = build_expression(language, separator_node)?.into_template(); + let template = FormattablePropertyListTemplate::new(self_property, separator); + language.wrap_template(template) + } + // TODO: .map() + _ => return Err(TemplateParseError::no_such_method("List", function)), + }; + Ok(property) } fn build_global_function<'a, L: TemplateLanguage<'a>>( diff --git a/src/templater.rs b/src/templater.rs index 51bb34077..f19b15c44 100644 --- a/src/templater.rs +++ b/src/templater.rs @@ -378,6 +378,44 @@ impl> TemplateProperty for PlainTextFormattedProperty { } } +/// Renders a list of template properties with the given separator. +/// +/// Each template property can be extracted as a context-less value, but +/// the separator takes a context of type `C`. +pub struct FormattablePropertyListTemplate { + property: P, + separator: S, +} + +impl FormattablePropertyListTemplate { + pub fn new(property: P, separator: S) -> Self + where + P: TemplateProperty, + P::Output: IntoIterator, + ::Item: Template<()>, + S: Template, + { + FormattablePropertyListTemplate { + property, + separator, + } + } +} + +impl Template for FormattablePropertyListTemplate +where + P: TemplateProperty, + P::Output: IntoIterator, + ::Item: Template<()>, + S: Template, +{ + fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> { + let contents = self.property.extract(context); + let contents_iter = contents.into_iter().map(Literal); // as Template + format_joined(context, formatter, contents_iter, &self.separator) + } +} + pub struct ConditionalTemplate { pub condition: P, pub true_template: T, diff --git a/tests/test_templater.rs b/tests/test_templater.rs index 7b2d1e3f4..7f88c6971 100644 --- a/tests/test_templater.rs +++ b/tests/test_templater.rs @@ -236,6 +236,19 @@ fn test_templater_parse_error() { "###); } +#[test] +fn test_templater_list_method() { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]); + let repo_path = test_env.env_root().join("repo"); + let render = |template| get_template_output(&test_env, &repo_path, "@-", template); + + insta::assert_snapshot!(render(r#""".lines().join("|")"#), @""); + insta::assert_snapshot!(render(r#""a\nb\nc".lines().join("|")"#), @"a|b|c"); + // Keyword as separator + insta::assert_snapshot!(render(r#""a\nb\nc".lines().join(commit_id.short(2))"#), @"a00b00c"); +} + #[test] fn test_templater_string_method() { let test_env = TestEnvironment::default();