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