From 7ca674443270ae2d6a7a4f16e00c7d20bbf8e17a Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 3 Mar 2024 20:45:36 +0900 Subject: [PATCH] cli: add "config list --template" support There's a caveat: "jj config list -Tname" will concatenate all names in a single line. That's correct but useless. We might want some option or config knob to complete missing "\n". This also applies to "log --no-graph". --- CHANGELOG.md | 2 + cli/src/commands/config.rs | 74 ++++++++++++++++++++++++-------- cli/src/config/templates.toml | 9 ++++ cli/tests/cli-reference@.md.snap | 1 + cli/tests/test_config_command.rs | 9 ++++ 5 files changed, 77 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ab8f6b4f..fe6699841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `jj show` now accepts `-T`/`--template` option to render its output using template +* `jj config list` now accepts `-T`/`--template` option. + * `jj git fetch` now accepts `-b` as a shorthand for `--branch`, making it more consistent with other commands that accept a branch diff --git a/cli/src/commands/config.rs b/cli/src/commands/config.rs index e39f27e21..639c87eb3 100644 --- a/cli/src/commands/config.rs +++ b/cli/src/commands/config.rs @@ -24,6 +24,9 @@ use crate::cli_util::{ }; use crate::command_error::{user_error, CommandError}; use crate::config::{AnnotatedValue, ConfigSource}; +use crate::generic_templater::GenericTemplateLanguage; +use crate::template_builder::TemplateLanguage as _; +use crate::templater::TemplatePropertyFn; use crate::ui::Ui; #[derive(clap::Args, Clone, Debug)] @@ -92,6 +95,17 @@ pub(crate) struct ConfigListArgs { #[arg(long)] repo: bool, // TODO(#1047): Support --show-origin using LayeredConfigs. + /// Render each variable using the given template + /// + /// The following keywords are defined: + /// + /// * `name: String`: Config name. + /// * `value: String`: Serialized value in TOML syntax. + /// * `overridden: Boolean`: True if the value is shadowed by other. + /// + /// For the syntax, see https://github.com/martinvonz/jj/blob/main/docs/templates.md + #[arg(long, short = 'T', verbatim_doc_comment)] + template: Option, } impl ConfigListArgs { @@ -170,50 +184,74 @@ pub(crate) fn cmd_config( } } +fn config_template_language() -> GenericTemplateLanguage<'static, AnnotatedValue> { + fn prop_fn R>(f: F) -> TemplatePropertyFn { + TemplatePropertyFn(f) + } + let mut language = GenericTemplateLanguage::new(); + // "name" instead of "path" to avoid confusion with the source file path + language.add_keyword("name", |language| { + let property = prop_fn(|annotated| Ok(annotated.path.join("."))); + Ok(language.wrap_string(property)) + }); + language.add_keyword("value", |language| { + // TODO: would be nice if we can provide raw dynamically-typed value + let property = prop_fn(|annotated| Ok(serialize_config_value(&annotated.value))); + Ok(language.wrap_string(property)) + }); + language.add_keyword("overridden", |language| { + let property = prop_fn(|annotated| Ok(annotated.is_overridden)); + Ok(language.wrap_boolean(property)) + }); + language +} + #[instrument(skip_all)] pub(crate) fn cmd_config_list( ui: &mut Ui, command: &CommandHelper, args: &ConfigListArgs, ) -> Result<(), CommandError> { + let template = { + let language = config_template_language(); + let text = match &args.template { + Some(value) => value.to_owned(), + None => command + .settings() + .config() + .get_string("templates.config_list")?, + }; + command.parse_template(ui, &language, &text)? + }; + ui.request_pager(); + let mut formatter = ui.stdout_formatter(); let name_path = args .name .as_ref() .map_or(vec![], |name| name.split('.').collect_vec()); - let values = command.resolved_config_values(&name_path)?; let mut wrote_values = false; - for AnnotatedValue { - path, - value, - source, - is_overridden, - } in &values - { + for annotated in command.resolved_config_values(&name_path)? { // Remove overridden values. - if *is_overridden && !args.include_overridden { + if annotated.is_overridden && !args.include_overridden { continue; } if let Some(target_source) = args.get_source_kind() { - if target_source != *source { + if target_source != annotated.source { continue; } } // Skip built-ins if not included. - if !args.include_defaults && *source == ConfigSource::Default { + if !args.include_defaults && annotated.source == ConfigSource::Default { continue; } - writeln!( - ui.stdout(), - "{}{}={}", - if *is_overridden { "# " } else { "" }, - path.join("."), - serialize_config_value(value) - )?; + + template.format(&annotated, formatter.as_mut())?; wrote_values = true; } + drop(formatter); if !wrote_values { // Note to stderr explaining why output is empty. if let Some(name) = &args.name { diff --git a/cli/src/config/templates.toml b/cli/src/config/templates.toml index a2b66c6e8..2ad29d193 100644 --- a/cli/src/config/templates.toml +++ b/cli/src/config/templates.toml @@ -24,6 +24,15 @@ separate(" ", ) ''' +config_list = ''' +concat( + if(overridden, "# "), + name, + "=", + value, +) ++ "\n" +''' + log = 'builtin_log_compact' op_log = 'builtin_op_log_compact' show = 'builtin_log_detailed' diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index 5eea230a6..b825dc6e0 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -489,6 +489,7 @@ List variables set in config file, along with their values Possible values: `true`, `false` +* `-T`, `--template