From baced8b1f6dea2c9811c6496402643bfaced29fa Mon Sep 17 00:00:00 2001 From: dploch Date: Thu, 29 Feb 2024 15:44:49 -0500 Subject: [PATCH] commit_templater: support extensions of the template language --- cli/examples/custom-commit-templater/main.rs | 87 ++++++++++++++++++++ cli/src/cli_util.rs | 23 ++++++ cli/src/commit_templater.rs | 40 ++++++++- 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 cli/examples/custom-commit-templater/main.rs diff --git a/cli/examples/custom-commit-templater/main.rs b/cli/examples/custom-commit-templater/main.rs new file mode 100644 index 0000000000..edb78fd843 --- /dev/null +++ b/cli/examples/custom-commit-templater/main.rs @@ -0,0 +1,87 @@ +// 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 jj_cli::cli_util::CliRunner; +use jj_cli::commit_templater::{CommitTemplateBuildFnTable, CommitTemplateLanguageExtension}; +use jj_cli::template_builder::TemplateLanguage; +use jj_cli::template_parser::{self, TemplateParseError}; +use jj_cli::templater::{TemplateFunction, TemplatePropertyError}; +use jj_lib::commit::Commit; +use jj_lib::object_id::ObjectId; + +struct HexCounter; + +fn num_digits_in_id(commit: Commit) -> Result { + let mut count = 0; + for ch in commit.id().hex().chars() { + if ch.is_ascii_digit() { + count += 1; + } + } + Ok(count) +} + +fn num_char_in_id(commit: Commit, ch_match: char) -> Result { + let mut count = 0; + for ch in commit.id().hex().chars() { + if ch == ch_match { + count += 1; + } + } + Ok(count) +} + +impl CommitTemplateLanguageExtension for HexCounter { + fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo> { + let mut table = CommitTemplateBuildFnTable::empty(); + table.commit_methods.insert( + "num_digits_in_id", + |language, _build_context, property, call| { + template_parser::expect_no_arguments(call)?; + Ok(language.wrap_integer(TemplateFunction::new(property, num_digits_in_id))) + }, + ); + table.commit_methods.insert( + "num_char_in_id", + |language, _build_context, property, call| { + let [string_arg] = template_parser::expect_exact_arguments(call)?; + let char_arg = + template_parser::expect_string_literal_with(string_arg, |string, span| { + let chars: Vec<_> = string.chars().collect(); + match chars[..] { + [ch] => Ok(ch), + _ => Err(TemplateParseError::unexpected_expression( + "Expected singular character argument", + span, + )), + } + })?; + + Ok( + language.wrap_integer(TemplateFunction::new(property, move |commit| { + num_char_in_id(commit, char_arg) + })), + ) + }, + ); + + table + } +} + +fn main() -> std::process::ExitCode { + CliRunner::init() + .set_commit_template_extension(Box::new(HexCounter)) + .run() +} diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 3b3dac4725..1acc9295ec 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -78,6 +78,7 @@ use tracing::instrument; use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; +use crate::commit_templater::CommitTemplateLanguageExtension; use crate::config::{ new_config_path, AnnotatedValue, CommandNameAndArgs, ConfigSource, LayeredConfigs, }; @@ -590,6 +591,7 @@ pub struct CommandHelper { global_args: GlobalArgs, settings: UserSettings, layered_configs: LayeredConfigs, + commit_template_extension: Option>, maybe_workspace_loader: Result, store_factories: StoreFactories, working_copy_factories: HashMap>, @@ -605,6 +607,7 @@ impl CommandHelper { global_args: GlobalArgs, settings: UserSettings, layered_configs: LayeredConfigs, + commit_template_extension: Option>, maybe_workspace_loader: Result, store_factories: StoreFactories, working_copy_factories: HashMap>, @@ -621,6 +624,7 @@ impl CommandHelper { global_args, settings, layered_configs, + commit_template_extension, maybe_workspace_loader, store_factories, working_copy_factories, @@ -799,6 +803,7 @@ pub struct WorkspaceCommandHelper { settings: UserSettings, workspace: Workspace, user_repo: ReadonlyUserRepo, + commit_template_extension: Option>, revset_aliases_map: RevsetAliasesMap, template_aliases_map: TemplateAliasesMap, may_update_working_copy: bool, @@ -823,6 +828,7 @@ impl WorkspaceCommandHelper { repo.as_ref(), workspace.workspace_id(), &id_prefix_context, + command.commit_template_extension.as_deref(), &template_aliases_map, &command.settings, )?; @@ -836,6 +842,7 @@ impl WorkspaceCommandHelper { settings: command.settings.clone(), workspace, user_repo: ReadonlyUserRepo::new(repo), + commit_template_extension: command.commit_template_extension.clone(), revset_aliases_map, template_aliases_map, may_update_working_copy, @@ -1263,6 +1270,7 @@ Set which revision the branch points to with `jj branch set {branch_name} -r { self.tx.repo(), self.helper.workspace_id(), &id_prefix_context, + self.helper.commit_template_extension.as_deref(), &self.helper.template_aliases_map, &self.helper.settings, ) @@ -2152,6 +2162,7 @@ fn parse_commit_summary_template<'a>( repo: &'a dyn Repo, workspace_id: &WorkspaceId, id_prefix_context: &'a IdPrefixContext, + extension: Option<&dyn CommitTemplateLanguageExtension>, aliases_map: &TemplateAliasesMap, settings: &UserSettings, ) -> Result + 'a>, CommandError> { @@ -2160,6 +2171,7 @@ fn parse_commit_summary_template<'a>( repo, workspace_id, id_prefix_context, + extension, &template_text, aliases_map, )?) @@ -2841,6 +2853,7 @@ pub struct CliRunner { extra_configs: Option, store_factories: Option, working_copy_factories: Option>>, + commit_template_extension: Option>, dispatch_fn: CliDispatchFn, start_hook_fns: Vec, process_global_args_fns: Vec, @@ -2862,6 +2875,7 @@ impl CliRunner { extra_configs: None, store_factories: None, working_copy_factories: None, + commit_template_extension: None, dispatch_fn: Box::new(crate::commands::run_command), start_hook_fns: vec![], process_global_args_fns: vec![], @@ -2895,6 +2909,14 @@ impl CliRunner { self } + pub fn set_commit_template_extension( + mut self, + commit_template_extension: Box, + ) -> Self { + self.commit_template_extension = Some(commit_template_extension.into()); + self + } + pub fn add_start_hook(mut self, start_hook_fn: CliDispatchFn) -> Self { self.start_hook_fns.push(start_hook_fn); self @@ -3009,6 +3031,7 @@ impl CliRunner { args.global_args, settings, layered_configs, + self.commit_template_extension, maybe_workspace_loader, self.store_factories.unwrap_or_default(), working_copy_factories, diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index ed49762c66..5fa704fb52 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -30,8 +30,8 @@ use once_cell::unsync::OnceCell; use crate::formatter::Formatter; use crate::template_builder::{ - self, BuildContext, CoreTemplateBuildFnTable, CoreTemplatePropertyKind, IntoTemplateProperty, - TemplateBuildMethodFnMap, TemplateLanguage, + self, merge_fn_map, BuildContext, CoreTemplateBuildFnTable, CoreTemplatePropertyKind, + IntoTemplateProperty, TemplateBuildMethodFnMap, TemplateLanguage, }; use crate::template_parser::{self, FunctionCallNode, TemplateAliasesMap, TemplateParseResult}; use crate::templater::{ @@ -48,6 +48,10 @@ pub struct CommitTemplateLanguage<'repo> { keyword_cache: CommitKeywordCache, } +pub trait CommitTemplateLanguageExtension { + fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>; +} + impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Context = Commit; type Property = CommitTemplatePropertyKind<'repo>; @@ -228,7 +232,7 @@ impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<' } /// Table of functions that translate method call node of self type `T`. -type CommitTemplateBuildMethodFnMap<'repo, T> = +pub type CommitTemplateBuildMethodFnMap<'repo, T> = TemplateBuildMethodFnMap<'repo, CommitTemplateLanguage<'repo>, T>; /// Symbol table of methods available in the commit template. @@ -261,6 +265,28 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> { shortest_id_prefix_methods: HashMap::new(), } } + + fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) { + let CommitTemplateBuildFnTable { + core, + commit_methods, + ref_name_methods, + commit_or_change_id_methods, + shortest_id_prefix_methods, + } = extension; + + self.core.merge(core); + merge_fn_map(&mut self.commit_methods, commit_methods); + merge_fn_map(&mut self.ref_name_methods, ref_name_methods); + merge_fn_map( + &mut self.commit_or_change_id_methods, + commit_or_change_id_methods, + ); + merge_fn_map( + &mut self.shortest_id_prefix_methods, + shortest_id_prefix_methods, + ); + } } #[derive(Debug, Default)] @@ -805,14 +831,20 @@ pub fn parse<'repo>( repo: &'repo dyn Repo, workspace_id: &WorkspaceId, id_prefix_context: &'repo IdPrefixContext, + extension: Option<&dyn CommitTemplateLanguageExtension>, template_text: &str, aliases_map: &TemplateAliasesMap, ) -> TemplateParseResult + 'repo>> { + let mut build_fn_table = CommitTemplateBuildFnTable::builtin(); + if let Some(extension) = extension { + build_fn_table.merge(extension.build_fn_table()); + } + let language = CommitTemplateLanguage { repo, workspace_id: workspace_id.clone(), id_prefix_context, - build_fn_table: CommitTemplateBuildFnTable::builtin(), + build_fn_table, keyword_cache: CommitKeywordCache::default(), }; let node = template_parser::parse(template_text, aliases_map)?;