From 9a5b001d58353afb7ea6cb894c22d80878b811ae Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Tue, 30 Apr 2024 14:24:08 +0900 Subject: [PATCH] templater: add SizeHint type to represent revset.count_estimate() value We'll probably add binary comparison operators at some point, but this patch also adds size_hint.zero() method. Otherwise, we'll have to write "if(x.upper() && x.upper() == 0, ..)" to deal with None. The resulting "branch list" template will look like: ``` separate(", ", if(!ref.tracking_ahead_count().zero(), if(ref.tracking_ahead_count().exact(), "ahead by " ++ ref.tracking_ahead_count().exact() ++ " commits", "ahead by at least " ++ ref.tracking_ahead_count().lower() ++ " commits")), if(!ref.tracking_behind_count().zero(), if(ref.tracking_behind_count().exact(), "behind by " ++ ref.tracking_behind_count().exact() ++ " commits", "behind by at least " ++ ref.tracking_behind_count().lower() ++ " commits")), ) ``` --- cli/src/template_builder.rs | 75 ++++++++++++++++++++++++++++++++++++- cli/src/templater.rs | 5 +++ docs/templates.md | 10 +++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/cli/src/template_builder.rs b/cli/src/template_builder.rs index deae24d3e9..31ae846756 100644 --- a/cli/src/template_builder.rs +++ b/cli/src/template_builder.rs @@ -24,8 +24,8 @@ use crate::template_parser::{ use crate::templater::{ CoalesceTemplate, ConcatTemplate, ConditionalTemplate, LabelTemplate, ListPropertyTemplate, ListTemplate, Literal, PlainTextFormattedProperty, PropertyPlaceholder, ReformatTemplate, - SeparateTemplate, Template, TemplateProperty, TemplatePropertyError, TemplatePropertyExt as _, - TemplateRenderer, TimestampRange, + SeparateTemplate, SizeHint, Template, TemplateProperty, TemplatePropertyError, + TemplatePropertyExt as _, TemplateRenderer, TimestampRange, }; use crate::{text_util, time_util}; @@ -43,6 +43,7 @@ pub trait TemplateLanguage<'a> { property: impl TemplateProperty> + 'a, ) -> Self::Property; fn wrap_signature(property: impl TemplateProperty + 'a) -> Self::Property; + fn wrap_size_hint(property: impl TemplateProperty + 'a) -> Self::Property; fn wrap_timestamp(property: impl TemplateProperty + 'a) -> Self::Property; fn wrap_timestamp_range( property: impl TemplateProperty + 'a, @@ -86,6 +87,7 @@ macro_rules! impl_core_wrap_property_fns { wrap_integer(i64) => Integer, wrap_integer_opt(Option) => IntegerOpt, wrap_signature(jj_lib::backend::Signature) => Signature, + wrap_size_hint($crate::templater::SizeHint) => SizeHint, wrap_timestamp(jj_lib::backend::Timestamp) => Timestamp, wrap_timestamp_range($crate::templater::TimestampRange) => TimestampRange, } @@ -139,6 +141,7 @@ pub enum CoreTemplatePropertyKind<'a> { Integer(Box + 'a>), IntegerOpt(Box> + 'a>), Signature(Box + 'a>), + SizeHint(Box + 'a>), Timestamp(Box + 'a>), TimestampRange(Box + 'a>), @@ -165,6 +168,7 @@ impl<'a> IntoTemplateProperty<'a> for CoreTemplatePropertyKind<'a> { CoreTemplatePropertyKind::Integer(_) => "Integer", CoreTemplatePropertyKind::IntegerOpt(_) => "Option", CoreTemplatePropertyKind::Signature(_) => "Signature", + CoreTemplatePropertyKind::SizeHint(_) => "SizeHint", CoreTemplatePropertyKind::Timestamp(_) => "Timestamp", CoreTemplatePropertyKind::TimestampRange(_) => "TimestampRange", CoreTemplatePropertyKind::Template(_) => "Template", @@ -186,6 +190,7 @@ impl<'a> IntoTemplateProperty<'a> for CoreTemplatePropertyKind<'a> { Some(Box::new(property.map(|opt| opt.is_some()))) } CoreTemplatePropertyKind::Signature(_) => None, + CoreTemplatePropertyKind::SizeHint(_) => None, CoreTemplatePropertyKind::Timestamp(_) => None, CoreTemplatePropertyKind::TimestampRange(_) => None, // Template types could also be evaluated to boolean, but it's less likely @@ -224,6 +229,7 @@ impl<'a> IntoTemplateProperty<'a> for CoreTemplatePropertyKind<'a> { CoreTemplatePropertyKind::Integer(property) => Some(property.into_template()), CoreTemplatePropertyKind::IntegerOpt(property) => Some(property.into_template()), CoreTemplatePropertyKind::Signature(property) => Some(property.into_template()), + CoreTemplatePropertyKind::SizeHint(_) => None, CoreTemplatePropertyKind::Timestamp(property) => Some(property.into_template()), CoreTemplatePropertyKind::TimestampRange(property) => Some(property.into_template()), CoreTemplatePropertyKind::Template(template) => Some(template), @@ -268,6 +274,7 @@ pub struct CoreTemplateBuildFnTable<'a, L: TemplateLanguage<'a> + ?Sized> { pub boolean_methods: TemplateBuildMethodFnMap<'a, L, bool>, pub integer_methods: TemplateBuildMethodFnMap<'a, L, i64>, pub signature_methods: TemplateBuildMethodFnMap<'a, L, Signature>, + pub size_hint_methods: TemplateBuildMethodFnMap<'a, L, SizeHint>, pub timestamp_methods: TemplateBuildMethodFnMap<'a, L, Timestamp>, pub timestamp_range_methods: TemplateBuildMethodFnMap<'a, L, TimestampRange>, } @@ -289,6 +296,7 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { boolean_methods: HashMap::new(), integer_methods: HashMap::new(), signature_methods: builtin_signature_methods(), + size_hint_methods: builtin_size_hint_methods(), timestamp_methods: builtin_timestamp_methods(), timestamp_range_methods: builtin_timestamp_range_methods(), } @@ -301,6 +309,7 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { boolean_methods: HashMap::new(), integer_methods: HashMap::new(), signature_methods: HashMap::new(), + size_hint_methods: HashMap::new(), timestamp_methods: HashMap::new(), timestamp_range_methods: HashMap::new(), } @@ -313,6 +322,7 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { boolean_methods, integer_methods, signature_methods, + size_hint_methods, timestamp_methods, timestamp_range_methods, } = extension; @@ -322,6 +332,7 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { merge_fn_map(&mut self.boolean_methods, boolean_methods); merge_fn_map(&mut self.integer_methods, integer_methods); merge_fn_map(&mut self.signature_methods, signature_methods); + merge_fn_map(&mut self.size_hint_methods, size_hint_methods); merge_fn_map(&mut self.timestamp_methods, timestamp_methods); merge_fn_map(&mut self.timestamp_range_methods, timestamp_range_methods); } @@ -382,6 +393,11 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { let build = template_parser::lookup_method(type_name, table, function)?; build(language, build_ctx, property, function) } + CoreTemplatePropertyKind::SizeHint(property) => { + let table = &self.size_hint_methods; + let build = template_parser::lookup_method(type_name, table, function)?; + build(language, build_ctx, property, function) + } CoreTemplatePropertyKind::Timestamp(property) => { let table = &self.timestamp_methods; let build = template_parser::lookup_method(type_name, table, function)?; @@ -704,6 +720,38 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>( map } +fn builtin_size_hint_methods<'a, L: TemplateLanguage<'a> + ?Sized>( +) -> TemplateBuildMethodFnMap<'a, L, SizeHint> { + // Not using maplit::hashmap!{} or custom declarative macro here because + // code completion inside macro is quite restricted. + let mut map = TemplateBuildMethodFnMap::::new(); + map.insert("lower", |_language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = self_property.and_then(|(lower, _)| Ok(i64::try_from(lower)?)); + Ok(L::wrap_integer(out_property)) + }); + map.insert("upper", |_language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = + self_property.and_then(|(_, upper)| Ok(upper.map(i64::try_from).transpose()?)); + Ok(L::wrap_integer_opt(out_property)) + }); + map.insert("exact", |_language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = self_property.and_then(|(lower, upper)| { + let exact = (Some(lower) == upper).then_some(lower); + Ok(exact.map(i64::try_from).transpose()?) + }); + Ok(L::wrap_integer_opt(out_property)) + }); + map.insert("zero", |_language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = self_property.map(|(_, upper)| upper == Some(0)); + Ok(L::wrap_boolean(out_property)) + }); + map +} + fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>( ) -> TemplateBuildMethodFnMap<'a, L, Timestamp> { // Not using maplit::hashmap!{} or custom declarative macro here because @@ -1847,6 +1895,29 @@ mod tests { insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @""); } + #[test] + fn test_size_hint_method() { + let mut env = TestTemplateEnv::new(); + + env.add_keyword("unbounded", || L::wrap_size_hint(Literal((5, None)))); + insta::assert_snapshot!(env.render_ok(r#"unbounded.lower()"#), @"5"); + insta::assert_snapshot!(env.render_ok(r#"unbounded.upper()"#), @""); + insta::assert_snapshot!(env.render_ok(r#"unbounded.exact()"#), @""); + insta::assert_snapshot!(env.render_ok(r#"unbounded.zero()"#), @"false"); + + env.add_keyword("bounded", || L::wrap_size_hint(Literal((0, Some(10))))); + insta::assert_snapshot!(env.render_ok(r#"bounded.lower()"#), @"0"); + insta::assert_snapshot!(env.render_ok(r#"bounded.upper()"#), @"10"); + insta::assert_snapshot!(env.render_ok(r#"bounded.exact()"#), @""); + insta::assert_snapshot!(env.render_ok(r#"bounded.zero()"#), @"false"); + + env.add_keyword("zero", || L::wrap_size_hint(Literal((0, Some(0))))); + insta::assert_snapshot!(env.render_ok(r#"zero.lower()"#), @"0"); + insta::assert_snapshot!(env.render_ok(r#"zero.upper()"#), @"0"); + insta::assert_snapshot!(env.render_ok(r#"zero.exact()"#), @"0"); + insta::assert_snapshot!(env.render_ok(r#"zero.zero()"#), @"true"); + } + #[test] fn test_timestamp_method() { let mut env = TestTemplateEnv::new(); diff --git a/cli/src/templater.rs b/cli/src/templater.rs index 87734c3ceb..b0b0f2cad2 100644 --- a/cli/src/templater.rs +++ b/cli/src/templater.rs @@ -74,6 +74,11 @@ impl Template for Signature { } } +// In template language, an integer value is represented as i64. However, we use +// usize here because it's more convenient to guarantee that the lower value is +// bounded to 0. +pub type SizeHint = (usize, Option); + impl Template for String { fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> { write!(formatter, "{self}") diff --git a/docs/templates.md b/docs/templates.md index fe446db88e..2869f553ae 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -182,6 +182,16 @@ The following methods are defined. * `.username() -> String` * `.timestamp() -> Timestamp` +### SizeHint type + +This type cannot be printed. The following methods are defined. + +* `.lower() -> Integer`: Lower bound. +* `.upper() -> Option`: Upper bound if known. +* `.exact() -> Option`: Exact value if upper bound is known and it + equals to the lower bound. +* `.zero() -> Boolean`: True if upper bound is known and is `0`. + ### String type A string can be implicitly converted to `Boolean`. The following methods are