1use 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
38pub 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 pub fn new(settings: &UserSettings) -> Self {
57 Self::with_keywords(HashMap::new(), settings)
58 }
59
60 pub fn with_keywords(
62 keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
63 settings: &UserSettings,
64 ) -> Self {
65 Self {
66 settings: settings.clone(),
68 build_fn_table: GenericTemplateBuildFnTable {
69 core: CoreTemplateBuildFnTable::builtin(),
70 keywords,
71 },
72 }
73 }
74
75 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 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
149macro_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
262pub type GenericTemplateBuildKeywordFn<'a, C> = Box<
268 dyn Fn(BoxedTemplateProperty<'a, C>) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
269 + 'a,
270>;
271
272pub type GenericTemplateBuildKeywordFnMap<'a, C> =
274 HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>;
275
276struct GenericTemplateBuildFnTable<'a, C> {
278 core: CoreTemplateBuildFnTable<
279 'a,
280 GenericTemplateLanguage<'a, C>,
281 GenericTemplatePropertyKind<'a, C>,
282 >,
283 keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
284}