Skip to main content

jj_cli/
generic_templater.rs

1// Copyright 2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::Ordering;
16use std::collections::HashMap;
17
18use bstr::BString;
19use jj_lib::backend::Timestamp;
20use jj_lib::settings::UserSettings;
21
22use crate::template_builder;
23use crate::template_builder::BuildContext;
24use crate::template_builder::CoreTemplateBuildFnTable;
25use crate::template_builder::CoreTemplatePropertyKind;
26use crate::template_builder::CoreTemplatePropertyVar;
27use crate::template_builder::TemplateLanguage;
28use crate::template_parser;
29use crate::template_parser::FunctionCallNode;
30use crate::template_parser::TemplateDiagnostics;
31use crate::template_parser::TemplateParseResult;
32use crate::templater::BoxedAnyProperty;
33use crate::templater::BoxedSerializeProperty;
34use crate::templater::BoxedTemplateProperty;
35use crate::templater::Template;
36use crate::templater::TemplatePropertyExt as _;
37
38/// General-purpose template language for basic value types.
39///
40/// This template language only supports the core template property types (plus
41/// the self type `C`.) The self type `C` is usually a tuple or struct of value
42/// types. It's cloned several times internally. Keyword functions need to be
43/// registered to extract properties from the self object.
44pub struct GenericTemplateLanguage<'a, C> {
45    settings: UserSettings,
46    build_fn_table: GenericTemplateBuildFnTable<'a, C>,
47}
48
49impl<'a, C> GenericTemplateLanguage<'a, C>
50where
51    C: serde::Serialize + 'a,
52{
53    /// Sets up environment with no keywords.
54    ///
55    /// New keyword functions can be registered by `add_keyword()`.
56    pub fn new(settings: &UserSettings) -> Self {
57        Self::with_keywords(HashMap::new(), settings)
58    }
59
60    /// Sets up environment with the given `keywords` table.
61    pub fn with_keywords(
62        keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
63        settings: &UserSettings,
64    ) -> Self {
65        Self {
66            // Clone settings to keep lifetime simple. It's cheap.
67            settings: settings.clone(),
68            build_fn_table: GenericTemplateBuildFnTable {
69                core: CoreTemplateBuildFnTable::builtin(),
70                keywords,
71            },
72        }
73    }
74
75    /// Registers new function that translates keyword to property.
76    ///
77    /// A keyword function returns `Self::Property`, which is basically a
78    /// closure tagged by its return type. The inner closure is usually wrapped
79    /// by `TemplateFunction`.
80    ///
81    /// ```ignore
82    /// language.add_keyword("name", |self_property| {
83    ///     let out_property = self_property.map(|v| v.to_string());
84    ///     Ok(out_property.into_dyn_wrapped())
85    /// });
86    /// ```
87    pub fn add_keyword<F>(&mut self, name: &'static str, build: F)
88    where
89        F: Fn(
90                BoxedTemplateProperty<'a, C>,
91            ) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
92            + 'a,
93    {
94        self.build_fn_table.keywords.insert(name, Box::new(build));
95    }
96}
97
98impl<'a, C> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C>
99where
100    C: serde::Serialize + 'a,
101{
102    type Property = GenericTemplatePropertyKind<'a, C>;
103
104    fn settings(&self) -> &UserSettings {
105        &self.settings
106    }
107
108    fn build_function(
109        &self,
110        diagnostics: &mut TemplateDiagnostics,
111        build_ctx: &BuildContext<Self::Property>,
112        function: &FunctionCallNode,
113    ) -> TemplateParseResult<Self::Property> {
114        let table = &self.build_fn_table.core;
115        table.build_function(self, diagnostics, build_ctx, function)
116    }
117
118    fn build_method(
119        &self,
120        diagnostics: &mut TemplateDiagnostics,
121        build_ctx: &BuildContext<Self::Property>,
122        property: Self::Property,
123        function: &FunctionCallNode,
124    ) -> TemplateParseResult<Self::Property> {
125        let type_name = property.type_name();
126        match property {
127            GenericTemplatePropertyKind::Core(property) => {
128                let table = &self.build_fn_table.core;
129                table.build_method(self, diagnostics, build_ctx, property, function)
130            }
131            GenericTemplatePropertyKind::Self_(property) => {
132                let table = &self.build_fn_table.keywords;
133                let build = template_parser::lookup_method(type_name, table, function)?;
134                // For simplicity, only 0-ary method is supported.
135                function.expect_no_arguments()?;
136                build(property)
137            }
138        }
139    }
140}
141
142pub enum GenericTemplatePropertyKind<'a, C> {
143    Core(CoreTemplatePropertyKind<'a>),
144    Self_(BoxedTemplateProperty<'a, C>),
145}
146
147template_builder::impl_core_property_wrappers!(<'a, C> GenericTemplatePropertyKind<'a, C> => Core);
148
149/// Implements conversion trait for the self property type.
150///
151/// Since we cannot guarantee that the generic type `C` does not conflict with
152/// the core template types, the conversion trait has to be implemented for each
153/// concrete type.
154macro_rules! impl_self_property_wrapper {
155    ($context:path) => {
156        $crate::template_builder::impl_property_wrappers!(
157            $crate::generic_templater::GenericTemplatePropertyKind<'static, $context> {
158                Self_($context),
159            }
160        );
161    };
162    (<$a:lifetime> $context:path) => {
163        $crate::template_builder::impl_property_wrappers!(
164            <$a> $crate::generic_templater::GenericTemplatePropertyKind<$a, $context> {
165                Self_($context),
166            }
167        );
168    };
169}
170
171pub(crate) use impl_self_property_wrapper;
172
173impl<'a, C> CoreTemplatePropertyVar<'a> for GenericTemplatePropertyKind<'a, C>
174where
175    C: serde::Serialize + 'a,
176{
177    fn wrap_template(template: Box<dyn Template + 'a>) -> Self {
178        Self::Core(CoreTemplatePropertyKind::wrap_template(template))
179    }
180
181    fn wrap_any(property: BoxedAnyProperty<'a>) -> Self {
182        Self::Core(CoreTemplatePropertyKind::wrap_any(property))
183    }
184
185    fn wrap_any_list(property: BoxedAnyProperty<'a>) -> Self {
186        Self::Core(CoreTemplatePropertyKind::wrap_any_list(property))
187    }
188
189    fn type_name(&self) -> &'static str {
190        match self {
191            Self::Core(property) => property.type_name(),
192            Self::Self_(_) => "Self",
193        }
194    }
195
196    fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'a, BString>, Self> {
197        match self {
198            Self::Core(property) => property.try_into_byte_string().map_err(Self::Core),
199            Self::Self_(_) => Err(self),
200        }
201    }
202
203    fn try_into_string(self) -> Result<BoxedTemplateProperty<'a, String>, Self> {
204        match self {
205            Self::Core(property) => property.try_into_string().map_err(Self::Core),
206            Self::Self_(_) => Err(self),
207        }
208    }
209
210    fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'a, bool>, Self> {
211        match self {
212            Self::Core(property) => property.try_into_boolean().map_err(Self::Core),
213            Self::Self_(_) => Err(self),
214        }
215    }
216
217    fn try_into_integer(self) -> Result<BoxedTemplateProperty<'a, i64>, Self> {
218        match self {
219            Self::Core(property) => property.try_into_integer().map_err(Self::Core),
220            Self::Self_(_) => Err(self),
221        }
222    }
223
224    fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'a, Timestamp>, Self> {
225        match self {
226            Self::Core(property) => property.try_into_timestamp().map_err(Self::Core),
227            Self::Self_(_) => Err(self),
228        }
229    }
230
231    fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> {
232        match self {
233            Self::Core(property) => property.try_into_serialize(),
234            Self::Self_(property) => Some(property.into_serialize()),
235        }
236    }
237
238    fn try_into_template(self) -> Option<Box<dyn Template + 'a>> {
239        match self {
240            Self::Core(property) => property.try_into_template(),
241            Self::Self_(_) => None,
242        }
243    }
244
245    fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> {
246        match (self, other) {
247            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_eq(rhs),
248            (Self::Core(_), _) => None,
249            (Self::Self_(_), _) => None,
250        }
251    }
252
253    fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> {
254        match (self, other) {
255            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_cmp(rhs),
256            (Self::Core(_), _) => None,
257            (Self::Self_(_), _) => None,
258        }
259    }
260}
261
262/// Function that translates keyword (or 0-ary method call node of the self type
263/// `C`.)
264///
265/// Because the `GenericTemplateLanguage` doesn't provide a way to pass around
266/// global resources, the keyword function is allowed to capture resources.
267pub type GenericTemplateBuildKeywordFn<'a, C> = Box<
268    dyn Fn(BoxedTemplateProperty<'a, C>) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
269        + 'a,
270>;
271
272/// Table of functions that translate keyword node.
273pub type GenericTemplateBuildKeywordFnMap<'a, C> =
274    HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>;
275
276/// Symbol table of methods available in the general-purpose template.
277struct GenericTemplateBuildFnTable<'a, C> {
278    core: CoreTemplateBuildFnTable<
279        'a,
280        GenericTemplateLanguage<'a, C>,
281        GenericTemplatePropertyKind<'a, C>,
282    >,
283    keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
284}