Martin von Zweigbergkformatter: reset colors on early drop46a8afe3 years agoHistory
1748 lines · 67.6 KB · Rust
RawDownload
// 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::collections::HashMap; use itertools::Itertools as _;use jj_lib::backend::{Signature, Timestamp}; use crate::template_parser::{    self, ExpressionKind, ExpressionNode, FunctionCallNode, MethodCallNode, TemplateParseError,    TemplateParseResult,};use crate::templater::{    ConcatTemplate, ConditionalTemplate, IntoTemplate, LabelTemplate, ListPropertyTemplate,    ListTemplate, Literal, PlainTextFormattedProperty, PropertyPlaceholder, ReformatTemplate,    SeparateTemplate, Template, TemplateFunction, TemplateProperty, TimestampRange,};use crate::{text_util, time_util}; /// Callbacks to build language-specific evaluation objects from AST nodes.pub trait TemplateLanguage<'a> {    type Context: 'a;    type Property: IntoTemplateProperty<'a, Self::Context>;     fn wrap_string(        &self,        property: impl TemplateProperty<Self::Context, Output = String> + 'a,    ) -> Self::Property;    fn wrap_string_list(        &self,        property: impl TemplateProperty<Self::Context, Output = Vec<String>> + 'a,    ) -> Self::Property;    fn wrap_boolean(        &self,        property: impl TemplateProperty<Self::Context, Output = bool> + 'a,    ) -> Self::Property;    fn wrap_integer(        &self,        property: impl TemplateProperty<Self::Context, Output = i64> + 'a,    ) -> Self::Property;    fn wrap_signature(        &self,        property: impl TemplateProperty<Self::Context, Output = Signature> + 'a,    ) -> Self::Property;    fn wrap_timestamp(        &self,        property: impl TemplateProperty<Self::Context, Output = Timestamp> + 'a,    ) -> Self::Property;    fn wrap_timestamp_range(        &self,        property: impl TemplateProperty<Self::Context, Output = TimestampRange> + 'a,    ) -> Self::Property;     fn wrap_template(&self, template: Box<dyn Template<Self::Context> + 'a>) -> Self::Property;    fn wrap_list_template(        &self,        template: Box<dyn ListTemplate<Self::Context> + 'a>,    ) -> Self::Property;     fn build_keyword(&self, name: &str, span: pest::Span) -> TemplateParseResult<Self::Property>;    fn build_method(        &self,        build_ctx: &BuildContext<Self::Property>,        property: Self::Property,        function: &FunctionCallNode,    ) -> TemplateParseResult<Self::Property>;} /// Implements `TemplateLanguage::wrap_<type>()` functions.////// - `impl_core_wrap_property_fns('a)` for `CoreTemplatePropertyKind`,/// - `impl_core_wrap_property_fns('a, MyKind::Core)` for `MyKind::Core(..)`.macro_rules! impl_core_wrap_property_fns {    ($a:lifetime) => {        $crate::template_builder::impl_core_wrap_property_fns!($a, std::convert::identity);    };    ($a:lifetime, $outer:path) => {        $crate::template_builder::impl_wrap_property_fns!(            $a, $crate::template_builder::CoreTemplatePropertyKind, $outer, {                wrap_string(String) => String,                wrap_string_list(Vec<String>) => StringList,                wrap_boolean(bool) => Boolean,                wrap_integer(i64) => Integer,                wrap_signature(jj_lib::backend::Signature) => Signature,                wrap_timestamp(jj_lib::backend::Timestamp) => Timestamp,                wrap_timestamp_range($crate::templater::TimestampRange) => TimestampRange,            }        );        fn wrap_template(            &self,            template: Box<dyn $crate::templater::Template<Self::Context> + $a>,        ) -> Self::Property {            use $crate::template_builder::CoreTemplatePropertyKind as Kind;            $outer(Kind::Template(template))        }        fn wrap_list_template(            &self,            template: Box<dyn $crate::templater::ListTemplate<Self::Context> + $a>,        ) -> Self::Property {            use $crate::template_builder::CoreTemplatePropertyKind as Kind;            $outer(Kind::ListTemplate(template))        }    };} macro_rules! impl_wrap_property_fns {    ($a:lifetime, $kind:path, $outer:path, { $( $func:ident($ty:ty) => $var:ident, )+ }) => {        $(            fn $func(                &self,                property: impl $crate::templater::TemplateProperty<                    Self::Context, Output = $ty> + $a,            ) -> Self::Property {                use $kind as Kind; // https://github.com/rust-lang/rust/issues/48067                $outer(Kind::$var(Box::new(property)))            }        )+    };} pub(crate) use {impl_core_wrap_property_fns, impl_wrap_property_fns}; /// Provides access to basic template property types.pub trait IntoTemplateProperty<'a, C> {    fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<C, Output = bool> + 'a>>;    fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<C, Output = i64> + 'a>>;     fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<C, Output = String> + 'a>>;    fn try_into_template(self) -> Option<Box<dyn Template<C> + 'a>>;} pub enum CoreTemplatePropertyKind<'a, I> {    String(Box<dyn TemplateProperty<I, Output = String> + 'a>),    StringList(Box<dyn TemplateProperty<I, Output = Vec<String>> + 'a>),    Boolean(Box<dyn TemplateProperty<I, Output = bool> + 'a>),    Integer(Box<dyn TemplateProperty<I, Output = i64> + 'a>),    Signature(Box<dyn TemplateProperty<I, Output = Signature> + 'a>),    Timestamp(Box<dyn TemplateProperty<I, Output = Timestamp> + 'a>),    TimestampRange(Box<dyn TemplateProperty<I, Output = TimestampRange> + 'a>),     // Similar to `TemplateProperty<I, Output = Box<dyn Template<()> + 'a>`, but doesn't    // capture `I` to produce `Template<()>`. The context `I` would have to be cloned    // to convert `Template<I>` to `Template<()>`.    Template(Box<dyn Template<I> + 'a>),    ListTemplate(Box<dyn ListTemplate<I> + 'a>),} impl<'a, I: 'a> IntoTemplateProperty<'a, I> for CoreTemplatePropertyKind<'a, I> {    fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<I, Output = bool> + 'a>> {        match self {            CoreTemplatePropertyKind::String(property) => {                Some(Box::new(TemplateFunction::new(property, |s| !s.is_empty())))            }            CoreTemplatePropertyKind::StringList(property) => {                Some(Box::new(TemplateFunction::new(property, |l| !l.is_empty())))            }            CoreTemplatePropertyKind::Boolean(property) => Some(property),            CoreTemplatePropertyKind::Integer(_) => None,            CoreTemplatePropertyKind::Signature(_) => None,            CoreTemplatePropertyKind::Timestamp(_) => None,            CoreTemplatePropertyKind::TimestampRange(_) => None,            // Template types could also be evaluated to boolean, but it's less likely            // to apply label() or .map() and use the result as conditional. It's also            // unclear whether ListTemplate should behave as a "list" or a "template".            CoreTemplatePropertyKind::Template(_) => None,            CoreTemplatePropertyKind::ListTemplate(_) => None,        }    }     fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<I, Output = i64> + 'a>> {        match self {            CoreTemplatePropertyKind::Integer(property) => Some(property),            _ => None,        }    }     fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<I, Output = String> + 'a>> {        match self {            CoreTemplatePropertyKind::String(property) => Some(property),            _ => {                let template = self.try_into_template()?;                Some(Box::new(PlainTextFormattedProperty::new(template)))            }        }    }     fn try_into_template(self) -> Option<Box<dyn Template<I> + 'a>> {        match self {            CoreTemplatePropertyKind::String(property) => Some(property.into_template()),            CoreTemplatePropertyKind::StringList(property) => Some(property.into_template()),            CoreTemplatePropertyKind::Boolean(property) => Some(property.into_template()),            CoreTemplatePropertyKind::Integer(property) => Some(property.into_template()),            CoreTemplatePropertyKind::Signature(property) => Some(property.into_template()),            CoreTemplatePropertyKind::Timestamp(property) => Some(property.into_template()),            CoreTemplatePropertyKind::TimestampRange(property) => Some(property.into_template()),            CoreTemplatePropertyKind::Template(template) => Some(template),            CoreTemplatePropertyKind::ListTemplate(template) => Some(template.into_template()),        }    }} /// Opaque struct that represents a template value.pub struct Expression<P> {    property: P,    labels: Vec<String>,} impl<P> Expression<P> {    fn unlabeled(property: P) -> Self {        let labels = vec![];        Expression { property, labels }    }     fn with_label(property: P, label: impl Into<String>) -> Self {        let labels = vec![label.into()];        Expression { property, labels }    }     pub fn try_into_boolean<'a, C: 'a>(        self,    ) -> Option<Box<dyn TemplateProperty<C, Output = bool> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        self.property.try_into_boolean()    }     pub fn try_into_integer<'a, C: 'a>(        self,    ) -> Option<Box<dyn TemplateProperty<C, Output = i64> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        self.property.try_into_integer()    }     pub fn try_into_plain_text<'a, C: 'a>(        self,    ) -> Option<Box<dyn TemplateProperty<C, Output = String> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        self.property.try_into_plain_text()    }     pub fn try_into_template<'a, C: 'a>(self) -> Option<Box<dyn Template<C> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        let template = self.property.try_into_template()?;        if self.labels.is_empty() {            Some(template)        } else {            Some(Box::new(LabelTemplate::new(template, Literal(self.labels))))        }    }} pub struct BuildContext<'i, P> {    /// Map of functions to create `L::Property`.    local_variables: HashMap<&'i str, &'i (dyn Fn() -> P)>,} fn build_method_call<'a, L: TemplateLanguage<'a>>(    language: &L,    build_ctx: &BuildContext<L::Property>,    method: &MethodCallNode,) -> TemplateParseResult<Expression<L::Property>> {    let mut expression = build_expression(language, build_ctx, &method.object)?;    expression.property =        language.build_method(build_ctx, expression.property, &method.function)?;    expression.labels.push(method.function.name.to_owned());    Ok(expression)} pub fn build_core_method<'a, L: TemplateLanguage<'a>>(    language: &L,    build_ctx: &BuildContext<L::Property>,    property: CoreTemplatePropertyKind<'a, L::Context>,    function: &FunctionCallNode,) -> TemplateParseResult<L::Property> {    match property {        CoreTemplatePropertyKind::String(property) => {            build_string_method(language, build_ctx, property, function)        }        CoreTemplatePropertyKind::StringList(property) => {            build_formattable_list_method(language, build_ctx, property, function, |item| {                language.wrap_string(item)            })        }        CoreTemplatePropertyKind::Boolean(property) => {            build_boolean_method(language, build_ctx, property, function)        }        CoreTemplatePropertyKind::Integer(property) => {            build_integer_method(language, build_ctx, property, function)        }        CoreTemplatePropertyKind::Signature(property) => {            build_signature_method(language, build_ctx, property, function)        }        CoreTemplatePropertyKind::Timestamp(property) => {            build_timestamp_method(language, build_ctx, property, function)        }        CoreTemplatePropertyKind::TimestampRange(property) => {            build_timestamp_range_method(language, build_ctx, property, function)        }        CoreTemplatePropertyKind::Template(_) => {            Err(TemplateParseError::no_such_method("Template", function))        }        CoreTemplatePropertyKind::ListTemplate(template) => {            build_list_template_method(language, build_ctx, template, function)        }    }} fn build_string_method<'a, L: TemplateLanguage<'a>>(    language: &L,    build_ctx: &BuildContext<L::Property>,    self_property: impl TemplateProperty<L::Context, Output = String> + 'a,    function: &FunctionCallNode,) -> TemplateParseResult<L::Property> {    let property = match function.name {        "contains" => {            let [needle_node] = template_parser::expect_exact_arguments(function)?;            // TODO: or .try_into_string() to disable implicit type cast?            let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;            language.wrap_boolean(TemplateFunction::new(                (self_property, needle_property),                |(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)| {                    // TODO: If we add .len() method, we'll expose bytes-based and char-based APIs.                    // Having different index units would be confusing, so we might want to change                    // .substr() to bytes-based and round up/down towards char or grapheme-cluster                    // boundary.                    let to_idx = |i: i64| -> usize {                        let magnitude = usize::try_from(i.unsigned_abs()).unwrap_or(usize::MAX);                        if i < 0 {                            s.chars().count().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| {                s.lines().next().unwrap_or_default().to_string()            }))        }        "lines" => {            template_parser::expect_no_arguments(function)?;            language.wrap_string_list(TemplateFunction::new(self_property, |s| {                s.lines().map(|l| l.to_owned()).collect()            }))        }        "upper" => {            template_parser::expect_no_arguments(function)?;            language.wrap_string(TemplateFunction::new(self_property, |s| s.to_uppercase()))        }        "lower" => {            template_parser::expect_no_arguments(function)?;            language.wrap_string(TemplateFunction::new(self_property, |s| s.to_lowercase()))        }        _ => return Err(TemplateParseError::no_such_method("String", function)),    };    Ok(property)} fn build_boolean_method<'a, L: TemplateLanguage<'a>>(    _language: &L,    _build_ctx: &BuildContext<L::Property>,    _self_property: impl TemplateProperty<L::Context, Output = bool> + 'a,    function: &FunctionCallNode,) -> TemplateParseResult<L::Property> {    Err(TemplateParseError::no_such_method("Boolean", function))} fn build_integer_method<'a, L: TemplateLanguage<'a>>(    _language: &L,    _build_ctx: &BuildContext<L::Property>,    _self_property: impl TemplateProperty<L::Context, Output = i64> + 'a,    function: &FunctionCallNode,) -> TemplateParseResult<L::Property> {    Err(TemplateParseError::no_such_method("Integer", function))} fn build_signature_method<'a, L: TemplateLanguage<'a>>(    language: &L,    _build_ctx: &BuildContext<L::Property>,    self_property: impl TemplateProperty<L::Context, Output = Signature> + 'a,    function: &FunctionCallNode,) -> TemplateParseResult<L::Property> {    let property = match function.name {        "name" => {            template_parser::expect_no_arguments(function)?;            language.wrap_string(TemplateFunction::new(self_property, |signature| {                signature.name            }))        }        "email" => {            template_parser::expect_no_arguments(function)?;            language.wrap_string(TemplateFunction::new(self_property, |signature| {                signature.email            }))        }        "username" => {            template_parser::expect_no_arguments(function)?;            language.wrap_string(TemplateFunction::new(self_property, |signature| {                let (username, _) = text_util::split_email(&signature.email);                username.to_owned()            }))        }        "timestamp" => {            template_parser::expect_no_arguments(function)?;            language.wrap_timestamp(TemplateFunction::new(self_property, |signature| {                signature.timestamp            }))        }        _ => return Err(TemplateParseError::no_such_method("Signature", function)),    };    Ok(property)} fn build_timestamp_method<'a, L: TemplateLanguage<'a>>(    language: &L,    _build_ctx: &BuildContext<L::Property>,    self_property: impl TemplateProperty<L::Context, Output = Timestamp> + 'a,    function: &FunctionCallNode,

Lines 1–500 of 1748.