1use 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
40pub 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 pub fn new(settings: &UserSettings, current_dir: &Path) -> Self {
60 Self::with_keywords(HashMap::new(), settings, current_dir)
61 }
62
63 pub fn with_keywords(
65 keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
66 settings: &UserSettings,
67 current_dir: &Path,
68 ) -> Self {
69 Self {
70 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 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 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
158macro_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
271pub type GenericTemplateBuildKeywordFn<'a, C> = Box<
277 dyn Fn(BoxedTemplateProperty<'a, C>) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
278 + 'a,
279>;
280
281pub type GenericTemplateBuildKeywordFnMap<'a, C> =
283 HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>;
284
285struct GenericTemplateBuildFnTable<'a, C> {
287 core: CoreTemplateBuildFnTable<
288 'a,
289 GenericTemplateLanguage<'a, C>,
290 GenericTemplatePropertyKind<'a, C>,
291 >,
292 keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
293}