// Copyright 2020-2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::cmp::Ordering;use std::collections::HashMap;use std::io;use std::iter; use itertools::Itertools as _;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::config::ConfigGetResultExt as _;use jj_lib::config::ConfigNamePathBuf;use jj_lib::config::ConfigValue;use jj_lib::content_hash::blake2b_hash;use jj_lib::hex_util;use jj_lib::op_store::TimestampRange;use jj_lib::settings::UserSettings;use jj_lib::time_util::DatePattern;use serde::Deserialize;use serde::de::IntoDeserializer as _; use crate::config;use crate::formatter::FormatRecorder;use crate::formatter::Formatter;use crate::template_parser;use crate::template_parser::BinaryOp;use crate::template_parser::ExpressionKind;use crate::template_parser::ExpressionNode;use crate::template_parser::FunctionCallNode;use crate::template_parser::LambdaNode;use crate::template_parser::TemplateAliasesMap;use crate::template_parser::TemplateDiagnostics;use crate::template_parser::TemplateParseError;use crate::template_parser::TemplateParseErrorKind;use crate::template_parser::TemplateParseResult;use crate::template_parser::UnaryOp;use crate::templater::AnyTemplateProperty;use crate::templater::BoxedAnyProperty;use crate::templater::BoxedSerializeProperty;use crate::templater::BoxedTemplateProperty;use crate::templater::CoalesceTemplate;use crate::templater::ConcatTemplate;use crate::templater::ConditionalProperty;use crate::templater::Email;use crate::templater::HyperlinkTemplate;use crate::templater::JoinTemplate;use crate::templater::LabelTemplate;use crate::templater::ListMapProperty;use crate::templater::ListPropertyTemplate;use crate::templater::Literal;use crate::templater::PlainTextFormattedProperty;use crate::templater::PropertyPlaceholder;use crate::templater::RawEscapeSequenceTemplate;use crate::templater::ReformatTemplate;use crate::templater::SeparateTemplate;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateProperty;use crate::templater::TemplatePropertyError;use crate::templater::TemplatePropertyExt as _;use crate::templater::TemplateRenderer;use crate::templater::WrapTemplateProperty;use crate::text_util;use crate::time_util; /// Callbacks to build usage-context-specific evaluation objects from AST nodes.////// This is used to implement different meanings of `self` or different/// globally available functions in the template language depending on the/// context in which it is invoked.pub trait TemplateLanguage<'a> { type Property: CoreTemplatePropertyVar<'a> + 'a; fn settings(&self) -> &UserSettings; /// Translates the given global `function` call to a property. /// /// This should be delegated to /// `CoreTemplateBuildFnTable::build_function()`. fn build_function( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>; /// Creates a method call thunk for the given `function` of the given /// `property`. fn build_method( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>;} /// Implements [`WrapTemplateProperty<'a, O>`] for property types.////// - `impl_property_wrappers!(Kind { Foo(Foo), FooList(Vec<Foo>), .. });` to/// implement conversion from types `Foo`, `Vec<Foo>`, .../// - `impl_property_wrappers!(<'a> Kind<'a> { .. });` for types with lifetime./// - `impl_property_wrappers!(Kind => Core { .. });` to forward conversion to/// `Kind::Core(_)`.macro_rules! impl_property_wrappers { ($kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [], 'static, $kind $(=> $var)?, { $($body)* }); }; // capture the first lifetime as the lifetime of template objects. (<$a:lifetime $(, $p:lifetime)* $(, $q:ident)*> $kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [$a, $($p,)* $($q,)*], $a, $kind $(=> $var)?, { $($body)* }); };} macro_rules! _impl_property_wrappers_many { // lifetime/type parameters are packed in order to disable zipping. // https://github.com/rust-lang/rust/issues/96184#issuecomment-1294999418 ($ps:tt, $a:lifetime, $kind:path, { $( $var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, std::convert::identity); )* }; // variant part in body is ignored so the same body can be reused for // implementing forwarding conversion. ($ps:tt, $a:lifetime, $kind:path => $var:ident, { $( $ignored_var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, $crate::templater::WrapTemplateProperty::wrap_property); )* };} macro_rules! _impl_property_wrappers_one { ([$($p:tt)*], $a:lifetime, $kind:path, $var:ident, $ty:ty, $inner:path) => { impl<$($p)*> $crate::templater::WrapTemplateProperty<$a, $ty> for $kind { fn wrap_property(property: $crate::templater::BoxedTemplateProperty<$a, $ty>) -> Self { Self::$var($inner(property)) } } };} pub(crate) use _impl_property_wrappers_many;pub(crate) use _impl_property_wrappers_one;pub(crate) use impl_property_wrappers; /// Wrapper for the core template property types.pub trait CoreTemplatePropertyVar<'a>where Self: WrapTemplateProperty<'a, String>, Self: WrapTemplateProperty<'a, Vec<String>>, Self: WrapTemplateProperty<'a, bool>, Self: WrapTemplateProperty<'a, i64>, Self: WrapTemplateProperty<'a, Option<i64>>, Self: WrapTemplateProperty<'a, ConfigValue>, Self: WrapTemplateProperty<'a, Option<ConfigValue>>, Self: WrapTemplateProperty<'a, Signature>, Self: WrapTemplateProperty<'a, Email>, Self: WrapTemplateProperty<'a, SizeHint>, Self: WrapTemplateProperty<'a, Timestamp>, Self: WrapTemplateProperty<'a, TimestampRange>,{ fn wrap_template(template: Box<dyn Template + 'a>) -> Self; fn wrap_any(property: BoxedAnyProperty<'a>) -> Self; fn wrap_any_list(property: BoxedAnyProperty<'a>) -> Self; /// Type name of the property output. fn type_name(&self) -> &'static str; fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>>; fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>>; fn try_into_timestamp(self) -> Option<BoxedTemplateProperty<'a, Timestamp>>; /// Transforms into a string property by formatting the value if needed. fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>>; fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>>; fn try_into_template(self) -> Option<Box<dyn Template + 'a>>; /// Transforms into a property that will evaluate to `self == other`. fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>>; /// Transforms into a property that will evaluate to an [`Ordering`]. fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>>;} pub enum CoreTemplatePropertyKind<'a> { String(BoxedTemplateProperty<'a, String>), StringList(BoxedTemplateProperty<'a, Vec<String>>), Boolean(BoxedTemplateProperty<'a, bool>), Integer(BoxedTemplateProperty<'a, i64>), IntegerOpt(BoxedTemplateProperty<'a, Option<i64>>), ConfigValue(BoxedTemplateProperty<'a, ConfigValue>), ConfigValueOpt(BoxedTemplateProperty<'a, Option<ConfigValue>>), Signature(BoxedTemplateProperty<'a, Signature>), Email(BoxedTemplateProperty<'a, Email>), SizeHint(BoxedTemplateProperty<'a, SizeHint>), Timestamp(BoxedTemplateProperty<'a, Timestamp>), TimestampRange(BoxedTemplateProperty<'a, TimestampRange>), // Both TemplateProperty and Template can represent a value to be evaluated // dynamically, which suggests that `Box<dyn Template + 'a>` could be // composed as `Box<dyn TemplateProperty<Output = Box<dyn Template ..`. // However, there's a subtle difference: TemplateProperty is strict on // error, whereas Template is usually lax and prints an error inline. If // `concat(x, y)` were a property returning Template, and if `y` failed to // evaluate, the whole expression would fail. In this example, a partial // evaluation output is more useful. That's one reason why Template isn't // wrapped in a TemplateProperty. Another reason is that the outermost // caller expects a Template, not a TemplateProperty of Template output. Template(Box<dyn Template + 'a>), Any(BoxedAnyProperty<'a>), AnyList(BoxedAnyProperty<'a>),} /// Implements `WrapTemplateProperty<type>` for core property types.////// Use `impl_core_property_wrappers!(<'a> Kind<'a> => Core);` to implement/// forwarding conversion.macro_rules! impl_core_property_wrappers { ($($head:tt)+) => { $crate::template_builder::impl_property_wrappers!($($head)+ { String(String), StringList(Vec<String>), Boolean(bool), Integer(i64), IntegerOpt(Option<i64>), ConfigValue(jj_lib::config::ConfigValue), ConfigValueOpt(Option<jj_lib::config::ConfigValue>), Signature(jj_lib::backend::Signature), Email($crate::templater::Email), SizeHint($crate::templater::SizeHint), Timestamp(jj_lib::backend::Timestamp), TimestampRange(jj_lib::op_store::TimestampRange), }); };} pub(crate) use impl_core_property_wrappers; impl_core_property_wrappers!(<'a> CoreTemplatePropertyKind<'a>); impl<'a> CoreTemplatePropertyVar<'a> for CoreTemplatePropertyKind<'a> { fn wrap_template(template: Box<dyn Template + 'a>) -> Self { Self::Template(template) } fn wrap_any(property: BoxedAnyProperty<'a>) -> Self { Self::Any(property) } fn wrap_any_list(property: BoxedAnyProperty<'a>) -> Self { Self::AnyList(property) } fn type_name(&self) -> &'static str { match self { Self::String(_) => "String", Self::StringList(_) => "List<String>", Self::Boolean(_) => "Boolean", Self::Integer(_) => "Integer", Self::IntegerOpt(_) => "Option<Integer>", Self::ConfigValue(_) => "ConfigValue", Self::ConfigValueOpt(_) => "Option<ConfigValue>", Self::Signature(_) => "Signature", Self::Email(_) => "Email", Self::SizeHint(_) => "SizeHint", Self::Timestamp(_) => "Timestamp", Self::TimestampRange(_) => "TimestampRange", Self::Template(_) => "Template", Self::Any(_) => "Any", Self::AnyList(_) => "AnyList", } } fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> { match self { Self::String(property) => Some(property.map(|s| !s.is_empty()).into_dyn()), Self::StringList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()), Self::Boolean(property) => Some(property), Self::Integer(_) => None, Self::IntegerOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()), Self::ConfigValue(_) => None, Self::ConfigValueOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()), Self::Signature(_) => None, Self::Email(property) => Some(property.map(|e| !e.0.is_empty()).into_dyn()), Self::SizeHint(_) => None, Self::Timestamp(_) => None, Self::TimestampRange(_) => None, // Template and AnyList types could also be evaluated to boolean, // but it's less likely to apply label() or .map() and use the // result as conditional. Self::Template(_) => None, Self::Any(_) => None, Self::AnyList(_) => None, } } fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> { match self { Self::Integer(property) => Some(property), Self::IntegerOpt(property) => Some(property.try_unwrap("Integer").into_dyn()), _ => None, } } fn try_into_timestamp(self) -> Option<BoxedTemplateProperty<'a, Timestamp>> { match self { Self::Timestamp(property) => Some(property), _ => None, } } fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>> { match self { Self::String(property) => Some(property), _ => { let template = self.try_into_template()?; Some(PlainTextFormattedProperty::new(template).into_dyn()) } } } fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> { match self { Self::String(property) => Some(property.into_serialize()), Self::StringList(property) => Some(property.into_serialize()), Self::Boolean(property) => Some(property.into_serialize()), Self::Integer(property) => Some(property.into_serialize()), Self::IntegerOpt(property) => Some(property.into_serialize()), Self::ConfigValue(property) => { Some(property.map(config::to_serializable_value).into_serialize()) } Self::ConfigValueOpt(property) => Some( property .map(|opt| opt.map(config::to_serializable_value)) .into_serialize(), ), Self::Signature(property) => Some(property.into_serialize()), Self::Email(property) => Some(property.into_serialize()), Self::SizeHint(property) => Some(property.into_serialize()), Self::Timestamp(property) => Some(property.into_serialize()), Self::TimestampRange(property) => Some(property.into_serialize()), Self::Template(_) => None, Self::Any(property) => property.try_into_serialize(), Self::AnyList(property) => property.try_into_serialize(), } } fn try_into_template(self) -> Option<Box<dyn Template + 'a>> { match self { Self::String(property) => Some(property.into_template()), Self::StringList(property) => Some(property.into_template()), Self::Boolean(property) => Some(property.into_template()), Self::Integer(property) => Some(property.into_template()), Self::IntegerOpt(property) => Some(property.into_template()), Self::ConfigValue(property) => Some(property.into_template()), Self::ConfigValueOpt(property) => Some(property.into_template()), Self::Signature(property) => Some(property.into_template()), Self::Email(property) => Some(property.into_template()), Self::SizeHint(_) => None, Self::Timestamp(property) => Some(property.into_template()), Self::TimestampRange(property) => Some(property.into_template()), Self::Template(template) => Some(template), Self::Any(property) => property.try_into_template(), Self::AnyList(property) => property.try_into_template(), } } fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> { match (self, other) { (Self::String(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::String(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r.0).into_dyn()) } (Self::Boolean(lhs), Self::Boolean(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l) == r).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == Some(r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.0 == r).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::ConfigValueOpt(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::Any(_), _) => None, (Self::AnyList(_), _) => None, } } fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> { match (self, other) { (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l).cmp(&r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&Some(r))).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::ConfigValueOpt(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::Any(_), _) => None, (Self::AnyList(_), _) => None, } }} /// Function that translates global function call node.// The lifetime parameter 'a could be replaced with for<'a> to keep the method// table away from a certain lifetime. That's technically more correct, but I// couldn't find an easy way to expand that to the core template methods, which// are defined for L: TemplateLanguage<'a>. That's why the build fn table is// bound to a named lifetime, and therefore can't be cached statically.pub type TemplateBuildFunctionFn<'a, L, P> = fn(&L, &mut TemplateDiagnostics, &BuildContext<P>, &FunctionCallNode) -> TemplateParseResult<P>; type BuildMethodFn<'a, L, T, P> = fn( &L, &mut TemplateDiagnostics, &BuildContext<P>, T, &FunctionCallNode,) -> TemplateParseResult<P>; /// Function that translates method call node of self type `T`.pub type TemplateBuildMethodFn<'a, L, T, P> = BuildMethodFn<'a, L, BoxedTemplateProperty<'a, T>, P>; /// Function that translates method call node of `Template`.pub type BuildTemplateMethodFn<'a, L, P> = BuildMethodFn<'a, L, Box<dyn Template + 'a>, P>; /// Function that translates method call node of `Any*`.pub type BuildAnyMethodFn<'a, L, P> = BuildMethodFn<'a, L, BoxedAnyProperty<'a>, P>; /// Table of functions that translate global function call node.pub type TemplateBuildFunctionFnMap<'a, L, P = <L as TemplateLanguage<'a>>::Property> = HashMap<&'static str, TemplateBuildFunctionFn<'a, L, P>>; /// Table of functions that translate method call node of self type `T`.pub type TemplateBuildMethodFnMap<'a, L, T, P = <L as TemplateLanguage<'a>>::Property> = HashMap<&'static str, TemplateBuildMethodFn<'a, L, T, P>>; /// Table of functions that translate method call node of `Template`.pub type BuildTemplateMethodFnMap<'a, L, P = <L as TemplateLanguage<'a>>::Property> =Lines 1–500 of 5025. Load more
// Copyright 2020-2023 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::cmp::Ordering; use std::collections::HashMap; use std::io; use std::iter; use itertools::Itertools as _; use jj_lib::backend::Signature; use jj_lib::backend::Timestamp; use jj_lib::config::ConfigGetResultExt as _; use jj_lib::config::ConfigNamePathBuf; use jj_lib::config::ConfigValue; use jj_lib::content_hash::blake2b_hash; use jj_lib::hex_util; use jj_lib::op_store::TimestampRange; use jj_lib::settings::UserSettings; use jj_lib::time_util::DatePattern; use serde::Deserialize; use serde::de::IntoDeserializer as _; use crate::config; use crate::formatter::FormatRecorder; use crate::formatter::Formatter; use crate::template_parser; use crate::template_parser::BinaryOp; use crate::template_parser::ExpressionKind; use crate::template_parser::ExpressionNode; use crate::template_parser::FunctionCallNode; use crate::template_parser::LambdaNode; use crate::template_parser::TemplateAliasesMap; use crate::template_parser::TemplateDiagnostics; use crate::template_parser::TemplateParseError; use crate::template_parser::TemplateParseErrorKind; use crate::template_parser::TemplateParseResult; use crate::template_parser::UnaryOp; use crate::templater::AnyTemplateProperty; use crate::templater::BoxedAnyProperty; use crate::templater::BoxedSerializeProperty; use crate::templater::BoxedTemplateProperty; use crate::templater::CoalesceTemplate; use crate::templater::ConcatTemplate; use crate::templater::ConditionalProperty; use crate::templater::Email; use crate::templater::HyperlinkTemplate; use crate::templater::JoinTemplate; use crate::templater::LabelTemplate; use crate::templater::ListMapProperty; use crate::templater::ListPropertyTemplate; use crate::templater::Literal; use crate::templater::PlainTextFormattedProperty; use crate::templater::PropertyPlaceholder; use crate::templater::RawEscapeSequenceTemplate; use crate::templater::ReformatTemplate; use crate::templater::SeparateTemplate; use crate::templater::SizeHint; use crate::templater::Template; use crate::templater::TemplateProperty; use crate::templater::TemplatePropertyError; use crate::templater::TemplatePropertyExt as _; use crate::templater::TemplateRenderer; use crate::templater::WrapTemplateProperty; use crate::text_util; use crate::time_util; /// Callbacks to build usage-context-specific evaluation objects from AST nodes. /// /// This is used to implement different meanings of `self` or different /// globally available functions in the template language depending on the /// context in which it is invoked. pub trait TemplateLanguage<'a> { type Property: CoreTemplatePropertyVar<'a> + 'a; fn settings(&self) -> &UserSettings; /// Translates the given global `function` call to a property. /// /// This should be delegated to /// `CoreTemplateBuildFnTable::build_function()`. fn build_function( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>; /// Creates a method call thunk for the given `function` of the given /// `property`. fn build_method( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>; } /// Implements [`WrapTemplateProperty<'a, O>`] for property types. /// /// - `impl_property_wrappers!(Kind { Foo(Foo), FooList(Vec<Foo>), .. });` to /// implement conversion from types `Foo`, `Vec<Foo>`, ... /// - `impl_property_wrappers!(<'a> Kind<'a> { .. });` for types with lifetime. /// - `impl_property_wrappers!(Kind => Core { .. });` to forward conversion to /// `Kind::Core(_)`. macro_rules! impl_property_wrappers { ($kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [], 'static, $kind $(=> $var)?, { $($body)* }); }; // capture the first lifetime as the lifetime of template objects. (<$a:lifetime $(, $p:lifetime)* $(, $q:ident)*> $kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [$a, $($p,)* $($q,)*], $a, $kind $(=> $var)?, { $($body)* }); }; } macro_rules! _impl_property_wrappers_many { // lifetime/type parameters are packed in order to disable zipping. // https://github.com/rust-lang/rust/issues/96184#issuecomment-1294999418 ($ps:tt, $a:lifetime, $kind:path, { $( $var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, std::convert::identity); )* }; // variant part in body is ignored so the same body can be reused for // implementing forwarding conversion. ($ps:tt, $a:lifetime, $kind:path => $var:ident, { $( $ignored_var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, $crate::templater::WrapTemplateProperty::wrap_property); )* }; } macro_rules! _impl_property_wrappers_one { ([$($p:tt)*], $a:lifetime, $kind:path, $var:ident, $ty:ty, $inner:path) => { impl<$($p)*> $crate::templater::WrapTemplateProperty<$a, $ty> for $kind { fn wrap_property(property: $crate::templater::BoxedTemplateProperty<$a, $ty>) -> Self { Self::$var($inner(property)) } } }; } pub(crate) use _impl_property_wrappers_many; pub(crate) use _impl_property_wrappers_one; pub(crate) use impl_property_wrappers; /// Wrapper for the core template property types. pub trait CoreTemplatePropertyVar<'a> where Self: WrapTemplateProperty<'a, String>, Self: WrapTemplateProperty<'a, Vec<String>>, Self: WrapTemplateProperty<'a, bool>, Self: WrapTemplateProperty<'a, i64>, Self: WrapTemplateProperty<'a, Option<i64>>, Self: WrapTemplateProperty<'a, ConfigValue>, Self: WrapTemplateProperty<'a, Option<ConfigValue>>, Self: WrapTemplateProperty<'a, Signature>, Self: WrapTemplateProperty<'a, Email>, Self: WrapTemplateProperty<'a, SizeHint>, Self: WrapTemplateProperty<'a, Timestamp>, Self: WrapTemplateProperty<'a, TimestampRange>, { fn wrap_template(template: Box<dyn Template + 'a>) -> Self; fn wrap_any(property: BoxedAnyProperty<'a>) -> Self; fn wrap_any_list(property: BoxedAnyProperty<'a>) -> Self; /// Type name of the property output. fn type_name(&self) -> &'static str; fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>>; fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>>; fn try_into_timestamp(self) -> Option<BoxedTemplateProperty<'a, Timestamp>>; /// Transforms into a string property by formatting the value if needed. fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>>; fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>>; fn try_into_template(self) -> Option<Box<dyn Template + 'a>>; /// Transforms into a property that will evaluate to `self == other`. fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>>; /// Transforms into a property that will evaluate to an [`Ordering`]. fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>>; } pub enum CoreTemplatePropertyKind<'a> { String(BoxedTemplateProperty<'a, String>), StringList(BoxedTemplateProperty<'a, Vec<String>>), Boolean(BoxedTemplateProperty<'a, bool>), Integer(BoxedTemplateProperty<'a, i64>), IntegerOpt(BoxedTemplateProperty<'a, Option<i64>>), ConfigValue(BoxedTemplateProperty<'a, ConfigValue>), ConfigValueOpt(BoxedTemplateProperty<'a, Option<ConfigValue>>), Signature(BoxedTemplateProperty<'a, Signature>), Email(BoxedTemplateProperty<'a, Email>), SizeHint(BoxedTemplateProperty<'a, SizeHint>), Timestamp(BoxedTemplateProperty<'a, Timestamp>), TimestampRange(BoxedTemplateProperty<'a, TimestampRange>), // Both TemplateProperty and Template can represent a value to be evaluated // dynamically, which suggests that `Box<dyn Template + 'a>` could be // composed as `Box<dyn TemplateProperty<Output = Box<dyn Template ..`. // However, there's a subtle difference: TemplateProperty is strict on // error, whereas Template is usually lax and prints an error inline. If // `concat(x, y)` were a property returning Template, and if `y` failed to // evaluate, the whole expression would fail. In this example, a partial // evaluation output is more useful. That's one reason why Template isn't // wrapped in a TemplateProperty. Another reason is that the outermost // caller expects a Template, not a TemplateProperty of Template output. Template(Box<dyn Template + 'a>), Any(BoxedAnyProperty<'a>), AnyList(BoxedAnyProperty<'a>), } /// Implements `WrapTemplateProperty<type>` for core property types. /// /// Use `impl_core_property_wrappers!(<'a> Kind<'a> => Core);` to implement /// forwarding conversion. macro_rules! impl_core_property_wrappers { ($($head:tt)+) => { $crate::template_builder::impl_property_wrappers!($($head)+ { String(String), StringList(Vec<String>), Boolean(bool), Integer(i64), IntegerOpt(Option<i64>), ConfigValue(jj_lib::config::ConfigValue), ConfigValueOpt(Option<jj_lib::config::ConfigValue>), Signature(jj_lib::backend::Signature), Email($crate::templater::Email), SizeHint($crate::templater::SizeHint), Timestamp(jj_lib::backend::Timestamp), TimestampRange(jj_lib::op_store::TimestampRange), }); }; } pub(crate) use impl_core_property_wrappers; impl_core_property_wrappers!(<'a> CoreTemplatePropertyKind<'a>); impl<'a> CoreTemplatePropertyVar<'a> for CoreTemplatePropertyKind<'a> { fn wrap_template(template: Box<dyn Template + 'a>) -> Self { Self::Template(template) } fn wrap_any(property: BoxedAnyProperty<'a>) -> Self { Self::Any(property) } fn wrap_any_list(property: BoxedAnyProperty<'a>) -> Self { Self::AnyList(property) } fn type_name(&self) -> &'static str { match self { Self::String(_) => "String", Self::StringList(_) => "List<String>", Self::Boolean(_) => "Boolean", Self::Integer(_) => "Integer", Self::IntegerOpt(_) => "Option<Integer>", Self::ConfigValue(_) => "ConfigValue", Self::ConfigValueOpt(_) => "Option<ConfigValue>", Self::Signature(_) => "Signature", Self::Email(_) => "Email", Self::SizeHint(_) => "SizeHint", Self::Timestamp(_) => "Timestamp", Self::TimestampRange(_) => "TimestampRange", Self::Template(_) => "Template", Self::Any(_) => "Any", Self::AnyList(_) => "AnyList", } } fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> { match self { Self::String(property) => Some(property.map(|s| !s.is_empty()).into_dyn()), Self::StringList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()), Self::Boolean(property) => Some(property), Self::Integer(_) => None, Self::IntegerOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()), Self::ConfigValue(_) => None, Self::ConfigValueOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()), Self::Signature(_) => None, Self::Email(property) => Some(property.map(|e| !e.0.is_empty()).into_dyn()), Self::SizeHint(_) => None, Self::Timestamp(_) => None, Self::TimestampRange(_) => None, // Template and AnyList types could also be evaluated to boolean, // but it's less likely to apply label() or .map() and use the // result as conditional. Self::Template(_) => None, Self::Any(_) => None, Self::AnyList(_) => None, } } fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> { match self { Self::Integer(property) => Some(property), Self::IntegerOpt(property) => Some(property.try_unwrap("Integer").into_dyn()), _ => None, } } fn try_into_timestamp(self) -> Option<BoxedTemplateProperty<'a, Timestamp>> { match self { Self::Timestamp(property) => Some(property), _ => None, } } fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>> { match self { Self::String(property) => Some(property), _ => { let template = self.try_into_template()?; Some(PlainTextFormattedProperty::new(template).into_dyn()) } } } fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> { match self { Self::String(property) => Some(property.into_serialize()), Self::StringList(property) => Some(property.into_serialize()), Self::Boolean(property) => Some(property.into_serialize()), Self::Integer(property) => Some(property.into_serialize()), Self::IntegerOpt(property) => Some(property.into_serialize()), Self::ConfigValue(property) => { Some(property.map(config::to_serializable_value).into_serialize()) } Self::ConfigValueOpt(property) => Some( property .map(|opt| opt.map(config::to_serializable_value)) .into_serialize(), ), Self::Signature(property) => Some(property.into_serialize()), Self::Email(property) => Some(property.into_serialize()), Self::SizeHint(property) => Some(property.into_serialize()), Self::Timestamp(property) => Some(property.into_serialize()), Self::TimestampRange(property) => Some(property.into_serialize()), Self::Template(_) => None, Self::Any(property) => property.try_into_serialize(), Self::AnyList(property) => property.try_into_serialize(), } } fn try_into_template(self) -> Option<Box<dyn Template + 'a>> { match self { Self::String(property) => Some(property.into_template()), Self::StringList(property) => Some(property.into_template()), Self::Boolean(property) => Some(property.into_template()), Self::Integer(property) => Some(property.into_template()), Self::IntegerOpt(property) => Some(property.into_template()), Self::ConfigValue(property) => Some(property.into_template()), Self::ConfigValueOpt(property) => Some(property.into_template()), Self::Signature(property) => Some(property.into_template()), Self::Email(property) => Some(property.into_template()), Self::SizeHint(_) => None, Self::Timestamp(property) => Some(property.into_template()), Self::TimestampRange(property) => Some(property.into_template()), Self::Template(template) => Some(template), Self::Any(property) => property.try_into_template(), Self::AnyList(property) => property.try_into_template(), } } fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> { match (self, other) { (Self::String(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::String(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r.0).into_dyn()) } (Self::Boolean(lhs), Self::Boolean(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l) == r).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == Some(r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.0 == r).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::ConfigValueOpt(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::Any(_), _) => None, (Self::AnyList(_), _) => None, } } fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> { match (self, other) { (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l).cmp(&r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&Some(r))).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::ConfigValueOpt(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::Any(_), _) => None, (Self::AnyList(_), _) => None, } } } /// Function that translates global function call node. // The lifetime parameter 'a could be replaced with for<'a> to keep the method // table away from a certain lifetime. That's technically more correct, but I // couldn't find an easy way to expand that to the core template methods, which // are defined for L: TemplateLanguage<'a>. That's why the build fn table is // bound to a named lifetime, and therefore can't be cached statically. pub type TemplateBuildFunctionFn<'a, L, P> = fn(&L, &mut TemplateDiagnostics, &BuildContext<P>, &FunctionCallNode) -> TemplateParseResult<P>; type BuildMethodFn<'a, L, T, P> = fn( &L, &mut TemplateDiagnostics, &BuildContext<P>, T, &FunctionCallNode, ) -> TemplateParseResult<P>; /// Function that translates method call node of self type `T`. pub type TemplateBuildMethodFn<'a, L, T, P> = BuildMethodFn<'a, L, BoxedTemplateProperty<'a, T>, P>; /// Function that translates method call node of `Template`. pub type BuildTemplateMethodFn<'a, L, P> = BuildMethodFn<'a, L, Box<dyn Template + 'a>, P>; /// Function that translates method call node of `Any*`. pub type BuildAnyMethodFn<'a, L, P> = BuildMethodFn<'a, L, BoxedAnyProperty<'a>, P>; /// Table of functions that translate global function call node. pub type TemplateBuildFunctionFnMap<'a, L, P = <L as TemplateLanguage<'a>>::Property> = HashMap<&'static str, TemplateBuildFunctionFn<'a, L, P>>; /// Table of functions that translate method call node of self type `T`. pub type TemplateBuildMethodFnMap<'a, L, T, P = <L as TemplateLanguage<'a>>::Property> = HashMap<&'static str, TemplateBuildMethodFn<'a, L, T, P>>; /// Table of functions that translate method call node of `Template`. pub type BuildTemplateMethodFnMap<'a, L, P = <L as TemplateLanguage<'a>>::Property> =