Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
templater: provide environment variables accessing in template languages
to reuse environment hashmap passed from `ConfigEnv::environment`
instead of `std::env::var`.

Signed-off-by: Chawye Hsu <su+git@chawyehsu.com>
  • Loading branch information
chawyehsu committed Mar 14, 2026
commit 45bbcbd3def9da5fc09305d5f0556b4e12f9815e
6 changes: 6 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,7 @@ impl WorkspaceCommandEnvironment {
repo,
&self.path_converter,
&self.workspace_name,
self.environment(),
self.revset_parse_context(),
id_prefix_context,
self.immutable_expression(),
Expand All @@ -1061,6 +1062,10 @@ impl WorkspaceCommandEnvironment {
pub fn operation_template_extensions(&self) -> &[Arc<dyn OperationTemplateLanguageExtension>] {
&self.command.data.operation_template_extensions
}

pub(crate) fn environment(&self) -> &HashMap<String, String> {
self.command.config_env().environment()
}
}

/// A token that holds a lock for git import/export operations in colocated
Expand Down Expand Up @@ -1835,6 +1840,7 @@ to the current parents may contain changes from multiple commits.
OperationTemplateLanguage::new(
self.workspace.repo_loader(),
Some(self.repo().op_id()),
self.env.environment(),
self.env.operation_template_extensions(),
)
}
Expand Down
8 changes: 4 additions & 4 deletions cli/src/commands/config/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use clap_complete::ArgValueCandidates;
use jj_lib::config::ConfigNamePathBuf;
use jj_lib::config::ConfigSource;
use jj_lib::settings::UserSettings;
use tracing::instrument;

use super::ConfigLevelArgs;
Expand Down Expand Up @@ -81,7 +80,7 @@ pub async fn cmd_config_list(
args: &ConfigListArgs,
) -> Result<(), CommandError> {
let template: TemplateRenderer<AnnotatedValue> = {
let language = config_template_language(command.settings());
let language = config_template_language(command);
let text = match &args.template {
Some(value) => value.to_owned(),
None => command.settings().get_string("templates.config_list")?,
Expand Down Expand Up @@ -128,8 +127,9 @@ generic_templater::impl_self_property_wrapper!(AnnotatedValue);

// AnnotatedValue will be cloned internally in the templater. If the cloning
// cost matters, wrap it with Rc.
fn config_template_language(settings: &UserSettings) -> ConfigTemplateLanguage {
let mut language = ConfigTemplateLanguage::new(settings);
fn config_template_language(command: &CommandHelper) -> ConfigTemplateLanguage {
let mut language =
ConfigTemplateLanguage::new(command.config_env().environment(), command.settings());
language.add_keyword("name", |self_property| {
let out_property = self_property.map(|annotated| annotated.name.to_string());
Ok(out_property.into_dyn_wrapped())
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/operation/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ async fn do_op_log(
let language = OperationTemplateLanguage::new(
repo_loader,
Some(current_op.id()),
workspace_env.environment(),
workspace_env.operation_template_extensions(),
);
let text = match &args.template {
Expand Down
10 changes: 10 additions & 0 deletions cli/src/commit_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub struct CommitTemplateLanguage<'repo> {
repo: &'repo dyn Repo,
path_converter: &'repo RepoPathUiConverter,
workspace_name: WorkspaceNameBuf,
environment: &'repo HashMap<String, String>,
// RevsetParseContext doesn't borrow a repo, but we'll need 'repo lifetime
// anyway to capture it to evaluate dynamically-constructed user expression
// such as `revset("ancestors(" ++ commit_id ++ ")")`.
Expand All @@ -159,6 +160,7 @@ impl<'repo> CommitTemplateLanguage<'repo> {
repo: &'repo dyn Repo,
path_converter: &'repo RepoPathUiConverter,
workspace_name: &WorkspaceName,
environment: &'repo HashMap<String, String>,
revset_parse_context: RevsetParseContext<'repo>,
id_prefix_context: &'repo IdPrefixContext,
immutable_expression: Arc<UserRevsetExpression>,
Expand All @@ -179,6 +181,7 @@ impl<'repo> CommitTemplateLanguage<'repo> {
repo,
path_converter,
workspace_name: workspace_name.to_owned(),
environment,
revset_parse_context,
id_prefix_context,
immutable_expression,
Expand All @@ -200,6 +203,10 @@ impl<'repo> CommitTemplateLanguage<'repo> {
impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
type Property = CommitTemplatePropertyKind<'repo>;

fn environment(&self) -> &HashMap<String, String> {
self.environment
}

fn settings(&self) -> &UserSettings {
self.repo.base_repo().settings()
}
Expand Down Expand Up @@ -3030,6 +3037,7 @@ mod tests {
revset_aliases_map: RevsetAliasesMap,
template_aliases_map: TemplateAliasesMap,
immutable_expression: Arc<UserRevsetExpression>,
environment: HashMap<String, String>,
extra_functions: HashMap<&'static str, BuildFunctionFn>,
}

Expand All @@ -3054,6 +3062,7 @@ mod tests {
revset_aliases_map: RevsetAliasesMap::new(),
template_aliases_map: TemplateAliasesMap::new(),
immutable_expression: RevsetExpression::none(),
environment: HashMap::new(),
extra_functions: HashMap::new(),
}
}
Expand Down Expand Up @@ -3088,6 +3097,7 @@ mod tests {
self.test_workspace.repo.as_ref(),
&self.path_converter,
self.test_workspace.workspace.workspace_name(),
&self.environment,
revset_parse_context,
&self.id_prefix_context,
self.immutable_expression.clone(),
Expand Down
4 changes: 4 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ impl ConfigEnv {
self.command = Some(command);
}

pub fn environment(&self) -> &HashMap<String, String> {
&self.environment
}

fn load_secure_config(
&self,
ui: &Ui,
Expand Down
11 changes: 9 additions & 2 deletions cli/src/generic_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use crate::templater::TemplatePropertyExt as _;
/// registered to extract properties from the self object.
pub struct GenericTemplateLanguage<'a, C> {
settings: UserSettings,
environment: HashMap<String, String>,
build_fn_table: GenericTemplateBuildFnTable<'a, C>,
}

Expand All @@ -51,18 +52,20 @@ where
/// Sets up environment with no keywords.
///
/// New keyword functions can be registered by `add_keyword()`.
pub fn new(settings: &UserSettings) -> Self {
Self::with_keywords(HashMap::new(), settings)
pub fn new(environment: &HashMap<String, String>, settings: &UserSettings) -> Self {
Self::with_keywords(HashMap::new(), environment, settings)
}

/// Sets up environment with the given `keywords` table.
pub fn with_keywords(
keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
environment: &HashMap<String, String>,
settings: &UserSettings,
) -> Self {
Self {
// Clone settings to keep lifetime simple. It's cheap.
settings: settings.clone(),
environment: environment.clone(),
build_fn_table: GenericTemplateBuildFnTable {
core: CoreTemplateBuildFnTable::builtin(),
keywords,
Expand Down Expand Up @@ -99,6 +102,10 @@ where
{
type Property = GenericTemplatePropertyKind<'a, C>;

fn environment(&self) -> &HashMap<String, String> {
&self.environment
}

fn settings(&self) -> &UserSettings {
&self.settings
}
Expand Down
7 changes: 7 additions & 0 deletions cli/src/operation_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub trait OperationTemplateEnvironment {
pub struct OperationTemplateLanguage {
repo_loader: RepoLoader,
current_op_id: Option<OperationId>,
environment: HashMap<String, String>,
build_fn_table: OperationTemplateLanguageBuildFnTable,
cache_extensions: ExtensionsMap,
}
Expand All @@ -75,6 +76,7 @@ impl OperationTemplateLanguage {
pub fn new(
repo_loader: &RepoLoader,
current_op_id: Option<&OperationId>,
environment: &HashMap<String, String>,
extensions: &[impl AsRef<dyn OperationTemplateLanguageExtension>],
) -> Self {
let mut build_fn_table = OperationTemplateLanguageBuildFnTable::builtin();
Expand All @@ -91,6 +93,7 @@ impl OperationTemplateLanguage {
// Clone these to keep lifetime simple
repo_loader: repo_loader.clone(),
current_op_id: current_op_id.cloned(),
environment: environment.clone(),
build_fn_table,
cache_extensions,
}
Expand All @@ -100,6 +103,10 @@ impl OperationTemplateLanguage {
impl TemplateLanguage<'static> for OperationTemplateLanguage {
type Property = OperationTemplateLanguagePropertyKind;

fn environment(&self) -> &HashMap<String, String> {
&self.environment
}

fn settings(&self) -> &UserSettings {
self.repo_loader.settings()
}
Expand Down
51 changes: 43 additions & 8 deletions cli/src/template_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ use crate::time_util;
pub trait TemplateLanguage<'a> {
type Property: CoreTemplatePropertyVar<'a> + 'a;

fn environment(&self) -> &HashMap<String, String>;

fn settings(&self) -> &UserSettings;

/// Translates the given global `function` call to a property.
Expand Down Expand Up @@ -2139,13 +2141,16 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
// .decorated("", "") to trim leading/trailing whitespace
Ok(Literal(value.map(|v| v.decorated("", ""))).into_dyn_wrapped())
});
map.insert("env", |_language, diagnostics, _build_ctx, function| {
let [var_name_node] = function.expect_exact_arguments()?;
let var_name =
template_parser::catch_aliases(diagnostics, var_name_node, |_diagnostics, node| {
template_parser::expect_string_literal(node)
})?;
let out_property = std::env::var(var_name).unwrap_or_default();
map.insert("env", |language, diagnostics, _build_ctx, function| {
let [name_node] = function.expect_exact_arguments()?;
let name = template_parser::catch_aliases(diagnostics, name_node, |_diagnostics, node| {
template_parser::expect_string_literal(node)
})?;
let out_property = language
.environment()
.get(name)
.cloned()
.unwrap_or_default();
Ok(Literal(out_property).into_dyn_wrapped())
});
map
Expand Down Expand Up @@ -2512,6 +2517,8 @@ mod tests {
/// Helper to set up template evaluation environment.
struct TestTemplateEnv {
language: TestTemplateLanguage,
#[allow(unused)]
environment: HashMap<String, String>,
aliases_map: TemplateAliasesMap,
color_rules: Vec<(Vec<String>, formatter::Style)>,
}
Expand All @@ -2522,9 +2529,17 @@ mod tests {
}

fn with_config(config: StackedConfig) -> Self {
Self::with_config_and_environment(config, HashMap::new())
}

fn with_config_and_environment(
config: StackedConfig,
environment: HashMap<String, String>,
) -> Self {
let settings = UserSettings::from_config(config).unwrap();
Self {
language: TestTemplateLanguage::new(&settings),
language: TestTemplateLanguage::new(&environment, &settings),
environment,
aliases_map: TemplateAliasesMap::new(),
color_rules: Vec::new(),
}
Expand Down Expand Up @@ -4841,6 +4856,26 @@ mod tests {
");
}

#[test]
fn test_env_function() {
let environment = HashMap::from([
("JJ_TEMPLATE_ENV".to_owned(), "template-value".to_owned()),
("JJ_EMPTY_ENV".to_owned(), "".to_owned()),
]);
let env = TestTemplateEnv::with_config_and_environment(
StackedConfig::with_defaults(),
environment,
);

insta::assert_snapshot!(env.render_ok(r#"env("JJ_TEMPLATE_ENV")"#), @"template-value");
insta::assert_snapshot!(env.render_ok(r#"env("JJ_EMPTY_ENV")"#), @"");
insta::assert_snapshot!(env.render_ok(r#"env("JJ_MISSING_ENV")"#), @"");
insta::assert_snapshot!(
env.render_ok(r#"if(env("JJ_EMPTY_ENV"), "yes", "no")"#),
@"no"
);
}

#[test]
fn test_any_type() {
let mut env = TestTemplateEnv::new();
Expand Down
Loading