diff --git a/cli/src/config.rs b/cli/src/config.rs index 42d06f14b..12d8b480f 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -22,6 +22,7 @@ use std::{env, fmt, slice}; use config::Source; use itertools::Itertools; use jj_lib::settings::ConfigResultExt as _; +use regex::{Captures, Regex}; use thiserror::Error; use tracing::instrument; @@ -661,12 +662,22 @@ impl CommandNameAndArgs { /// Returns process builder configured with this. pub fn to_command(&self) -> Command { + let empty: HashMap<&str, &str> = HashMap::new(); + self.to_command_with_variables(&empty) + } + + /// Returns process builder configured with this after interpolating + /// variables into the arguments. + pub fn to_command_with_variables>( + &self, + variables: &HashMap<&str, V>, + ) -> Command { let (name, args) = self.split_name_and_args(); let mut cmd = Command::new(name.as_ref()); if let CommandNameAndArgs::Structured { env, .. } = self { cmd.envs(env); } - cmd.args(args.as_ref()); + cmd.args(interpolate_variables(&args, variables)); cmd } } @@ -693,6 +704,41 @@ impl fmt::Display for CommandNameAndArgs { } } +// Not interested in $UPPER_CASE_VARIABLES +static VARIABLE_REGEX: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| Regex::new(r"\$([a-z0-9_]+)\b").unwrap()); + +pub fn interpolate_variables>( + args: &[String], + variables: &HashMap<&str, V>, +) -> Vec { + args.iter() + .map(|arg| { + VARIABLE_REGEX + .replace_all(arg, |caps: &Captures| { + let name = &caps[1]; + if let Some(subst) = variables.get(name) { + subst.as_ref().to_owned() + } else { + caps[0].to_owned() + } + }) + .into_owned() + }) + .collect() +} + +/// Return all variable names found in the args, without the dollar sign +pub fn find_all_variables(args: &[String]) -> impl Iterator { + let regex = &*VARIABLE_REGEX; + args.iter() + .flat_map(|arg| regex.find_iter(arg)) + .map(|single_match| { + let s = single_match.as_str(); + &s[1..] + }) +} + /// Wrapper to reject an array without command name. // Based on https://github.com/serde-rs/serde/issues/939 #[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize)] diff --git a/cli/src/merge_tools/external.rs b/cli/src/merge_tools/external.rs index f8b3c70fb..1425a66d5 100644 --- a/cli/src/merge_tools/external.rs +++ b/cli/src/merge_tools/external.rs @@ -12,14 +12,13 @@ use jj_lib::merge::{Merge, MergedTreeValue}; use jj_lib::merged_tree::{MergedTree, MergedTreeBuilder}; use jj_lib::repo_path::RepoPath; use pollster::FutureExt; -use regex::{Captures, Regex}; use thiserror::Error; use super::diff_working_copies::{ check_out_trees, new_utf8_temp_dir, set_readonly_recursively, DiffEditWorkingCopies, DiffSide, }; use super::{ConflictResolveError, DiffEditError, DiffGenerateError}; -use crate::config::CommandNameAndArgs; +use crate::config::{find_all_variables, interpolate_variables, CommandNameAndArgs}; use crate::ui::Ui; /// Merge/diff tool loaded from the settings. @@ -220,41 +219,6 @@ pub fn run_mergetool_external( Ok(new_tree) } -// Not interested in $UPPER_CASE_VARIABLES -static VARIABLE_REGEX: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(|| Regex::new(r"\$([a-z0-9_]+)\b").unwrap()); - -fn interpolate_variables>( - args: &[String], - variables: &HashMap<&str, V>, -) -> Vec { - args.iter() - .map(|arg| { - VARIABLE_REGEX - .replace_all(arg, |caps: &Captures| { - let name = &caps[1]; - if let Some(subst) = variables.get(name) { - subst.as_ref().to_owned() - } else { - caps[0].to_owned() - } - }) - .into_owned() - }) - .collect() -} - -/// Return all variable names found in the args, without the dollar sign -fn find_all_variables(args: &[String]) -> impl Iterator { - let regex = &*VARIABLE_REGEX; - args.iter() - .flat_map(|arg| regex.find_iter(arg)) - .map(|single_match| { - let s = single_match.as_str(); - &s[1..] - }) -} - pub fn edit_diff_external( editor: &ExternalMergeTool, left_tree: &MergedTree,