templater: add list.join(separator) method

The implementation is a bit tricky since we have to combine a property
(of C -> Vec<Template<()>> type) and a separator of Template<C> type.
This commit is contained in:
Yuya Nishihara 2023-03-06 22:02:39 +09:00
parent 6c146de2e8
commit e8fd12aff6
4 changed files with 72 additions and 9 deletions

View file

@ -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

View file

@ -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<L::Context, Output = Vec<P>> + 'a,
pub fn build_list_method<'a, L: TemplateLanguage<'a>, P: Template<()> + 'a>(
language: &L,
self_property: impl TemplateProperty<L::Context, Output = Vec<P>> + 'a,
function: &FunctionCallNode,
) -> TemplateParseResult<L::Property> {
// 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>>(

View file

@ -378,6 +378,44 @@ impl<C, T: Template<C>> TemplateProperty<C> for PlainTextFormattedProperty<T> {
}
}
/// 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<P, S> {
property: P,
separator: S,
}
impl<P, S> FormattablePropertyListTemplate<P, S> {
pub fn new<C>(property: P, separator: S) -> Self
where
P: TemplateProperty<C>,
P::Output: IntoIterator,
<P::Output as IntoIterator>::Item: Template<()>,
S: Template<C>,
{
FormattablePropertyListTemplate {
property,
separator,
}
}
}
impl<C, P, S> Template<C> for FormattablePropertyListTemplate<P, S>
where
P: TemplateProperty<C>,
P::Output: IntoIterator,
<P::Output as IntoIterator>::Item: Template<()>,
S: Template<C>,
{
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<C>
format_joined(context, formatter, contents_iter, &self.separator)
}
}
pub struct ConditionalTemplate<P, T, U> {
pub condition: P,
pub true_template: T,

View file

@ -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();