jj_cli/commands/config/
list.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use clap_complete::ArgValueCandidates;
16use jj_lib::config::ConfigNamePathBuf;
17use jj_lib::config::ConfigSource;
18use jj_lib::settings::UserSettings;
19use tracing::instrument;
20
21use super::ConfigLevelArgs;
22use crate::cli_util::CommandHelper;
23use crate::command_error::CommandError;
24use crate::complete;
25use crate::config::AnnotatedValue;
26use crate::config::resolved_config_values;
27use crate::generic_templater;
28use crate::generic_templater::GenericTemplateLanguage;
29use crate::templater::TemplatePropertyExt as _;
30use crate::templater::TemplateRenderer;
31use crate::ui::Ui;
32
33/// List variables set in config files, along with their values.
34#[derive(clap::Args, Clone, Debug)]
35#[command(mut_group("config_level", |g| g.required(false)))]
36pub struct ConfigListArgs {
37    /// An optional name of a specific config option to look up.
38    #[arg(add = ArgValueCandidates::new(complete::config_keys))]
39    pub name: Option<ConfigNamePathBuf>,
40    /// Whether to explicitly include built-in default values in the list.
41    #[arg(long, conflicts_with = "config_level")]
42    pub include_defaults: bool,
43    /// Allow printing overridden values.
44    #[arg(long)]
45    pub include_overridden: bool,
46    #[command(flatten)]
47    pub level: ConfigLevelArgs,
48    /// Render each variable using the given template
49    ///
50    /// The following keywords are available in the template expression:
51    ///
52    /// * `name: String`: Config name.
53    /// * `value: ConfigValue`: Value to be formatted in TOML syntax.
54    /// * `overridden: Boolean`: True if the value is shadowed by other.
55    /// * `source: String`: Source of the value.
56    /// * `path: String`: Path to the config file.
57    ///
58    /// Can be overridden by the `templates.config_list` setting. To
59    /// see a detailed config list, use the `builtin_config_list_detailed`
60    /// template.
61    ///
62    /// See [`jj help -k templates`] for more information.
63    ///
64    /// [`jj help -k templates`]:
65    ///     https://jj-vcs.github.io/jj/latest/templates/
66    #[arg(
67        long, short = 'T',
68        verbatim_doc_comment,
69        add = ArgValueCandidates::new(complete::template_aliases)
70    )]
71    template: Option<String>,
72}
73
74#[instrument(skip_all)]
75pub fn cmd_config_list(
76    ui: &mut Ui,
77    command: &CommandHelper,
78    args: &ConfigListArgs,
79) -> Result<(), CommandError> {
80    let template: TemplateRenderer<AnnotatedValue> = {
81        let language = config_template_language(command.settings());
82        let text = match &args.template {
83            Some(value) => value.to_owned(),
84            None => command.settings().get_string("templates.config_list")?,
85        };
86        command
87            .parse_template(ui, &language, &text)?
88            .labeled(["config_list"])
89    };
90
91    let name_path = args.name.clone().unwrap_or_else(ConfigNamePathBuf::root);
92    let mut annotated_values = resolved_config_values(command.settings().config(), &name_path);
93    // The default layer could be excluded beforehand as layers[len..], but we
94    // can't do the same for "annotated.source == target_source" in order for
95    // resolved_config_values() to mark values overridden by the upper layers.
96    if let Some(target_source) = args.level.get_source_kind() {
97        annotated_values.retain(|annotated| annotated.source == target_source);
98    } else if !args.include_defaults {
99        annotated_values.retain(|annotated| annotated.source != ConfigSource::Default);
100    }
101    if !args.include_overridden {
102        annotated_values.retain(|annotated| !annotated.is_overridden);
103    }
104
105    if !annotated_values.is_empty() {
106        ui.request_pager();
107        let mut formatter = ui.stdout_formatter();
108        for annotated in &annotated_values {
109            template.format(annotated, formatter.as_mut())?;
110        }
111    } else {
112        // Note to stderr explaining why output is empty.
113        if let Some(name) = &args.name {
114            writeln!(ui.warning_default(), "No matching config key for {name}")?;
115        } else {
116            writeln!(ui.warning_default(), "No config to list")?;
117        }
118    }
119    Ok(())
120}
121
122type ConfigTemplateLanguage = GenericTemplateLanguage<'static, AnnotatedValue>;
123
124generic_templater::impl_self_property_wrapper!(AnnotatedValue);
125
126// AnnotatedValue will be cloned internally in the templater. If the cloning
127// cost matters, wrap it with Rc.
128fn config_template_language(settings: &UserSettings) -> ConfigTemplateLanguage {
129    let mut language = ConfigTemplateLanguage::new(settings);
130    language.add_keyword("name", |self_property| {
131        let out_property = self_property.map(|annotated| annotated.name.to_string());
132        Ok(out_property.into_dyn_wrapped())
133    });
134    language.add_keyword("value", |self_property| {
135        // .decorated("", "") to trim leading/trailing whitespace
136        let out_property = self_property.map(|annotated| annotated.value.decorated("", ""));
137        Ok(out_property.into_dyn_wrapped())
138    });
139    language.add_keyword("source", |self_property| {
140        let out_property = self_property.map(|annotated| annotated.source.to_string());
141        Ok(out_property.into_dyn_wrapped())
142    });
143    language.add_keyword("path", |self_property| {
144        let out_property = self_property.map(|annotated| {
145            // TODO: maybe add FilePath(PathBuf) template type?
146            annotated
147                .path
148                .as_ref()
149                .map_or_else(String::new, |path| path.to_string_lossy().into_owned())
150        });
151        Ok(out_property.into_dyn_wrapped())
152    });
153    language.add_keyword("overridden", |self_property| {
154        let out_property = self_property.map(|annotated| annotated.is_overridden);
155        Ok(out_property.into_dyn_wrapped())
156    });
157    language
158}