mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-17 13:14:26 +00:00
templater: add separate(sep, contents...) function
This is a copy of Mercurial's separate() function.
This commit is contained in:
parent
84ee0edc51
commit
13b5661094
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::{RangeFrom, RangeInclusive};
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
@ -29,8 +29,8 @@ use thiserror::Error;
|
|||||||
use crate::templater::{
|
use crate::templater::{
|
||||||
BranchProperty, CommitOrChangeId, ConditionalTemplate, FormattablePropertyTemplate,
|
BranchProperty, CommitOrChangeId, ConditionalTemplate, FormattablePropertyTemplate,
|
||||||
GitHeadProperty, GitRefsProperty, IdWithHighlightedPrefix, LabelTemplate, ListTemplate,
|
GitHeadProperty, GitRefsProperty, IdWithHighlightedPrefix, LabelTemplate, ListTemplate,
|
||||||
Literal, PlainTextFormattedProperty, TagProperty, Template, TemplateFunction, TemplateProperty,
|
Literal, PlainTextFormattedProperty, SeparateTemplate, TagProperty, Template, TemplateFunction,
|
||||||
TemplatePropertyFn, WorkingCopiesProperty,
|
TemplateProperty, TemplatePropertyFn, WorkingCopiesProperty,
|
||||||
};
|
};
|
||||||
use crate::{cli_util, time_util};
|
use crate::{cli_util, time_util};
|
||||||
|
|
||||||
@ -59,6 +59,8 @@ pub enum TemplateParseErrorKind {
|
|||||||
InvalidArgumentCountExact(usize),
|
InvalidArgumentCountExact(usize),
|
||||||
#[error("Expected {} to {} arguments", .0.start(), .0.end())]
|
#[error("Expected {} to {} arguments", .0.start(), .0.end())]
|
||||||
InvalidArgumentCountRange(RangeInclusive<usize>),
|
InvalidArgumentCountRange(RangeInclusive<usize>),
|
||||||
|
#[error("Expected at least {} arguments", .0.start)]
|
||||||
|
InvalidArgumentCountRangeFrom(RangeFrom<usize>),
|
||||||
#[error(r#"Expected argument of type "{0}""#)]
|
#[error(r#"Expected argument of type "{0}""#)]
|
||||||
InvalidArgumentType(String),
|
InvalidArgumentType(String),
|
||||||
}
|
}
|
||||||
@ -112,6 +114,13 @@ impl TemplateParseError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invalid_argument_count_range_from(count: RangeFrom<usize>, span: pest::Span<'_>) -> Self {
|
||||||
|
TemplateParseError::with_span(
|
||||||
|
TemplateParseErrorKind::InvalidArgumentCountRangeFrom(count),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn invalid_argument_type(expected_type_name: impl Into<String>, span: pest::Span<'_>) -> Self {
|
fn invalid_argument_type(expected_type_name: impl Into<String>, span: pest::Span<'_>) -> Self {
|
||||||
TemplateParseError::with_span(
|
TemplateParseError::with_span(
|
||||||
TemplateParseErrorKind::InvalidArgumentType(expected_type_name.into()),
|
TemplateParseErrorKind::InvalidArgumentType(expected_type_name.into()),
|
||||||
@ -533,6 +542,21 @@ fn parse_commit_term<'a>(
|
|||||||
));
|
));
|
||||||
Expression::Template(template)
|
Expression::Template(template)
|
||||||
}
|
}
|
||||||
|
"separate" => {
|
||||||
|
let arg_count_error =
|
||||||
|
|| TemplateParseError::invalid_argument_count_range_from(1.., args_span);
|
||||||
|
let separator_pair = args.next().ok_or_else(arg_count_error)?;
|
||||||
|
let separator = parse_commit_template_rule(repo, workspace_id, separator_pair)?
|
||||||
|
.into_template();
|
||||||
|
let contents = args
|
||||||
|
.map(|pair| {
|
||||||
|
parse_commit_template_rule(repo, workspace_id, pair)
|
||||||
|
.map(|x| x.into_template())
|
||||||
|
})
|
||||||
|
.try_collect()?;
|
||||||
|
let template = Box::new(SeparateTemplate::new(separator, contents));
|
||||||
|
Expression::Template(template)
|
||||||
|
}
|
||||||
_ => return Err(TemplateParseError::no_such_function(&name)),
|
_ => return Err(TemplateParseError::no_such_function(&name)),
|
||||||
};
|
};
|
||||||
Ok(expression)
|
Ok(expression)
|
||||||
|
@ -135,6 +135,57 @@ impl<C, T: Template<C>> Template<C> for ListTemplate<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `ListTemplate`, but inserts a separator between non-empty templates.
|
||||||
|
pub struct SeparateTemplate<S, T> {
|
||||||
|
separator: S,
|
||||||
|
contents: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> SeparateTemplate<S, T> {
|
||||||
|
pub fn new<C>(separator: S, contents: Vec<T>) -> Self
|
||||||
|
where
|
||||||
|
S: Template<C>,
|
||||||
|
T: Template<C>,
|
||||||
|
{
|
||||||
|
SeparateTemplate {
|
||||||
|
separator,
|
||||||
|
contents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, S, T> Template<C> for SeparateTemplate<S, T>
|
||||||
|
where
|
||||||
|
S: Template<C>,
|
||||||
|
T: Template<C>,
|
||||||
|
{
|
||||||
|
fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> {
|
||||||
|
// TemplateProperty may be evaluated twice, by has_content() and format().
|
||||||
|
// If that's too expensive, we can instead create a buffered formatter
|
||||||
|
// inheriting the state, and write to it to test the emptiness. In this case,
|
||||||
|
// the formatter should guarantee push/pop_label() is noop without content.
|
||||||
|
let mut content_templates = self
|
||||||
|
.contents
|
||||||
|
.iter()
|
||||||
|
.filter(|template| template.has_content(context))
|
||||||
|
.fuse();
|
||||||
|
if let Some(template) = content_templates.next() {
|
||||||
|
template.format(context, formatter)?;
|
||||||
|
}
|
||||||
|
for template in content_templates {
|
||||||
|
self.separator.format(context, formatter)?;
|
||||||
|
template.format(context, formatter)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_content(&self, context: &C) -> bool {
|
||||||
|
self.contents
|
||||||
|
.iter()
|
||||||
|
.any(|template| template.has_content(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait TemplateProperty<C> {
|
pub trait TemplateProperty<C> {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
|
@ -222,6 +222,57 @@ fn test_templater_label_function() {
|
|||||||
render(r#"label(if(empty, "error", "warning"), "text")"#), @"[38;5;1mtext[39m");
|
render(r#"label(if(empty, "error", "warning"), "text")"#), @"[38;5;1mtext[39m");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_templater_separate_function() {
|
||||||
|
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_colored_template_output(&test_env, &repo_path, "@-", template);
|
||||||
|
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ")"#), @"");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "")"#), @"");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "a")"#), @"a");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "a", "b")"#), @"a b");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "a", "", "b")"#), @"a b");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "a", "b", "")"#), @"a b");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "", "a", "b")"#), @"a b");
|
||||||
|
|
||||||
|
// Labeled
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", label("error", ""), label("warning", "a"), "b")"#),
|
||||||
|
@"[38;5;3ma[39m b");
|
||||||
|
|
||||||
|
// List template
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "a", ("" ""))"#), @"a");
|
||||||
|
insta::assert_snapshot!(render(r#"separate(" ", "a", ("" "b"))"#), @"a b");
|
||||||
|
|
||||||
|
// Nested separate
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", separate("|", "", ""))"#), @"a");
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", separate("|", "b", ""))"#), @"a b");
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", separate("|", "b", "c"))"#), @"a b|c");
|
||||||
|
|
||||||
|
// Conditional template
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", if("t", ""))"#), @"a");
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", if("t", "", "f"))"#), @"a");
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", if("", "t", ""))"#), @"a");
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", "a", if("t", "t", "f"))"#), @"a t");
|
||||||
|
|
||||||
|
// Separate keywords
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(" ", author, description, empty)"#), @" <> [38;5;2mtrue[39m");
|
||||||
|
|
||||||
|
// Keyword as separator
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
render(r#"separate(author, "X", "Y", "Z")"#), @"X <>Y <>Z");
|
||||||
|
}
|
||||||
|
|
||||||
fn get_template_output(
|
fn get_template_output(
|
||||||
test_env: &TestEnvironment,
|
test_env: &TestEnvironment,
|
||||||
repo_path: &Path,
|
repo_path: &Path,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user