1use std::any::Any;
18use std::cmp::Ordering;
19use std::collections::HashMap;
20use std::io;
21
22use itertools::Itertools as _;
23use jj_lib::extensions_map::ExtensionsMap;
24use jj_lib::object_id::ObjectId as _;
25use jj_lib::op_store::OperationId;
26use jj_lib::operation::Operation;
27use jj_lib::repo::RepoLoader;
28use jj_lib::settings::UserSettings;
29
30use crate::template_builder;
31use crate::template_builder::BuildContext;
32use crate::template_builder::CoreTemplateBuildFnTable;
33use crate::template_builder::CoreTemplatePropertyKind;
34use crate::template_builder::CoreTemplatePropertyVar;
35use crate::template_builder::TemplateBuildMethodFnMap;
36use crate::template_builder::TemplateLanguage;
37use crate::template_builder::merge_fn_map;
38use crate::template_parser;
39use crate::template_parser::FunctionCallNode;
40use crate::template_parser::TemplateDiagnostics;
41use crate::template_parser::TemplateParseResult;
42use crate::templater::BoxedSerializeProperty;
43use crate::templater::BoxedTemplateProperty;
44use crate::templater::ListTemplate;
45use crate::templater::PlainTextFormattedProperty;
46use crate::templater::Template;
47use crate::templater::TemplateFormatter;
48use crate::templater::TemplatePropertyExt as _;
49use crate::templater::WrapTemplateProperty;
50
51pub trait OperationTemplateLanguageExtension {
52 fn build_fn_table(&self) -> OperationTemplateLanguageBuildFnTable;
53
54 fn build_cache_extensions(&self, extensions: &mut ExtensionsMap);
55}
56
57pub trait OperationTemplateEnvironment {
59 fn repo_loader(&self) -> &RepoLoader;
60 fn current_op_id(&self) -> Option<&OperationId>;
61}
62
63pub struct OperationTemplateLanguage {
65 repo_loader: RepoLoader,
66 current_op_id: Option<OperationId>,
67 build_fn_table: OperationTemplateLanguageBuildFnTable,
68 cache_extensions: ExtensionsMap,
69}
70
71impl OperationTemplateLanguage {
72 pub fn new(
75 repo_loader: &RepoLoader,
76 current_op_id: Option<&OperationId>,
77 extensions: &[impl AsRef<dyn OperationTemplateLanguageExtension>],
78 ) -> Self {
79 let mut build_fn_table = OperationTemplateLanguageBuildFnTable::builtin();
80 let mut cache_extensions = ExtensionsMap::empty();
81
82 for extension in extensions {
83 build_fn_table.merge(extension.as_ref().build_fn_table());
84 extension
85 .as_ref()
86 .build_cache_extensions(&mut cache_extensions);
87 }
88
89 Self {
90 repo_loader: repo_loader.clone(),
92 current_op_id: current_op_id.cloned(),
93 build_fn_table,
94 cache_extensions,
95 }
96 }
97}
98
99impl TemplateLanguage<'static> for OperationTemplateLanguage {
100 type Property = OperationTemplateLanguagePropertyKind;
101
102 fn settings(&self) -> &UserSettings {
103 self.repo_loader.settings()
104 }
105
106 fn build_function(
107 &self,
108 diagnostics: &mut TemplateDiagnostics,
109 build_ctx: &BuildContext<Self::Property>,
110 function: &FunctionCallNode,
111 ) -> TemplateParseResult<Self::Property> {
112 let table = &self.build_fn_table.core;
113 table.build_function(self, diagnostics, build_ctx, function)
114 }
115
116 fn build_method(
117 &self,
118 diagnostics: &mut TemplateDiagnostics,
119 build_ctx: &BuildContext<Self::Property>,
120 property: Self::Property,
121 function: &FunctionCallNode,
122 ) -> TemplateParseResult<Self::Property> {
123 match property {
124 OperationTemplateLanguagePropertyKind::Core(property) => {
125 let table = &self.build_fn_table.core;
126 table.build_method(self, diagnostics, build_ctx, property, function)
127 }
128 OperationTemplateLanguagePropertyKind::Operation(property) => {
129 let table = &self.build_fn_table.operation;
130 table.build_method(self, diagnostics, build_ctx, property, function)
131 }
132 }
133 }
134}
135
136impl OperationTemplateEnvironment for OperationTemplateLanguage {
137 fn repo_loader(&self) -> &RepoLoader {
138 &self.repo_loader
139 }
140
141 fn current_op_id(&self) -> Option<&OperationId> {
142 self.current_op_id.as_ref()
143 }
144}
145
146impl OperationTemplateLanguage {
147 pub fn cache_extension<T: Any>(&self) -> Option<&T> {
148 self.cache_extensions.get::<T>()
149 }
150}
151
152pub trait OperationTemplatePropertyVar<'a>
154where
155 Self: WrapTemplateProperty<'a, Operation>,
156 Self: WrapTemplateProperty<'a, Option<Operation>>,
157 Self: WrapTemplateProperty<'a, Vec<Operation>>,
158 Self: WrapTemplateProperty<'a, OperationId>,
159{
160}
161
162pub enum OperationTemplatePropertyKind<'a> {
164 Operation(BoxedTemplateProperty<'a, Operation>),
165 OperationOpt(BoxedTemplateProperty<'a, Option<Operation>>),
166 OperationList(BoxedTemplateProperty<'a, Vec<Operation>>),
167 OperationId(BoxedTemplateProperty<'a, OperationId>),
168}
169
170macro_rules! impl_operation_property_wrappers {
175 ($($head:tt)+) => {
176 $crate::template_builder::impl_property_wrappers!($($head)+ {
177 Operation(jj_lib::operation::Operation),
178 OperationOpt(Option<jj_lib::operation::Operation>),
179 OperationList(Vec<jj_lib::operation::Operation>),
180 OperationId(jj_lib::op_store::OperationId),
181 });
182 };
183}
184
185pub(crate) use impl_operation_property_wrappers;
186
187impl_operation_property_wrappers!(<'a> OperationTemplatePropertyKind<'a>);
188
189impl<'a> OperationTemplatePropertyKind<'a> {
190 pub fn type_name(&self) -> &'static str {
191 match self {
192 Self::Operation(_) => "Operation",
193 Self::OperationOpt(_) => "Option<Operation>",
194 Self::OperationList(_) => "List<Operation>",
195 Self::OperationId(_) => "OperationId",
196 }
197 }
198
199 pub fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> {
200 match self {
201 Self::Operation(_) => None,
202 Self::OperationOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()),
203 Self::OperationList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
204 Self::OperationId(_) => None,
205 }
206 }
207
208 pub fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> {
209 None
210 }
211
212 pub fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>> {
213 let template = self.try_into_template()?;
214 Some(PlainTextFormattedProperty::new(template).into_dyn())
215 }
216
217 pub fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> {
218 match self {
219 Self::Operation(property) => Some(property.into_serialize()),
220 Self::OperationOpt(property) => Some(property.into_serialize()),
221 Self::OperationList(property) => Some(property.into_serialize()),
222 Self::OperationId(property) => Some(property.into_serialize()),
223 }
224 }
225
226 pub fn try_into_template(self) -> Option<Box<dyn Template + 'a>> {
227 match self {
228 Self::Operation(_) => None,
229 Self::OperationOpt(_) => None,
230 Self::OperationList(_) => None,
231 Self::OperationId(property) => Some(property.into_template()),
232 }
233 }
234
235 pub fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> {
236 match (self, other) {
237 (Self::Operation(_), _) => None,
238 (Self::OperationOpt(_), _) => None,
239 (Self::OperationList(_), _) => None,
240 (Self::OperationId(_), _) => None,
241 }
242 }
243
244 pub fn try_into_eq_core(
245 self,
246 other: CoreTemplatePropertyKind<'a>,
247 ) -> Option<BoxedTemplateProperty<'a, bool>> {
248 match (self, other) {
249 (Self::Operation(_), _) => None,
250 (Self::OperationOpt(_), _) => None,
251 (Self::OperationList(_), _) => None,
252 (Self::OperationId(_), _) => None,
253 }
254 }
255
256 pub fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> {
257 match (self, other) {
258 (Self::Operation(_), _) => None,
259 (Self::OperationOpt(_), _) => None,
260 (Self::OperationList(_), _) => None,
261 (Self::OperationId(_), _) => None,
262 }
263 }
264
265 pub fn try_into_cmp_core(
266 self,
267 other: CoreTemplatePropertyKind<'a>,
268 ) -> Option<BoxedTemplateProperty<'a, Ordering>> {
269 match (self, other) {
270 (Self::Operation(_), _) => None,
271 (Self::OperationOpt(_), _) => None,
272 (Self::OperationList(_), _) => None,
273 (Self::OperationId(_), _) => None,
274 }
275 }
276}
277
278pub enum OperationTemplateLanguagePropertyKind {
280 Core(CoreTemplatePropertyKind<'static>),
281 Operation(OperationTemplatePropertyKind<'static>),
282}
283
284template_builder::impl_core_property_wrappers!(OperationTemplateLanguagePropertyKind => Core);
285impl_operation_property_wrappers!(OperationTemplateLanguagePropertyKind => Operation);
286
287impl CoreTemplatePropertyVar<'static> for OperationTemplateLanguagePropertyKind {
288 fn wrap_template(template: Box<dyn Template>) -> Self {
289 Self::Core(CoreTemplatePropertyKind::wrap_template(template))
290 }
291
292 fn wrap_list_template(template: Box<dyn ListTemplate>) -> Self {
293 Self::Core(CoreTemplatePropertyKind::wrap_list_template(template))
294 }
295
296 fn type_name(&self) -> &'static str {
297 match self {
298 Self::Core(property) => property.type_name(),
299 Self::Operation(property) => property.type_name(),
300 }
301 }
302
303 fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'static, bool>> {
304 match self {
305 Self::Core(property) => property.try_into_boolean(),
306 Self::Operation(property) => property.try_into_boolean(),
307 }
308 }
309
310 fn try_into_integer(self) -> Option<BoxedTemplateProperty<'static, i64>> {
311 match self {
312 Self::Core(property) => property.try_into_integer(),
313 Self::Operation(property) => property.try_into_integer(),
314 }
315 }
316
317 fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'static, String>> {
318 match self {
319 Self::Core(property) => property.try_into_stringify(),
320 Self::Operation(property) => property.try_into_stringify(),
321 }
322 }
323
324 fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'static>> {
325 match self {
326 Self::Core(property) => property.try_into_serialize(),
327 Self::Operation(property) => property.try_into_serialize(),
328 }
329 }
330
331 fn try_into_template(self) -> Option<Box<dyn Template>> {
332 match self {
333 Self::Core(property) => property.try_into_template(),
334 Self::Operation(property) => property.try_into_template(),
335 }
336 }
337
338 fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'static, bool>> {
339 match (self, other) {
340 (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_eq(rhs),
341 (Self::Core(lhs), Self::Operation(rhs)) => rhs.try_into_eq_core(lhs),
342 (Self::Operation(lhs), Self::Core(rhs)) => lhs.try_into_eq_core(rhs),
343 (Self::Operation(lhs), Self::Operation(rhs)) => lhs.try_into_eq(rhs),
344 }
345 }
346
347 fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'static, Ordering>> {
348 match (self, other) {
349 (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_cmp(rhs),
350 (Self::Core(lhs), Self::Operation(rhs)) => rhs
351 .try_into_cmp_core(lhs)
352 .map(|property| property.map(Ordering::reverse).into_dyn()),
353 (Self::Operation(lhs), Self::Core(rhs)) => lhs.try_into_cmp_core(rhs),
354 (Self::Operation(lhs), Self::Operation(rhs)) => lhs.try_into_cmp(rhs),
355 }
356 }
357}
358
359impl OperationTemplatePropertyVar<'static> for OperationTemplateLanguagePropertyKind {}
360
361pub struct OperationTemplateBuildFnTable<'a, L: ?Sized, P = <L as TemplateLanguage<'a>>::Property> {
363 pub operation_methods: TemplateBuildMethodFnMap<'a, L, Operation, P>,
364 pub operation_list_methods: TemplateBuildMethodFnMap<'a, L, Vec<Operation>, P>,
365 pub operation_id_methods: TemplateBuildMethodFnMap<'a, L, OperationId, P>,
366}
367
368impl<L: ?Sized, P> OperationTemplateBuildFnTable<'_, L, P> {
369 pub fn empty() -> Self {
370 Self {
371 operation_methods: HashMap::new(),
372 operation_list_methods: HashMap::new(),
373 operation_id_methods: HashMap::new(),
374 }
375 }
376
377 pub fn merge(&mut self, other: Self) {
378 let Self {
379 operation_methods,
380 operation_list_methods,
381 operation_id_methods,
382 } = other;
383
384 merge_fn_map(&mut self.operation_methods, operation_methods);
385 merge_fn_map(&mut self.operation_list_methods, operation_list_methods);
386 merge_fn_map(&mut self.operation_id_methods, operation_id_methods);
387 }
388}
389
390impl<'a, L> OperationTemplateBuildFnTable<'a, L, L::Property>
391where
392 L: TemplateLanguage<'a> + OperationTemplateEnvironment + ?Sized,
393 L::Property: OperationTemplatePropertyVar<'a>,
394{
395 pub fn builtin() -> Self {
397 Self {
398 operation_methods: builtin_operation_methods(),
399 operation_list_methods: template_builder::builtin_unformattable_list_methods(),
400 operation_id_methods: builtin_operation_id_methods(),
401 }
402 }
403
404 pub fn build_method(
407 &self,
408 language: &L,
409 diagnostics: &mut TemplateDiagnostics,
410 build_ctx: &BuildContext<L::Property>,
411 property: OperationTemplatePropertyKind<'a>,
412 function: &FunctionCallNode,
413 ) -> TemplateParseResult<L::Property> {
414 let type_name = property.type_name();
415 match property {
416 OperationTemplatePropertyKind::Operation(property) => {
417 let table = &self.operation_methods;
418 let build = template_parser::lookup_method(type_name, table, function)?;
419 build(language, diagnostics, build_ctx, property, function)
420 }
421 OperationTemplatePropertyKind::OperationOpt(property) => {
422 let type_name = "Operation";
423 let table = &self.operation_methods;
424 let build = template_parser::lookup_method(type_name, table, function)?;
425 let inner_property = property.try_unwrap(type_name).into_dyn();
426 build(language, diagnostics, build_ctx, inner_property, function)
427 }
428 OperationTemplatePropertyKind::OperationList(property) => {
429 let table = &self.operation_list_methods;
430 let build = template_parser::lookup_method(type_name, table, function)?;
431 build(language, diagnostics, build_ctx, property, function)
432 }
433 OperationTemplatePropertyKind::OperationId(property) => {
434 let table = &self.operation_id_methods;
435 let build = template_parser::lookup_method(type_name, table, function)?;
436 build(language, diagnostics, build_ctx, property, function)
437 }
438 }
439 }
440}
441
442pub struct OperationTemplateLanguageBuildFnTable {
444 pub core: CoreTemplateBuildFnTable<'static, OperationTemplateLanguage>,
445 pub operation: OperationTemplateBuildFnTable<'static, OperationTemplateLanguage>,
446}
447
448impl OperationTemplateLanguageBuildFnTable {
449 pub fn empty() -> Self {
450 Self {
451 core: CoreTemplateBuildFnTable::empty(),
452 operation: OperationTemplateBuildFnTable::empty(),
453 }
454 }
455
456 fn merge(&mut self, other: Self) {
457 let Self { core, operation } = other;
458
459 self.core.merge(core);
460 self.operation.merge(operation);
461 }
462
463 fn builtin() -> Self {
465 Self {
466 core: CoreTemplateBuildFnTable::builtin(),
467 operation: OperationTemplateBuildFnTable::builtin(),
468 }
469 }
470}
471
472fn builtin_operation_methods<'a, L>() -> TemplateBuildMethodFnMap<'a, L, Operation>
473where
474 L: TemplateLanguage<'a> + OperationTemplateEnvironment + ?Sized,
475 L::Property: OperationTemplatePropertyVar<'a>,
476{
477 let mut map = TemplateBuildMethodFnMap::<L, Operation>::new();
480 map.insert(
481 "current_operation",
482 |language, _diagnostics, _build_ctx, self_property, function| {
483 function.expect_no_arguments()?;
484 let current_op_id = language.current_op_id().cloned();
485 let out_property = self_property.map(move |op| Some(op.id()) == current_op_id.as_ref());
486 Ok(out_property.into_dyn_wrapped())
487 },
488 );
489 map.insert(
490 "description",
491 |_language, _diagnostics, _build_ctx, self_property, function| {
492 function.expect_no_arguments()?;
493 let out_property = self_property.map(|op| op.metadata().description.clone());
494 Ok(out_property.into_dyn_wrapped())
495 },
496 );
497 map.insert(
498 "id",
499 |_language, _diagnostics, _build_ctx, self_property, function| {
500 function.expect_no_arguments()?;
501 let out_property = self_property.map(|op| op.id().clone());
502 Ok(out_property.into_dyn_wrapped())
503 },
504 );
505 map.insert(
506 "tags",
507 |_language, _diagnostics, _build_ctx, self_property, function| {
508 function.expect_no_arguments()?;
509 let out_property = self_property.map(|op| {
510 op.metadata()
512 .tags
513 .iter()
514 .map(|(key, value)| format!("{key}: {value}"))
515 .join("\n")
516 });
517 Ok(out_property.into_dyn_wrapped())
518 },
519 );
520 map.insert(
521 "snapshot",
522 |_language, _diagnostics, _build_ctx, self_property, function| {
523 function.expect_no_arguments()?;
524 let out_property = self_property.map(|op| op.metadata().is_snapshot);
525 Ok(out_property.into_dyn_wrapped())
526 },
527 );
528 map.insert(
529 "time",
530 |_language, _diagnostics, _build_ctx, self_property, function| {
531 function.expect_no_arguments()?;
532 let out_property = self_property.map(|op| op.metadata().time.clone());
533 Ok(out_property.into_dyn_wrapped())
534 },
535 );
536 map.insert(
537 "user",
538 |_language, _diagnostics, _build_ctx, self_property, function| {
539 function.expect_no_arguments()?;
540 let out_property = self_property.map(|op| {
541 format!("{}@{}", op.metadata().username, op.metadata().hostname)
543 });
544 Ok(out_property.into_dyn_wrapped())
545 },
546 );
547 map.insert(
548 "root",
549 |language, _diagnostics, _build_ctx, self_property, function| {
550 function.expect_no_arguments()?;
551 let op_store = language.repo_loader().op_store();
552 let root_op_id = op_store.root_operation_id().clone();
553 let out_property = self_property.map(move |op| op.id() == &root_op_id);
554 Ok(out_property.into_dyn_wrapped())
555 },
556 );
557 map.insert(
558 "parents",
559 |_language, _diagnostics, _build_ctx, self_property, function| {
560 function.expect_no_arguments()?;
561 let out_property = self_property.and_then(|op| {
562 let ops: Vec<_> = op.parents().try_collect()?;
563 Ok(ops)
564 });
565 Ok(out_property.into_dyn_wrapped())
566 },
567 );
568 map
569}
570
571impl Template for OperationId {
572 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
573 write!(formatter, "{}", self.hex())
574 }
575}
576
577fn builtin_operation_id_methods<'a, L>() -> TemplateBuildMethodFnMap<'a, L, OperationId>
578where
579 L: TemplateLanguage<'a> + OperationTemplateEnvironment + ?Sized,
580 L::Property: OperationTemplatePropertyVar<'a>,
581{
582 let mut map = TemplateBuildMethodFnMap::<L, OperationId>::new();
585 map.insert(
586 "short",
587 |language, diagnostics, build_ctx, self_property, function| {
588 let ([], [len_node]) = function.expect_arguments()?;
589 let len_property = len_node
590 .map(|node| {
591 template_builder::expect_usize_expression(
592 language,
593 diagnostics,
594 build_ctx,
595 node,
596 )
597 })
598 .transpose()?;
599 let out_property = (self_property, len_property).map(|(id, len)| {
600 let mut hex = id.hex();
601 hex.truncate(len.unwrap_or(12));
602 hex
603 });
604 Ok(out_property.into_dyn_wrapped())
605 },
606 );
607 map
608}