ok/jj
1
0
Fork 0
forked from mirrors/jj

templater: add general-purpose template engine for value types

This serves the role of the formatter in Mercurial, but the provided features
are rather restricted compared to mercurial.formatter. That's because both
implementation language "Rust" and jj's template language are statically typed.

The current implementation works well for simple commands like "config list -T",
but it might be not okay for "branch list -T". If we implement branch templating
by using the generic mechanism, the commit summary part would have to be
evaluated as a separate template:

  -T 'branch_name ++ target_commit_summary' (target_commit_summary: Template)

instead of

  -T 'branch_name ++ commit_summary(target_commit)' (target_commit: Commit)

where the branch template language is a superset of the commit template
language.
This commit is contained in:
Yuya Nishihara 2024-03-03 18:50:28 +09:00
parent fb5fe5cc26
commit d0168199f0
2 changed files with 164 additions and 0 deletions

View file

@ -0,0 +1,163 @@
// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use crate::template_builder::{
self, BuildContext, CoreTemplateBuildFnTable, CoreTemplatePropertyKind, IntoTemplateProperty,
TemplateLanguage,
};
use crate::template_parser::{self, FunctionCallNode, TemplateParseResult};
use crate::templater::{Template, TemplateProperty};
/// General-purpose template language for basic value types.
///
/// This template language only supports the core template property types (plus
/// the context type `C`.) The context type `C` is usually a tuple or struct of
/// value types. Keyword functions need to be registered to extract properties
/// from the context object.
pub struct GenericTemplateLanguage<'a, C> {
build_fn_table: GenericTemplateBuildFnTable<'a, C>,
}
impl<'a, C> GenericTemplateLanguage<'a, C> {
/// Sets up environment with no keywords.
///
/// New keyword functions can be registered by `add_keyword()`.
// It's not "Default" in a way that the core methods table is NOT empty.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self::with_keywords(HashMap::new())
}
/// Sets up environment with the given `keywords` table.
pub fn with_keywords(keywords: GenericTemplateBuildKeywordFnMap<'a, C>) -> Self {
GenericTemplateLanguage {
build_fn_table: GenericTemplateBuildFnTable {
core: CoreTemplateBuildFnTable::builtin(),
keywords,
},
}
}
/// Registers new function that translates keyword to property.
///
/// A keyword function returns `Self::Property`, which is basically a
/// closure tagged by its return type. The inner closure is usually wrapped
/// by `TemplatePropertyFn`.
///
/// ```ignore
/// language.add_keyword("name", |language| {
/// let property = TemplatePropertyFn(|v: &C| Ok(v.to_string()));
/// Ok(language.wrap_string(property))
/// });
/// ```
pub fn add_keyword<F>(&mut self, name: &'static str, build: F)
where
F: Fn(&Self) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>> + 'a,
{
self.build_fn_table.keywords.insert(name, Box::new(build));
}
}
impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
type Context = C;
type Property = GenericTemplatePropertyKind<'a, C>;
template_builder::impl_core_wrap_property_fns!('a, GenericTemplatePropertyKind::Core);
fn build_self(&self) -> Self::Property {
// No need to clone the context object because there are no other
// objects of "Self" type, and the context is available everywhere.
GenericTemplatePropertyKind::Self_
}
fn build_method(
&self,
build_ctx: &BuildContext<Self::Property>,
property: Self::Property,
function: &FunctionCallNode,
) -> TemplateParseResult<Self::Property> {
match property {
GenericTemplatePropertyKind::Core(property) => {
let table = &self.build_fn_table.core;
table.build_method(self, build_ctx, property, function)
}
GenericTemplatePropertyKind::Self_ => {
let table = &self.build_fn_table.keywords;
let build = template_parser::lookup_method("Self", table, function)?;
// For simplicity, only 0-ary method is supported.
template_parser::expect_no_arguments(function)?;
build(self)
}
}
}
}
pub enum GenericTemplatePropertyKind<'a, C> {
Core(CoreTemplatePropertyKind<'a, C>),
Self_,
}
impl<'a, C: 'a> IntoTemplateProperty<'a, C> for GenericTemplatePropertyKind<'a, C> {
fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<C, Output = bool> + 'a>> {
match self {
GenericTemplatePropertyKind::Core(property) => property.try_into_boolean(),
GenericTemplatePropertyKind::Self_ => None,
}
}
fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<C, Output = i64> + 'a>> {
match self {
GenericTemplatePropertyKind::Core(property) => property.try_into_integer(),
GenericTemplatePropertyKind::Self_ => None,
}
}
fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<C, Output = String> + 'a>> {
match self {
GenericTemplatePropertyKind::Core(property) => property.try_into_plain_text(),
GenericTemplatePropertyKind::Self_ => None,
}
}
fn try_into_template(self) -> Option<Box<dyn Template<C> + 'a>> {
match self {
GenericTemplatePropertyKind::Core(property) => property.try_into_template(),
GenericTemplatePropertyKind::Self_ => None,
}
}
}
/// Function that translates keyword (or 0-ary method call node of the context
/// type `C`.)
///
/// Because the `GenericTemplateLanguage` doesn't provide a way to pass around
/// global resources, the keyword function is allowed to capture resources.
pub type GenericTemplateBuildKeywordFn<'a, C> = Box<
dyn Fn(
&GenericTemplateLanguage<'a, C>,
) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
+ 'a,
>;
/// Table of functions that translate keyword node.
pub type GenericTemplateBuildKeywordFnMap<'a, C> =
HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>;
/// Symbol table of methods available in the general-purpose template.
struct GenericTemplateBuildFnTable<'a, C: 'a> {
core: CoreTemplateBuildFnTable<'a, GenericTemplateLanguage<'a, C>>,
keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
}

View file

@ -23,6 +23,7 @@ pub mod config;
pub mod description_util;
pub mod diff_util;
pub mod formatter;
pub mod generic_templater;
pub mod git_util;
pub mod graphlog;
pub mod merge_tools;