// 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 std::path::Path;use std::path::PathBuf; use bstr::BString;use bstr::ByteSlice as _;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::file_util;use jj_lib::hex_util;use jj_lib::op_store::TimestampRange;use jj_lib::settings::UserSettings;use jj_lib::str_util::StringPattern;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::RegexCaptures;use crate::templater::SeparateTemplate;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateFormatter;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::text_util::write_replaced;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; /// Returns the working directory for filesystem path template methods. fn current_dir(&self) -> &Path; /// 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, BString>, Self: WrapTemplateProperty<'a, Vec<BString>>, 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, PathBuf>, Self: WrapTemplateProperty<'a, Option<PathBuf>>, Self: WrapTemplateProperty<'a, Signature>, Self: WrapTemplateProperty<'a, Email>, Self: WrapTemplateProperty<'a, SizeHint>, Self: WrapTemplateProperty<'a, RegexCaptures>, 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; /// Extracts property of `ByteString` type or newtype. fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'a, BString>, Self>; /// Extracts property of `String` type or newtype. fn try_into_string(self) -> Result<BoxedTemplateProperty<'a, String>, Self>; // TODO: rename try_into_boolean() because it isn't a pure extraction fn? fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'a, bool>, Self>; fn try_into_integer(self) -> Result<BoxedTemplateProperty<'a, i64>, Self>; fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'a, Timestamp>, Self>; 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> { ByteString(BoxedTemplateProperty<'a, BString>), ByteStringList(BoxedTemplateProperty<'a, Vec<BString>>), 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>>), FsPath(BoxedTemplateProperty<'a, PathBuf>), FsPathOpt(BoxedTemplateProperty<'a, Option<PathBuf>>), Signature(BoxedTemplateProperty<'a, Signature>), Email(BoxedTemplateProperty<'a, Email>), SizeHint(BoxedTemplateProperty<'a, SizeHint>), RegexCaptures(BoxedTemplateProperty<'a, RegexCaptures>), 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)+ { ByteString(bstr::BString), ByteStringList(Vec<bstr::BString>), String(String), StringList(Vec<String>), Boolean(bool), Integer(i64), IntegerOpt(Option<i64>), ConfigValue(jj_lib::config::ConfigValue), ConfigValueOpt(Option<jj_lib::config::ConfigValue>), FsPath(std::path::PathBuf), FsPathOpt(Option<std::path::PathBuf>), Signature(jj_lib::backend::Signature), Email($crate::templater::Email), SizeHint($crate::templater::SizeHint), RegexCaptures($crate::templater::RegexCaptures), 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::ByteString(_) => "ByteString", Self::ByteStringList(_) => "List<ByteString>", 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::FsPath(_) => "FsPath", Self::FsPathOpt(_) => "Option<FsPath>", Self::Signature(_) => "Signature", Self::Email(_) => "Email", Self::SizeHint(_) => "SizeHint", Self::RegexCaptures(_) => "RegexCaptures", Self::Timestamp(_) => "Timestamp", Self::TimestampRange(_) => "TimestampRange", Self::Template(_) => "Template", Self::Any(_) => "Any", Self::AnyList(_) => "AnyList", } } fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'a, BString>, Self> { match self { Self::ByteString(property) => Ok(property), _ => Err(self), } } fn try_into_string(self) -> Result<BoxedTemplateProperty<'a, String>, Self> { match self { Self::String(property) => Ok(property), _ => Err(self), } } fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'a, bool>, Self> { match self { Self::ByteString(property) => Ok(property.map(|s| !s.is_empty()).into_dyn()), Self::ByteStringList(property) => Ok(property.map(|l| !l.is_empty()).into_dyn()), Self::String(property) => Ok(property.map(|s| !s.is_empty()).into_dyn()), Self::StringList(property) => Ok(property.map(|l| !l.is_empty()).into_dyn()), Self::Boolean(property) => Ok(property), Self::Integer(_) => Err(self), Self::IntegerOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()), Self::ConfigValue(_) => Err(self), Self::ConfigValueOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()), Self::FsPath(_) => Err(self), Self::FsPathOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()), Self::Signature(_) => Err(self), Self::Email(property) => Ok(property.map(|e| !e.0.is_empty()).into_dyn()), Self::SizeHint(_) => Err(self), Self::RegexCaptures(_) => Err(self), Self::Timestamp(_) => Err(self), Self::TimestampRange(_) => Err(self), // 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(_) => Err(self), Self::Any(_) => Err(self), Self::AnyList(_) => Err(self), } } fn try_into_integer(self) -> Result<BoxedTemplateProperty<'a, i64>, Self> { match self { Self::Integer(property) => Ok(property), Self::IntegerOpt(property) => Ok(property.try_unwrap("Integer").into_dyn()), _ => Err(self), } } fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'a, Timestamp>, Self> { match self { Self::Timestamp(property) => Ok(property), _ => Err(self), } } fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> { match self { Self::ByteString(property) => Some(property.into_serialize()), Self::ByteStringList(property) => Some(property.into_serialize()), 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::FsPath(property) => Some(property.into_serialize()), Self::FsPathOpt(property) => Some(property.into_serialize()), Self::Signature(property) => Some(property.into_serialize()), Self::Email(property) => Some(property.into_serialize()), Self::SizeHint(property) => Some(property.into_serialize()), Self::RegexCaptures(_) => None, 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::ByteString(property) => Some(property.into_template()), Self::ByteStringList(property) => Some(property.into_template()), 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::FsPath(property) => Some(property.into_template()), Self::FsPathOpt(property) => Some(property.into_template()), Self::Signature(property) => Some(property.into_template()), Self::Email(property) => Some(property.into_template()), Self::SizeHint(_) => None, Self::RegexCaptures(_) => 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::ByteString(lhs), Self::ByteString(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::ByteString(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::String(lhs), Self::ByteString(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (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::ByteString(_), _) => None, (Self::ByteStringList(_), _) => None, (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::ConfigValueOpt(_), _) => None, (Self::FsPath(_), _) => None, (Self::FsPathOpt(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::RegexCaptures(_), _) => 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>> {Lines 1–500 of 5939. 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 std::path::Path; use std::path::PathBuf; use bstr::BString; use bstr::ByteSlice as _; 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::file_util; use jj_lib::hex_util; use jj_lib::op_store::TimestampRange; use jj_lib::settings::UserSettings; use jj_lib::str_util::StringPattern; 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::RegexCaptures; use crate::templater::SeparateTemplate; use crate::templater::SizeHint; use crate::templater::Template; use crate::templater::TemplateFormatter; 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::text_util::write_replaced; 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; /// Returns the working directory for filesystem path template methods. fn current_dir(&self) -> &Path; /// 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, BString>, Self: WrapTemplateProperty<'a, Vec<BString>>, 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, PathBuf>, Self: WrapTemplateProperty<'a, Option<PathBuf>>, Self: WrapTemplateProperty<'a, Signature>, Self: WrapTemplateProperty<'a, Email>, Self: WrapTemplateProperty<'a, SizeHint>, Self: WrapTemplateProperty<'a, RegexCaptures>, 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; /// Extracts property of `ByteString` type or newtype. fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'a, BString>, Self>; /// Extracts property of `String` type or newtype. fn try_into_string(self) -> Result<BoxedTemplateProperty<'a, String>, Self>; // TODO: rename try_into_boolean() because it isn't a pure extraction fn? fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'a, bool>, Self>; fn try_into_integer(self) -> Result<BoxedTemplateProperty<'a, i64>, Self>; fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'a, Timestamp>, Self>; 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> { ByteString(BoxedTemplateProperty<'a, BString>), ByteStringList(BoxedTemplateProperty<'a, Vec<BString>>), 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>>), FsPath(BoxedTemplateProperty<'a, PathBuf>), FsPathOpt(BoxedTemplateProperty<'a, Option<PathBuf>>), Signature(BoxedTemplateProperty<'a, Signature>), Email(BoxedTemplateProperty<'a, Email>), SizeHint(BoxedTemplateProperty<'a, SizeHint>), RegexCaptures(BoxedTemplateProperty<'a, RegexCaptures>), 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)+ { ByteString(bstr::BString), ByteStringList(Vec<bstr::BString>), String(String), StringList(Vec<String>), Boolean(bool), Integer(i64), IntegerOpt(Option<i64>), ConfigValue(jj_lib::config::ConfigValue), ConfigValueOpt(Option<jj_lib::config::ConfigValue>), FsPath(std::path::PathBuf), FsPathOpt(Option<std::path::PathBuf>), Signature(jj_lib::backend::Signature), Email($crate::templater::Email), SizeHint($crate::templater::SizeHint), RegexCaptures($crate::templater::RegexCaptures), 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::ByteString(_) => "ByteString", Self::ByteStringList(_) => "List<ByteString>", 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::FsPath(_) => "FsPath", Self::FsPathOpt(_) => "Option<FsPath>", Self::Signature(_) => "Signature", Self::Email(_) => "Email", Self::SizeHint(_) => "SizeHint", Self::RegexCaptures(_) => "RegexCaptures", Self::Timestamp(_) => "Timestamp", Self::TimestampRange(_) => "TimestampRange", Self::Template(_) => "Template", Self::Any(_) => "Any", Self::AnyList(_) => "AnyList", } } fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'a, BString>, Self> { match self { Self::ByteString(property) => Ok(property), _ => Err(self), } } fn try_into_string(self) -> Result<BoxedTemplateProperty<'a, String>, Self> { match self { Self::String(property) => Ok(property), _ => Err(self), } } fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'a, bool>, Self> { match self { Self::ByteString(property) => Ok(property.map(|s| !s.is_empty()).into_dyn()), Self::ByteStringList(property) => Ok(property.map(|l| !l.is_empty()).into_dyn()), Self::String(property) => Ok(property.map(|s| !s.is_empty()).into_dyn()), Self::StringList(property) => Ok(property.map(|l| !l.is_empty()).into_dyn()), Self::Boolean(property) => Ok(property), Self::Integer(_) => Err(self), Self::IntegerOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()), Self::ConfigValue(_) => Err(self), Self::ConfigValueOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()), Self::FsPath(_) => Err(self), Self::FsPathOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()), Self::Signature(_) => Err(self), Self::Email(property) => Ok(property.map(|e| !e.0.is_empty()).into_dyn()), Self::SizeHint(_) => Err(self), Self::RegexCaptures(_) => Err(self), Self::Timestamp(_) => Err(self), Self::TimestampRange(_) => Err(self), // 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(_) => Err(self), Self::Any(_) => Err(self), Self::AnyList(_) => Err(self), } } fn try_into_integer(self) -> Result<BoxedTemplateProperty<'a, i64>, Self> { match self { Self::Integer(property) => Ok(property), Self::IntegerOpt(property) => Ok(property.try_unwrap("Integer").into_dyn()), _ => Err(self), } } fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'a, Timestamp>, Self> { match self { Self::Timestamp(property) => Ok(property), _ => Err(self), } } fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> { match self { Self::ByteString(property) => Some(property.into_serialize()), Self::ByteStringList(property) => Some(property.into_serialize()), 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::FsPath(property) => Some(property.into_serialize()), Self::FsPathOpt(property) => Some(property.into_serialize()), Self::Signature(property) => Some(property.into_serialize()), Self::Email(property) => Some(property.into_serialize()), Self::SizeHint(property) => Some(property.into_serialize()), Self::RegexCaptures(_) => None, 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::ByteString(property) => Some(property.into_template()), Self::ByteStringList(property) => Some(property.into_template()), 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::FsPath(property) => Some(property.into_template()), Self::FsPathOpt(property) => Some(property.into_template()), Self::Signature(property) => Some(property.into_template()), Self::Email(property) => Some(property.into_template()), Self::SizeHint(_) => None, Self::RegexCaptures(_) => 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::ByteString(lhs), Self::ByteString(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::ByteString(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::String(lhs), Self::ByteString(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (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::ByteString(_), _) => None, (Self::ByteStringList(_), _) => None, (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::ConfigValueOpt(_), _) => None, (Self::FsPath(_), _) => None, (Self::FsPathOpt(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::RegexCaptures(_), _) => 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>> {