templates: Timestamp: add {before,after} methods

This allows for more fine-grained control of timestamp formatting, for
example:

```
[template-aliases]
'format_timestamp(timestamp)' = '''
if(timestamp.before("1 week ago"),
  timestamp.format("%b %d %Y %H:%M"),
  timestamp.ago()
)
'''
```

Closes #3782.
This commit is contained in:
Benjamin Tan 2024-10-15 23:55:36 +08:00
parent 470275b0d0
commit b850670438
No known key found for this signature in database
GPG key ID: A853F0716C413825
4 changed files with 47 additions and 0 deletions

View file

@ -51,6 +51,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The output can be customized via the `templates.annotate_commit_summary`
config variable.
* Timestamp objects in templates now have `after(date) -> Boolean` and
`before(date) -> Boolean` methods for comparing timestamps to other dates.
### Fixed bugs
* Error on `trunk()` revset resolution is now handled gracefully.

View file

@ -18,6 +18,7 @@ use itertools::Itertools as _;
use jj_lib::backend::Signature;
use jj_lib::backend::Timestamp;
use jj_lib::dsl_util::AliasExpandError as _;
use jj_lib::time_util::DatePattern;
use crate::template_parser;
use crate::template_parser::BinaryOp;
@ -900,6 +901,24 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
Ok(L::wrap_timestamp(out_property))
},
);
map.insert(
"after",
|_language, _diagnostics, _build_ctx, self_property, function| {
let [date_pattern_node] = function.expect_exact_arguments()?;
let now = chrono::offset::Local::now();
let date_pattern = template_parser::expect_string_literal_with(
date_pattern_node,
|date_pattern, span| {
DatePattern::from_str_kind(date_pattern, function.name, now)
.map_err(|_| TemplateParseError::expression("Invalid date pattern", span))
},
)?;
let out_property =
self_property.and_then(move |timestamp| Ok(date_pattern.matches(&timestamp)));
Ok(L::wrap_boolean(out_property))
},
);
map.insert("before", map["after"]);
map
}

View file

@ -146,6 +146,29 @@ fn test_log_author_timestamp_local() {
"###);
}
#[test]
fn test_log_author_timestamp_after_before() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "first"]);
let template = r#"
separate(" ",
author.timestamp(),
":",
if(author.timestamp().after("1969"), "(after 1969)", "(before 1969)"),
if(author.timestamp().before("1975"), "(before 1975)", "(after 1975)"),
if(author.timestamp().before("now"), "(before now)", "(after now)")
) ++ "\n""#;
let stdout = test_env.jj_cmd_success(&repo_path, &["log", "--no-graph", "-T", template]);
insta::assert_snapshot!(stdout, @r#"
2001-02-03 04:05:08.000 +07:00 : (after 1969) (after 1975) (before now)
1970-01-01 00:00:00.000 +00:00 : (after 1969) (before 1975) (before now)
"#);
}
#[test]
fn test_mine_is_true_when_author_is_user() {
let test_env = TestEnvironment::default();

View file

@ -265,6 +265,8 @@ The following methods are defined.
format string](https://docs.rs/chrono/latest/chrono/format/strftime/).
* `.utc() -> Timestamp`: Convert timestamp into UTC timezone.
* `.local() -> Timestamp`: Convert timestamp into local timezone.
* `.after(date: String) -> Boolean`: True if the timestamp is exactly at or after the given date.
* `.before(date: String) -> Boolean`: True if the timestamp is before, but not including, the given date.
### TimestampRange type