forked from mirrors/jj
templates: Add more string methods
Add starts_with/ends_with/remove_prefix/remove_suffix/substr methods to string when templating.
This commit is contained in:
parent
9702a425e5
commit
ac448202da
4 changed files with 110 additions and 0 deletions
|
@ -106,6 +106,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
* `jj log` timestamp format now accepts `.utc()` to convert a timestamp to UTC.
|
||||
|
||||
* templates now support additional string methods `.starts_with(x)`, `.ends_with(x)`
|
||||
`.remove_prefix(x)`, `.remove_suffix(x)`, and `.substr(start, end)`.
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
* Fix issues related to .gitignore handling of untracked directories
|
||||
|
|
|
@ -329,6 +329,76 @@ fn build_string_method<'a, L: TemplateLanguage<'a>>(
|
|||
|(haystack, needle)| haystack.contains(&needle),
|
||||
))
|
||||
}
|
||||
"starts_with" => {
|
||||
let [needle_node] = template_parser::expect_exact_arguments(function)?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
language.wrap_boolean(TemplateFunction::new(
|
||||
(self_property, needle_property),
|
||||
move |(haystack, needle)| haystack.starts_with(&needle),
|
||||
))
|
||||
}
|
||||
"ends_with" => {
|
||||
let [needle_node] = template_parser::expect_exact_arguments(function)?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
language.wrap_boolean(TemplateFunction::new(
|
||||
(self_property, needle_property),
|
||||
move |(haystack, needle)| haystack.ends_with(&needle),
|
||||
))
|
||||
}
|
||||
"remove_prefix" => {
|
||||
let [needle_node] = template_parser::expect_exact_arguments(function)?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
language.wrap_string(TemplateFunction::new(
|
||||
(self_property, needle_property),
|
||||
move |(haystack, needle)| {
|
||||
haystack
|
||||
.strip_prefix(&needle)
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or(haystack)
|
||||
},
|
||||
))
|
||||
}
|
||||
"remove_suffix" => {
|
||||
let [needle_node] = template_parser::expect_exact_arguments(function)?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
language.wrap_string(TemplateFunction::new(
|
||||
(self_property, needle_property),
|
||||
move |(haystack, needle)| {
|
||||
haystack
|
||||
.strip_suffix(&needle)
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or(haystack)
|
||||
},
|
||||
))
|
||||
}
|
||||
"substr" => {
|
||||
let [start_idx, end_idx] = template_parser::expect_exact_arguments(function)?;
|
||||
let start_idx_property = expect_integer_expression(language, build_ctx, start_idx)?;
|
||||
let end_idx_property = expect_integer_expression(language, build_ctx, end_idx)?;
|
||||
language.wrap_string(TemplateFunction::new(
|
||||
(self_property, start_idx_property, end_idx_property),
|
||||
|(s, start_idx, end_idx)| {
|
||||
let to_idx = |i: i64| -> usize {
|
||||
let magnitude = usize::try_from(i.unsigned_abs()).unwrap_or(usize::MAX);
|
||||
if i < 0 {
|
||||
s.len().saturating_sub(magnitude)
|
||||
} else {
|
||||
magnitude
|
||||
}
|
||||
};
|
||||
let start_idx = to_idx(start_idx);
|
||||
let end_idx = to_idx(end_idx);
|
||||
if start_idx >= end_idx {
|
||||
String::new()
|
||||
} else {
|
||||
s.chars()
|
||||
.skip(start_idx)
|
||||
.take(end_idx - start_idx)
|
||||
.collect()
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
"first_line" => {
|
||||
template_parser::expect_no_arguments(function)?;
|
||||
language.wrap_string(TemplateFunction::new(self_property, |s| {
|
||||
|
|
|
@ -289,6 +289,8 @@ fn test_templater_list_method() {
|
|||
insta::assert_snapshot!(
|
||||
render(r#""a\nb\nc".lines().map(|s| "x\ny".lines().map(|t| s ++ t).join(",")).join(";")"#),
|
||||
@"ax,ay;bx,by;cx,cy");
|
||||
// Nested string operations
|
||||
insta::assert_snapshot!(render(r#""!a\n!b\nc\nend".remove_suffix("end").lines().map(|s| s.remove_prefix("!"))"#), @"a b c");
|
||||
|
||||
// Lambda expression in alias
|
||||
insta::assert_snapshot!(render(r#""a\nb\nc".lines().map(identity)"#), @"a b c");
|
||||
|
@ -364,6 +366,36 @@ fn test_templater_string_method() {
|
|||
|
||||
insta::assert_snapshot!(render(r#""".lines()"#), @"");
|
||||
insta::assert_snapshot!(render(r#""a\nb\nc\n".lines()"#), @"a b c");
|
||||
|
||||
insta::assert_snapshot!(render(r#""".starts_with("")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""everything".starts_with("")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""".starts_with("foo")"#), @"false");
|
||||
insta::assert_snapshot!(render(r#""foo".starts_with("foo")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""foobar".starts_with("foo")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""foobar".starts_with("bar")"#), @"false");
|
||||
|
||||
insta::assert_snapshot!(render(r#""".ends_with("")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""everything".ends_with("")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""".ends_with("foo")"#), @"false");
|
||||
insta::assert_snapshot!(render(r#""foo".ends_with("foo")"#), @"true");
|
||||
insta::assert_snapshot!(render(r#""foobar".ends_with("foo")"#), @"false");
|
||||
insta::assert_snapshot!(render(r#""foobar".ends_with("bar")"#), @"true");
|
||||
|
||||
insta::assert_snapshot!(render(r#""".remove_prefix("wip: ")"#), @"");
|
||||
insta::assert_snapshot!(render(r#""wip: testing".remove_prefix("wip: ")"#), @"testing");
|
||||
|
||||
insta::assert_snapshot!(render(r#""bar@my.example.com".remove_suffix("@other.example.com")"#), @"bar@my.example.com");
|
||||
insta::assert_snapshot!(render(r#""bar@other.example.com".remove_suffix("@other.example.com")"#), @"bar");
|
||||
|
||||
insta::assert_snapshot!(render(r#""foo".substr(0, 0)"#), @"");
|
||||
insta::assert_snapshot!(render(r#""foo".substr(0, 1)"#), @"f");
|
||||
insta::assert_snapshot!(render(r#""foo".substr(0, 99)"#), @"foo");
|
||||
insta::assert_snapshot!(render(r#""abcdef".substr(2, -1)"#), @"cde");
|
||||
insta::assert_snapshot!(render(r#""abcdef".substr(-3, 99)"#), @"def");
|
||||
|
||||
// ranges with end > start are empty
|
||||
insta::assert_snapshot!(render(r#""abcdef".substr(4, 2)"#), @"");
|
||||
insta::assert_snapshot!(render(r#""abcdef".substr(-2, -4)"#), @"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -142,6 +142,11 @@ defined.
|
|||
* `.lines() -> List<String>`: Split into lines excluding newline characters.
|
||||
* `.upper() -> String`
|
||||
* `.lower() -> String`
|
||||
* `.starts_with(needle: Template) -> Boolean`
|
||||
* `.ends_with(needle: Template) -> Boolean`
|
||||
* `.remove_prefix(needle: Template) -> String`: Removes the passed prefix, if present
|
||||
* `.remove_suffix(needle: Template) -> String`: Removes the passed suffix, if present
|
||||
* `.substr(start: Integer, end: Integer) -> String`: Extract substring. Negative values count from the end.
|
||||
|
||||
### Template type
|
||||
|
||||
|
|
Loading…
Reference in a new issue